/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2017 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 #include "xenia/base/logging.h" DEFINE_bool(spv_validate, false, "Validate SPIR-V shaders after generation"); DEFINE_bool(spv_disasm, false, "Disassemble SPIR-V shaders after generation"); namespace xe { namespace gpu { using namespace ucode; constexpr uint32_t kMaxInterpolators = 16; constexpr uint32_t kMaxTemporaryRegisters = 64; using spv::GLSLstd450; using spv::Id; using spv::Op; SpirvShaderTranslator::SpirvShaderTranslator() {} SpirvShaderTranslator::~SpirvShaderTranslator() = default; void SpirvShaderTranslator::StartTranslation() { // Create a new builder. builder_ = std::make_unique(SPV_VERSION, 0xFFFFFFFF, nullptr); auto& b = *builder_; // Import required modules. glsl_std_450_instruction_set_ = b.import("GLSL.std.450"); // Configure environment. b.setSource(spv::SourceLanguage::SourceLanguageUnknown, 0); b.setMemoryModel(spv::AddressingModel::AddressingModelLogical, spv::MemoryModel::MemoryModelGLSL450); b.addCapability(spv::Capability::CapabilityShader); b.addCapability(spv::Capability::CapabilityImageQuery); if (is_vertex_shader()) { b.addCapability(spv::Capability::CapabilityClipDistance); b.addCapability(spv::Capability::CapabilityCullDistance); } if (is_pixel_shader()) { b.addCapability(spv::Capability::CapabilityDerivativeControl); } bool_type_ = b.makeBoolType(); float_type_ = b.makeFloatType(32); int_type_ = b.makeIntType(32); uint_type_ = b.makeUintType(32); vec2_int_type_ = b.makeVectorType(int_type_, 2); vec2_uint_type_ = b.makeVectorType(uint_type_, 2); vec2_float_type_ = b.makeVectorType(float_type_, 2); vec3_float_type_ = b.makeVectorType(float_type_, 3); vec4_float_type_ = b.makeVectorType(float_type_, 4); vec4_int_type_ = b.makeVectorType(int_type_, 4); vec4_uint_type_ = b.makeVectorType(uint_type_, 4); vec2_bool_type_ = b.makeVectorType(bool_type_, 2); vec3_bool_type_ = b.makeVectorType(bool_type_, 3); vec4_bool_type_ = b.makeVectorType(bool_type_, 4); vec4_float_one_ = b.makeCompositeConstant( vec4_float_type_, std::vector({b.makeFloatConstant(1.f), b.makeFloatConstant(1.f), b.makeFloatConstant(1.f), b.makeFloatConstant(1.f)})); vec4_float_zero_ = b.makeCompositeConstant( vec4_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(0.f), b.makeFloatConstant(0.f), b.makeFloatConstant(0.f)})); cube_function_ = CreateCubeFunction(); spv::Block* function_block = nullptr; translated_main_ = b.makeFunctionEntry(spv::NoPrecision, b.makeVoidType(), "translated_main", {}, {}, &function_block); registers_type_ = b.makeArrayType(vec4_float_type_, b.makeUintConstant(register_count()), 0); registers_ptr_ = b.createVariable(spv::StorageClass::StorageClassFunction, registers_type_, "r"); aL_ = b.createVariable(spv::StorageClass::StorageClassFunction, vec4_uint_type_, "aL"); loop_count_ = b.createVariable(spv::StorageClass::StorageClassFunction, vec4_uint_type_, "loop_count"); p0_ = b.createVariable(spv::StorageClass::StorageClassFunction, bool_type_, "p0"); ps_ = b.createVariable(spv::StorageClass::StorageClassFunction, float_type_, "ps"); pv_ = b.createVariable(spv::StorageClass::StorageClassFunction, vec4_float_type_, "pv"); pc_ = b.createVariable(spv::StorageClass::StorageClassFunction, int_type_, "pc"); a0_ = b.createVariable(spv::StorageClass::StorageClassFunction, int_type_, "a0"); // Uniform constants. Id float_consts_type = b.makeArrayType(vec4_float_type_, b.makeUintConstant(512), 1); Id loop_consts_type = b.makeArrayType(uint_type_, b.makeUintConstant(32), 1); Id bool_consts_type = b.makeArrayType(uint_type_, b.makeUintConstant(8), 1); // Strides b.addDecoration(float_consts_type, spv::Decoration::DecorationArrayStride, 4 * sizeof(float)); b.addDecoration(loop_consts_type, spv::Decoration::DecorationArrayStride, sizeof(uint32_t)); b.addDecoration(bool_consts_type, spv::Decoration::DecorationArrayStride, sizeof(uint32_t)); Id consts_struct_type = b.makeStructType( {float_consts_type, loop_consts_type, bool_consts_type}, "consts_type"); b.addDecoration(consts_struct_type, spv::Decoration::DecorationBlock); // Constants member decorations. b.addMemberDecoration(consts_struct_type, 0, spv::Decoration::DecorationOffset, 0); b.addMemberName(consts_struct_type, 0, "float_consts"); b.addMemberDecoration(consts_struct_type, 1, spv::Decoration::DecorationOffset, 512 * 4 * sizeof(float)); b.addMemberName(consts_struct_type, 1, "loop_consts"); b.addMemberDecoration(consts_struct_type, 2, spv::Decoration::DecorationOffset, 512 * 4 * sizeof(float) + 32 * sizeof(uint32_t)); b.addMemberName(consts_struct_type, 2, "bool_consts"); consts_ = b.createVariable(spv::StorageClass::StorageClassUniform, consts_struct_type, "consts"); b.addDecoration(consts_, spv::Decoration::DecorationDescriptorSet, 0); if (is_vertex_shader()) { b.addDecoration(consts_, spv::Decoration::DecorationBinding, 0); } else if (is_pixel_shader()) { b.addDecoration(consts_, spv::Decoration::DecorationBinding, 1); } // Push constants, represented by SpirvPushConstants. Id push_constants_type = b.makeStructType({vec4_float_type_, vec4_float_type_, vec4_float_type_, vec4_float_type_, uint_type_}, "push_consts_type"); b.addDecoration(push_constants_type, spv::Decoration::DecorationBlock); // float4 window_scale; b.addMemberDecoration( push_constants_type, 0, spv::Decoration::DecorationOffset, static_cast(offsetof(SpirvPushConstants, window_scale))); b.addMemberName(push_constants_type, 0, "window_scale"); // float4 vtx_fmt; b.addMemberDecoration( push_constants_type, 1, spv::Decoration::DecorationOffset, static_cast(offsetof(SpirvPushConstants, vtx_fmt))); b.addMemberName(push_constants_type, 1, "vtx_fmt"); // float4 vtx_fmt; b.addMemberDecoration( push_constants_type, 2, spv::Decoration::DecorationOffset, static_cast(offsetof(SpirvPushConstants, point_size))); b.addMemberName(push_constants_type, 2, "point_size"); // float4 alpha_test; b.addMemberDecoration( push_constants_type, 3, spv::Decoration::DecorationOffset, static_cast(offsetof(SpirvPushConstants, alpha_test))); b.addMemberName(push_constants_type, 3, "alpha_test"); // uint ps_param_gen; b.addMemberDecoration( push_constants_type, 4, spv::Decoration::DecorationOffset, static_cast(offsetof(SpirvPushConstants, ps_param_gen))); b.addMemberName(push_constants_type, 4, "ps_param_gen"); push_consts_ = b.createVariable(spv::StorageClass::StorageClassPushConstant, push_constants_type, "push_consts"); image_2d_type_ = b.makeImageType(float_type_, spv::Dim::Dim2D, false, false, false, 1, spv::ImageFormat::ImageFormatUnknown); image_3d_type_ = b.makeImageType(float_type_, spv::Dim::Dim3D, false, false, false, 1, spv::ImageFormat::ImageFormatUnknown); image_cube_type_ = b.makeImageType(float_type_, spv::Dim::DimCube, false, false, false, 1, spv::ImageFormat::ImageFormatUnknown); // Texture bindings Id tex_t[] = {b.makeSampledImageType(image_2d_type_), b.makeSampledImageType(image_3d_type_), b.makeSampledImageType(image_cube_type_)}; Id tex_a_t[] = {b.makeArrayType(tex_t[0], b.makeUintConstant(32), 0), b.makeArrayType(tex_t[1], b.makeUintConstant(32), 0), b.makeArrayType(tex_t[2], b.makeUintConstant(32), 0)}; // Create 3 texture types, all aliased on the same binding for (int i = 0; i < 3; i++) { tex_[i] = b.createVariable(spv::StorageClass::StorageClassUniformConstant, tex_a_t[i], xe::format_string("textures%dD", i + 2).c_str()); b.addDecoration(tex_[i], spv::Decoration::DecorationDescriptorSet, 1); b.addDecoration(tex_[i], spv::Decoration::DecorationBinding, 0); } // Interpolators. Id interpolators_type = b.makeArrayType( vec4_float_type_, b.makeUintConstant(kMaxInterpolators), 0); if (is_vertex_shader()) { // Vertex inputs/outputs // Inputs: 32 SSBOs on DS 2 binding 0 Id vtx_t = b.makeRuntimeArray(uint_type_); Id vtx_a_t = b.makeArrayType(vtx_t, b.makeUintConstant(32), 0); vtx_ = b.createVariable(spv::StorageClass::StorageClassUniform, vtx_a_t, "vertex_bindings"); // Outputs interpolators_ = b.createVariable(spv::StorageClass::StorageClassOutput, interpolators_type, "interpolators"); b.addDecoration(interpolators_, spv::Decoration::DecorationLocation, 0); for (uint32_t i = 0; i < std::min(register_count(), kMaxInterpolators); i++) { // Zero interpolators. auto ptr = b.createAccessChain(spv::StorageClass::StorageClassOutput, interpolators_, std::vector({b.makeUintConstant(i)})); b.createStore(vec4_float_zero_, ptr); } point_size_ = b.createVariable(spv::StorageClass::StorageClassOutput, float_type_, "point_size"); b.addDecoration(point_size_, spv::Decoration::DecorationLocation, 17); // Set default point-size value (-1.0f, indicating to the geometry shader // that the register value should be used instead of the per-vertex value) b.createStore(b.makeFloatConstant(-1.0f), point_size_); point_coord_ = b.createVariable(spv::StorageClass::StorageClassOutput, vec2_float_type_, "point_coord"); b.addDecoration(point_coord_, spv::Decoration::DecorationLocation, 16); // point_coord is only ever populated in a geometry shader. Just write // zero to it in the vertex shader. b.createStore( b.makeCompositeConstant(vec2_float_type_, std::vector({b.makeFloatConstant(0.0f), b.makeFloatConstant(0.0f)})), point_coord_); pos_ = b.createVariable(spv::StorageClass::StorageClassOutput, vec4_float_type_, "gl_Position"); b.addDecoration(pos_, spv::Decoration::DecorationBuiltIn, spv::BuiltIn::BuiltInPosition); vertex_idx_ = b.createVariable(spv::StorageClass::StorageClassInput, int_type_, "gl_VertexIndex"); b.addDecoration(vertex_idx_, spv::Decoration::DecorationBuiltIn, spv::BuiltIn::BuiltInVertexIndex); interface_ids_.push_back(interpolators_); interface_ids_.push_back(point_coord_); interface_ids_.push_back(point_size_); interface_ids_.push_back(pos_); interface_ids_.push_back(vertex_idx_); auto vertex_idx = b.createLoad(vertex_idx_); vertex_idx = b.createUnaryOp(spv::Op::OpConvertSToF, float_type_, vertex_idx); auto r0_ptr = b.createAccessChain(spv::StorageClass::StorageClassFunction, registers_ptr_, std::vector({b.makeUintConstant(0)})); auto r0 = b.createLoad(r0_ptr); r0 = b.createCompositeInsert(vertex_idx, r0, vec4_float_type_, 0); b.createStore(r0, r0_ptr); } else { // Pixel inputs from vertex shader. interpolators_ = b.createVariable(spv::StorageClass::StorageClassInput, interpolators_type, "interpolators"); b.addDecoration(interpolators_, spv::Decoration::DecorationLocation, 0); point_coord_ = b.createVariable(spv::StorageClass::StorageClassInput, vec2_float_type_, "point_coord"); b.addDecoration(point_coord_, spv::Decoration::DecorationLocation, 16); // Pixel fragment outputs (one per render target). Id frag_outputs_type = b.makeArrayType(vec4_float_type_, b.makeUintConstant(4), 0); frag_outputs_ = b.createVariable(spv::StorageClass::StorageClassOutput, frag_outputs_type, "oC"); b.addDecoration(frag_outputs_, spv::Decoration::DecorationLocation, 0); frag_depth_ = b.createVariable(spv::StorageClass::StorageClassOutput, float_type_, "gl_FragDepth"); b.addDecoration(frag_depth_, spv::Decoration::DecorationBuiltIn, spv::BuiltIn::BuiltInFragDepth); interface_ids_.push_back(interpolators_); interface_ids_.push_back(point_coord_); interface_ids_.push_back(frag_outputs_); interface_ids_.push_back(frag_depth_); // TODO(benvanik): frag depth, etc. // TODO(DrChat): Verify this naive, stupid approach to uninitialized values. for (uint32_t i = 0; i < 4; i++) { auto idx = b.makeUintConstant(i); auto oC = b.createAccessChain(spv::StorageClass::StorageClassOutput, frag_outputs_, std::vector({idx})); b.createStore(vec4_float_zero_, oC); } // Copy interpolators to r[0..16]. // TODO: Need physical addressing in order to do this. // b.createNoResultOp(spv::Op::OpCopyMemorySized, // {registers_ptr_, interpolators_, // b.makeUintConstant(16 * 4 * sizeof(float))}); for (uint32_t i = 0; i < std::min(register_count(), kMaxInterpolators); i++) { // For now, copy interpolators register-by-register :/ auto idx = b.makeUintConstant(i); auto i_a = b.createAccessChain(spv::StorageClass::StorageClassInput, interpolators_, std::vector({idx})); auto r_a = b.createAccessChain(spv::StorageClass::StorageClassFunction, registers_ptr_, std::vector({idx})); b.createNoResultOp(spv::Op::OpCopyMemory, std::vector({r_a, i_a})); } // Setup ps_param_gen auto ps_param_gen_idx_ptr = b.createAccessChain( spv::StorageClass::StorageClassPushConstant, push_consts_, std::vector({b.makeUintConstant(4)})); auto ps_param_gen_idx = b.createLoad(ps_param_gen_idx_ptr); auto frag_coord = b.createVariable(spv::StorageClass::StorageClassInput, vec4_float_type_, "gl_FragCoord"); b.addDecoration(frag_coord, spv::Decoration::DecorationBuiltIn, spv::BuiltIn::BuiltInFragCoord); interface_ids_.push_back(frag_coord); auto param = b.createOp( spv::Op::OpVectorShuffle, vec4_float_type_, {b.createLoad(frag_coord), b.createLoad(point_coord_), 0, 1, 4, 5}); /* // TODO: gl_FrontFacing auto param_x = b.createCompositeExtract(param, float_type_, 0); auto param_x_inv = b.createBinOp(spv::Op::OpFMul, float_type_, param_x, b.makeFloatConstant(-1.f)); param_x = b.createCompositeInsert(param_x_inv, param, vec4_float_type_, 0); */ auto cond = b.createBinOp(spv::Op::OpINotEqual, bool_type_, ps_param_gen_idx, b.makeUintConstant(-1)); spv::Builder::If ifb(cond, 0, b); // FYI: We do this instead of r[ps_param_gen_idx] because that causes // nvidia to move all registers into local memory (slow!) for (uint32_t i = 0; i < std::min(register_count(), kMaxInterpolators); i++) { auto reg_ptr = b.createAccessChain( spv::StorageClass::StorageClassFunction, registers_ptr_, std::vector({b.makeUintConstant(i)})); auto cond = b.createBinOp(spv::Op::OpIEqual, bool_type_, ps_param_gen_idx, b.makeUintConstant(i)); cond = b.smearScalar(spv::NoPrecision, cond, vec4_bool_type_); auto reg = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, cond, param, b.createLoad(reg_ptr)); b.createStore(reg, reg_ptr); } ifb.makeEndIf(); } b.createStore(b.makeIntConstant(0x0), pc_); loop_head_block_ = &b.makeNewBlock(); auto block = &b.makeNewBlock(); loop_body_block_ = &b.makeNewBlock(); loop_cont_block_ = &b.makeNewBlock(); loop_exit_block_ = &b.makeNewBlock(); b.createBranch(loop_head_block_); // Setup continue block b.setBuildPoint(loop_cont_block_); b.createBranch(loop_head_block_); // While loop header block b.setBuildPoint(loop_head_block_); b.createLoopMerge(loop_exit_block_, loop_cont_block_, spv::LoopControlMask::LoopControlDontUnrollMask, 0); b.createBranch(block); // Condition block b.setBuildPoint(block); // while (pc != 0xFFFF) auto c = b.createBinOp(spv::Op::OpINotEqual, bool_type_, b.createLoad(pc_), b.makeIntConstant(0xFFFF)); b.createConditionalBranch(c, loop_body_block_, loop_exit_block_); b.setBuildPoint(loop_body_block_); } std::vector SpirvShaderTranslator::CompleteTranslation() { auto& b = *builder_; assert_false(open_predicated_block_); b.setBuildPoint(loop_exit_block_); b.makeReturn(false); exec_cond_ = false; exec_skip_block_ = nullptr; // main() entry point. auto mainFn = b.makeFunctionEntry(spv::NoPrecision, b.makeVoidType(), "main", {}, {}); if (is_vertex_shader()) { auto entry = b.addEntryPoint(spv::ExecutionModel::ExecutionModelVertex, mainFn, "main"); for (auto id : interface_ids_) { entry->addIdOperand(id); } } else { auto entry = b.addEntryPoint(spv::ExecutionModel::ExecutionModelFragment, mainFn, "main"); b.addExecutionMode(mainFn, spv::ExecutionModeOriginUpperLeft); // FIXME(DrChat): We need to declare the DepthReplacing execution mode if // we write depth, and we must unconditionally write depth if declared! for (auto id : interface_ids_) { entry->addIdOperand(id); } } // TODO(benvanik): transform feedback. if (false) { b.addCapability(spv::Capability::CapabilityTransformFeedback); b.addExecutionMode(mainFn, spv::ExecutionMode::ExecutionModeXfb); } b.createFunctionCall(translated_main_, std::vector({})); if (is_vertex_shader()) { // gl_Position transform auto vtx_fmt_ptr = b.createAccessChain( spv::StorageClass::StorageClassPushConstant, push_consts_, std::vector({b.makeUintConstant(1)})); auto window_scale_ptr = b.createAccessChain( spv::StorageClass::StorageClassPushConstant, push_consts_, std::vector({b.makeUintConstant(0)})); auto vtx_fmt = b.createLoad(vtx_fmt_ptr); auto window_scale = b.createLoad(window_scale_ptr); auto p = b.createLoad(pos_); auto c = b.createBinOp(spv::Op::OpFOrdNotEqual, vec4_bool_type_, vtx_fmt, vec4_float_zero_); // pos.w = vtx_fmt.w == 0.0 ? 1.0 / pos.w : pos.w auto c_w = b.createCompositeExtract(c, bool_type_, 3); auto p_w = b.createCompositeExtract(p, float_type_, 3); auto p_w_inv = b.createBinOp(spv::Op::OpFDiv, float_type_, b.makeFloatConstant(1.f), p_w); p_w = b.createTriOp(spv::Op::OpSelect, float_type_, c_w, p_w, p_w_inv); // pos.xyz = vtx_fmt.xyz != 0.0 ? pos.xyz / pos.w : pos.xyz auto p_all_w = b.smearScalar(spv::NoPrecision, p_w, vec4_float_type_); auto p_inv = b.createBinOp(spv::Op::OpFDiv, vec4_float_type_, p, p_all_w); p = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, p_inv, p); // Reinsert w p = b.createCompositeInsert(p_w, p, vec4_float_type_, 3); // Apply window scaling // pos.xy *= window_scale.xy auto p_scaled = b.createBinOp(spv::Op::OpFMul, vec4_float_type_, p, window_scale); p = b.createOp(spv::Op::OpVectorShuffle, vec4_float_type_, {p, p_scaled, 4, 5, 2, 3}); b.createStore(p, pos_); } else { // Alpha test auto alpha_test_ptr = b.createAccessChain( spv::StorageClass::StorageClassPushConstant, push_consts_, std::vector({b.makeUintConstant(3)})); auto alpha_test = b.createLoad(alpha_test_ptr); auto alpha_test_enabled = b.createCompositeExtract(alpha_test, float_type_, 0); auto alpha_test_func = b.createCompositeExtract(alpha_test, float_type_, 1); auto alpha_test_ref = b.createCompositeExtract(alpha_test, float_type_, 2); alpha_test_func = b.createUnaryOp(spv::Op::OpConvertFToU, uint_type_, alpha_test_func); auto oC0_ptr = b.createAccessChain( spv::StorageClass::StorageClassOutput, frag_outputs_, std::vector({b.makeUintConstant(0)})); auto oC0_alpha = b.createCompositeExtract(b.createLoad(oC0_ptr), float_type_, 3); auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, alpha_test_enabled, b.makeFloatConstant(1.f)); spv::Builder::If alpha_if(cond, 0, b); std::vector switch_segments; b.makeSwitch( alpha_test_func, 0, 8, std::vector({0, 1, 2, 3, 4, 5, 6, 7}), std::vector({0, 1, 2, 3, 4, 5, 6, 7}), 7, switch_segments); const static spv::Op alpha_op_map[] = { spv::Op::OpNop, spv::Op::OpFOrdGreaterThanEqual, spv::Op::OpFOrdNotEqual, spv::Op::OpFOrdGreaterThan, spv::Op::OpFOrdLessThanEqual, spv::Op::OpFOrdEqual, spv::Op::OpFOrdLessThan, spv::Op::OpNop, }; // if (alpha_func == 0) passes = false; b.nextSwitchSegment(switch_segments, 0); b.makeDiscard(); b.addSwitchBreak(); for (int i = 1; i < 7; i++) { b.nextSwitchSegment(switch_segments, i); auto cond = b.createBinOp(alpha_op_map[i], bool_type_, oC0_alpha, alpha_test_ref); spv::Builder::If discard_if(cond, 0, b); b.makeDiscard(); discard_if.makeEndIf(); b.addSwitchBreak(); } // if (alpha_func == 7) passes = true; b.nextSwitchSegment(switch_segments, 7); b.endSwitch(switch_segments); alpha_if.makeEndIf(); } b.makeReturn(false); // Compile the spv IR // compiler_.Compile(b.getModule()); std::vector spirv_words; b.dump(spirv_words); // Cleanup builder. cf_blocks_.clear(); loop_head_block_ = nullptr; loop_body_block_ = nullptr; loop_cont_block_ = nullptr; loop_exit_block_ = nullptr; builder_.reset(); interface_ids_.clear(); // Copy bytes out. // TODO(benvanik): avoid copy? std::vector spirv_bytes; spirv_bytes.resize(spirv_words.size() * 4); std::memcpy(spirv_bytes.data(), spirv_words.data(), spirv_bytes.size()); return spirv_bytes; } void SpirvShaderTranslator::PostTranslation(Shader* shader) { // Validation. if (FLAGS_spv_validate) { auto validation = validator_.Validate( reinterpret_cast(shader->translated_binary().data()), shader->translated_binary().size() / sizeof(uint32_t)); if (validation->has_error()) { XELOGE("SPIR-V Shader Validation failed! Error: %s", validation->error_string()); } } if (FLAGS_spv_disasm) { // TODO(benvanik): only if needed? could be slowish. auto disasm = disassembler_.Disassemble( reinterpret_cast(shader->translated_binary().data()), shader->translated_binary().size() / 4); if (disasm->has_error()) { XELOGE("Failed to disassemble SPIRV - invalid?"); } else { set_host_disassembly(shader, disasm->to_string()); } } } void SpirvShaderTranslator::PreProcessControlFlowInstructions( std::vector instrs) { auto& b = *builder_; auto default_block = &b.makeNewBlock(); switch_break_block_ = &b.makeNewBlock(); b.setBuildPoint(default_block); b.createStore(b.makeIntConstant(0xFFFF), pc_); b.createBranch(switch_break_block_); b.setBuildPoint(switch_break_block_); b.createBranch(loop_cont_block_); // Now setup the switch. default_block->addPredecessor(loop_body_block_); b.setBuildPoint(loop_body_block_); cf_blocks_.resize(instrs.size()); for (size_t i = 0; i < cf_blocks_.size(); i++) { cf_blocks_[i].block = &b.makeNewBlock(); cf_blocks_[i].labelled = false; } std::vector operands; operands.push_back(b.createLoad(pc_)); // Selector operands.push_back(default_block->getId()); // Default // Always have a case for block 0. operands.push_back(0); operands.push_back(cf_blocks_[0].block->getId()); cf_blocks_[0].block->addPredecessor(loop_body_block_); cf_blocks_[0].labelled = true; for (size_t i = 0; i < instrs.size(); i++) { auto& instr = instrs[i]; if (instr.opcode() == ucode::ControlFlowOpcode::kCondJmp) { uint32_t address = instr.cond_jmp.address(); if (!cf_blocks_[address].labelled) { cf_blocks_[address].labelled = true; operands.push_back(address); operands.push_back(cf_blocks_[address].block->getId()); cf_blocks_[address].block->addPredecessor(loop_body_block_); } if (!cf_blocks_[i + 1].labelled) { cf_blocks_[i + 1].labelled = true; operands.push_back(uint32_t(i + 1)); operands.push_back(cf_blocks_[i + 1].block->getId()); cf_blocks_[i + 1].block->addPredecessor(loop_body_block_); } } else if (instr.opcode() == ucode::ControlFlowOpcode::kLoopStart) { uint32_t address = instr.loop_start.address(); // Label the body if (!cf_blocks_[i + 1].labelled) { cf_blocks_[i + 1].labelled = true; operands.push_back(uint32_t(i + 1)); operands.push_back(cf_blocks_[i + 1].block->getId()); cf_blocks_[i + 1].block->addPredecessor(loop_body_block_); } // Label the loop skip address. if (!cf_blocks_[address].labelled) { cf_blocks_[address].labelled = true; operands.push_back(address); operands.push_back(cf_blocks_[address].block->getId()); cf_blocks_[address].block->addPredecessor(loop_body_block_); } } else if (instr.opcode() == ucode::ControlFlowOpcode::kLoopEnd) { uint32_t address = instr.loop_end.address(); if (!cf_blocks_[address].labelled) { cf_blocks_[address].labelled = true; operands.push_back(address); operands.push_back(cf_blocks_[address].block->getId()); cf_blocks_[address].block->addPredecessor(loop_body_block_); } } } b.createSelectionMerge(switch_break_block_, 0); b.createNoResultOp(spv::Op::OpSwitch, operands); } void SpirvShaderTranslator::ProcessLabel(uint32_t cf_index) { auto& b = *builder_; } void SpirvShaderTranslator::ProcessControlFlowInstructionBegin( uint32_t cf_index) { auto& b = *builder_; } void SpirvShaderTranslator::ProcessControlFlowInstructionEnd( uint32_t cf_index) { auto& b = *builder_; } void SpirvShaderTranslator::ProcessControlFlowNopInstruction( uint32_t cf_index) { auto& b = *builder_; auto head = cf_blocks_[cf_index].block; b.setBuildPoint(head); b.createNoResultOp(spv::Op::OpNop); if (cf_blocks_.size() > cf_index + 1) { b.createBranch(cf_blocks_[cf_index + 1].block); } else { b.makeReturn(false); } } void SpirvShaderTranslator::ProcessExecInstructionBegin( const ParsedExecInstruction& instr) { auto& b = *builder_; assert_false(open_predicated_block_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; // Head has the logic to check if the body should execute. auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); auto body = head; switch (instr.type) { case ParsedExecInstruction::Type::kUnconditional: { // No need to do anything. exec_cond_ = false; } break; case ParsedExecInstruction::Type::kConditional: { // Based off of bool_consts std::vector offsets; offsets.push_back(b.makeUintConstant(2)); // bool_consts offsets.push_back(b.makeUintConstant(instr.bool_constant_index / 32)); auto v = b.createAccessChain(spv::StorageClass::StorageClassUniform, consts_, offsets); v = b.createLoad(v); // Bitfield extract the bool constant. // FIXME: NVidia's compiler seems to be broken on this instruction? /* v = b.createTriOp(spv::Op::OpBitFieldUExtract, uint_type_, v, b.makeUintConstant(instr.bool_constant_index % 32), b.makeUintConstant(1)); auto cond = b.createBinOp(spv::Op::OpIEqual, bool_type_, v, b.makeUintConstant(instr.condition ? 1 : 0)); */ v = b.createBinOp( spv::Op::OpBitwiseAnd, uint_type_, v, b.makeUintConstant(1 << (instr.bool_constant_index % 32))); auto cond = b.createBinOp( instr.condition ? spv::Op::OpINotEqual : spv::Op::OpIEqual, bool_type_, v, b.makeUintConstant(0)); // Conditional branch body = &b.makeNewBlock(); exec_cond_ = true; exec_skip_block_ = &b.makeNewBlock(); b.createSelectionMerge( exec_skip_block_, spv::SelectionControlMask::SelectionControlMaskNone); b.createConditionalBranch(cond, body, exec_skip_block_); b.setBuildPoint(exec_skip_block_); if (!instr.is_end || cf_blocks_.size() > instr.dword_index + 1) { assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } else { b.makeReturn(false); } } break; case ParsedExecInstruction::Type::kPredicated: { // Branch based on p0. body = &b.makeNewBlock(); exec_cond_ = true; exec_skip_block_ = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.condition)); b.createSelectionMerge( exec_skip_block_, spv::SelectionControlMask::SelectionControlMaskNone); b.createConditionalBranch(cond, body, exec_skip_block_); b.setBuildPoint(exec_skip_block_); if (!instr.is_end || cf_blocks_.size() > instr.dword_index + 1) { assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } else { b.makeReturn(false); } } break; } b.setBuildPoint(body); } void SpirvShaderTranslator::ProcessExecInstructionEnd( const ParsedExecInstruction& instr) { auto& b = *builder_; if (open_predicated_block_) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } if (instr.is_end) { b.makeReturn(false); } else if (exec_cond_) { b.createBranch(exec_skip_block_); } else { assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } } void SpirvShaderTranslator::ProcessLoopStartInstruction( const ParsedLoopStartInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); // loop il, L - loop with loop data il, end @ L std::vector offsets; offsets.push_back(b.makeUintConstant(1)); // loop_consts offsets.push_back(b.makeUintConstant(instr.loop_constant_index)); auto loop_const = b.createAccessChain(spv::StorageClass::StorageClassUniform, consts_, offsets); loop_const = b.createLoad(loop_const); // uint loop_count_value = loop_const & 0xFF; auto loop_count_value = b.createBinOp(spv::Op::OpBitwiseAnd, uint_type_, loop_const, b.makeUintConstant(0xFF)); // uint loop_aL_value = (loop_const >> 8) & 0xFF; auto loop_aL_value = b.createBinOp(spv::Op::OpShiftRightLogical, uint_type_, loop_const, b.makeUintConstant(8)); loop_aL_value = b.createBinOp(spv::Op::OpBitwiseAnd, uint_type_, loop_aL_value, b.makeUintConstant(0xFF)); // loop_count_ = uvec4(loop_count_value, loop_count_.xyz); auto loop_count = b.createLoad(loop_count_); loop_count = b.createRvalueSwizzle(spv::NoPrecision, vec4_uint_type_, loop_count, std::vector({0, 0, 1, 2})); loop_count = b.createCompositeInsert(loop_count_value, loop_count, vec4_uint_type_, 0); b.createStore(loop_count, loop_count_); // aL = aL.xxyz; auto aL = b.createLoad(aL_); aL = b.createRvalueSwizzle(spv::NoPrecision, vec4_uint_type_, aL, std::vector({0, 0, 1, 2})); if (!instr.is_repeat) { // aL.x = loop_aL_value; aL = b.createCompositeInsert(loop_aL_value, aL, vec4_uint_type_, 0); } b.createStore(aL, aL_); // Short-circuit if loop counter is 0 auto cond = b.createBinOp(spv::Op::OpIEqual, bool_type_, loop_count_value, b.makeUintConstant(0)); auto next_pc = b.createTriOp(spv::Op::OpSelect, int_type_, cond, b.makeIntConstant(instr.loop_skip_address), b.makeIntConstant(instr.dword_index + 1)); b.createStore(next_pc, pc_); b.createBranch(switch_break_block_); } void SpirvShaderTranslator::ProcessLoopEndInstruction( const ParsedLoopEndInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); // endloop il, L - end loop w/ data il, head @ L auto loop_count = b.createLoad(loop_count_); auto count = b.createCompositeExtract(loop_count, uint_type_, 0); count = b.createBinOp(spv::Op::OpISub, uint_type_, count, b.makeUintConstant(1)); loop_count = b.createCompositeInsert(count, loop_count, vec4_uint_type_, 0); b.createStore(loop_count, loop_count_); // if (--loop_count_.x == 0 || [!]p0) auto c1 = b.createBinOp(spv::Op::OpIEqual, bool_type_, count, b.makeUintConstant(0)); auto c2 = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.predicate_condition)); auto cond = b.createBinOp(spv::Op::OpLogicalOr, bool_type_, c1, c2); auto loop = &b.makeNewBlock(); auto end = &b.makeNewBlock(); auto tail = &b.makeNewBlock(); b.createSelectionMerge(tail, spv::SelectionControlMaskNone); b.createConditionalBranch(cond, end, loop); // ================================================ // Loop completed - pop the current loop off the stack and exit b.setBuildPoint(end); loop_count = b.createLoad(loop_count_); auto aL = b.createLoad(aL_); // loop_count = loop_count.yzw0 loop_count = b.createRvalueSwizzle(spv::NoPrecision, vec4_uint_type_, loop_count, std::vector({1, 2, 3, 3})); loop_count = b.createCompositeInsert(b.makeUintConstant(0), loop_count, vec4_uint_type_, 3); b.createStore(loop_count, loop_count_); // aL = aL.yzw0 aL = b.createRvalueSwizzle(spv::NoPrecision, vec4_uint_type_, aL, std::vector({1, 2, 3, 3})); aL = b.createCompositeInsert(b.makeUintConstant(0), aL, vec4_uint_type_, 3); b.createStore(aL, aL_); // Update pc with the next block // pc_ = instr.dword_index + 1 b.createStore(b.makeIntConstant(instr.dword_index + 1), pc_); b.createBranch(tail); // ================================================ // Still looping - increment aL and loop b.setBuildPoint(loop); aL = b.createLoad(aL_); auto aL_x = b.createCompositeExtract(aL, uint_type_, 0); std::vector offsets; offsets.push_back(b.makeUintConstant(1)); // loop_consts offsets.push_back(b.makeUintConstant(instr.loop_constant_index)); auto loop_const = b.createAccessChain(spv::StorageClass::StorageClassUniform, consts_, offsets); loop_const = b.createLoad(loop_const); // uint loop_aL_value = (loop_const >> 16) & 0xFF; auto loop_aL_value = b.createBinOp(spv::Op::OpShiftRightLogical, uint_type_, loop_const, b.makeUintConstant(16)); loop_aL_value = b.createBinOp(spv::Op::OpBitwiseAnd, uint_type_, loop_aL_value, b.makeUintConstant(0xFF)); aL_x = b.createBinOp(spv::Op::OpIAdd, uint_type_, aL_x, loop_aL_value); aL = b.createCompositeInsert(aL_x, aL, vec4_uint_type_, 0); b.createStore(aL, aL_); // pc_ = instr.loop_body_address; b.createStore(b.makeIntConstant(instr.loop_body_address), pc_); b.createBranch(tail); // ================================================ b.setBuildPoint(tail); b.createBranch(switch_break_block_); } void SpirvShaderTranslator::ProcessCallInstruction( const ParsedCallInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); // Unused instruction(?) assert_always(); EmitUnimplementedTranslationError(); assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } void SpirvShaderTranslator::ProcessReturnInstruction( const ParsedReturnInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); // Unused instruction(?) assert_always(); EmitUnimplementedTranslationError(); assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } // CF jump void SpirvShaderTranslator::ProcessJumpInstruction( const ParsedJumpInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); switch (instr.type) { case ParsedJumpInstruction::Type::kUnconditional: { b.createStore(b.makeIntConstant(instr.target_address), pc_); b.createBranch(switch_break_block_); } break; case ParsedJumpInstruction::Type::kConditional: { assert_true(cf_blocks_.size() > instr.dword_index + 1); // Based off of bool_consts std::vector offsets; offsets.push_back(b.makeUintConstant(2)); // bool_consts offsets.push_back(b.makeUintConstant(instr.bool_constant_index / 32)); auto v = b.createAccessChain(spv::StorageClass::StorageClassUniform, consts_, offsets); v = b.createLoad(v); // FIXME: NVidia's compiler seems to be broken on this instruction? /* // Bitfield extract the bool constant. v = b.createTriOp(spv::Op::OpBitFieldUExtract, uint_type_, v, b.makeUintConstant(instr.bool_constant_index % 32), b.makeUintConstant(1)); // Conditional branch auto cond = b.createBinOp(spv::Op::OpIEqual, bool_type_, v, b.makeUintConstant(instr.condition ? 1 : 0)); */ v = b.createBinOp( spv::Op::OpBitwiseAnd, uint_type_, v, b.makeUintConstant(1 << (instr.bool_constant_index % 32))); auto cond = b.createBinOp( instr.condition ? spv::Op::OpINotEqual : spv::Op::OpIEqual, bool_type_, v, b.makeUintConstant(0)); auto next_pc = b.createTriOp(spv::Op::OpSelect, int_type_, cond, b.makeIntConstant(instr.target_address), b.makeIntConstant(instr.dword_index + 1)); b.createStore(next_pc, pc_); b.createBranch(switch_break_block_); } break; case ParsedJumpInstruction::Type::kPredicated: { assert_true(cf_blocks_.size() > instr.dword_index + 1); auto cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.condition)); auto next_pc = b.createTriOp(spv::Op::OpSelect, int_type_, cond, b.makeIntConstant(instr.target_address), b.makeIntConstant(instr.dword_index + 1)); b.createStore(next_pc, pc_); b.createBranch(switch_break_block_); } break; } } void SpirvShaderTranslator::ProcessAllocInstruction( const ParsedAllocInstruction& instr) { auto& b = *builder_; auto head = cf_blocks_[instr.dword_index].block; b.setBuildPoint(head); switch (instr.type) { case AllocType::kNone: { // ? } break; case AllocType::kVsPosition: { assert_true(is_vertex_shader()); } break; // Also PS Colors case AllocType::kVsInterpolators: { // Already included, nothing to do here. } break; case AllocType::kMemory: { // Nothing to do for this. } break; default: break; } assert_true(cf_blocks_.size() > instr.dword_index + 1); b.createBranch(cf_blocks_[instr.dword_index + 1].block); } spv::Id SpirvShaderTranslator::BitfieldExtract(spv::Id result_type, spv::Id base, bool is_signed, uint32_t offset, uint32_t count) { auto& b = *builder_; spv::Id base_type = b.getTypeId(base); // <-- 32 - (offset + count) ------ [bits] -?- if (32 - (offset + count) > 0) { base = b.createBinOp(spv::Op::OpShiftLeftLogical, base_type, base, b.makeUintConstant(32 - (offset + count))); } // [bits] -?-?-?--------------------------- auto op = is_signed ? spv::Op::OpShiftRightArithmetic : spv::Op::OpShiftRightLogical; base = b.createBinOp(op, base_type, base, b.makeUintConstant(32 - count)); return base; } spv::Id SpirvShaderTranslator::ConvertNormVar(spv::Id var, spv::Id result_type, uint32_t bits, bool is_signed) { auto& b = *builder_; if (is_signed) { auto c = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, var, b.makeFloatConstant(-float(1 << (bits - 1)))); auto v = b.createBinOp(spv::Op::OpFDiv, result_type, var, b.makeFloatConstant(float((1 << (bits - 1)) - 1))); var = b.createTriOp(spv::Op::OpSelect, result_type, c, b.makeFloatConstant(-1.f), v); } else { var = b.createBinOp(spv::Op::OpFDiv, result_type, var, b.makeFloatConstant(float((1 << bits) - 1))); } return var; } void SpirvShaderTranslator::ProcessVertexFetchInstruction( const ParsedVertexFetchInstruction& instr) { auto& b = *builder_; assert_true(is_vertex_shader()); assert_not_zero(vertex_idx_); // Close the open predicated block if this instr isn't predicated or the // conditions do not match. if (open_predicated_block_ && (!instr.is_predicated || instr.predicate_condition != predicated_block_cond_)) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } if (!open_predicated_block_ && instr.is_predicated) { Id pred_cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.predicate_condition)); auto block = &b.makeNewBlock(); open_predicated_block_ = true; predicated_block_cond_ = instr.predicate_condition; predicated_block_end_ = &b.makeNewBlock(); b.createSelectionMerge(predicated_block_end_, spv::SelectionControlMaskNone); b.createConditionalBranch(pred_cond, block, predicated_block_end_); b.setBuildPoint(block); } // Operand 0 is the index // Operand 1 is the binding // TODO: Indexed fetch auto vertex_idx = LoadFromOperand(instr.operands[0]); vertex_idx = b.createUnaryOp(spv::Op::OpConvertFToS, int_type_, vertex_idx); auto shader_vertex_idx = b.createLoad(vertex_idx_); auto vertex_components = GetVertexFormatComponentCount(instr.attributes.data_format); // Skip loading if it's an indexed fetch. auto cond = b.createBinOp(spv::Op::OpIEqual, bool_type_, vertex_idx, shader_vertex_idx); Id alt_vertex = 0; switch (vertex_components) { case 1: alt_vertex = b.makeFloatConstant(0.f); break; case 2: alt_vertex = b.makeCompositeConstant( vec2_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)})); cond = b.smearScalar(spv::NoPrecision, cond, vec2_bool_type_); break; case 3: alt_vertex = b.makeCompositeConstant( vec3_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)})); cond = b.smearScalar(spv::NoPrecision, cond, vec3_bool_type_); break; case 4: alt_vertex = b.makeCompositeConstant( vec4_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(0.f), b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)})); cond = b.smearScalar(spv::NoPrecision, cond, vec4_bool_type_); break; default: assert_unhandled_case(vertex_components); } auto vertex_ptr = vertex_binding_map_[instr.operands[1].storage_index] [instr.attributes.offset]; assert_not_zero(vertex_ptr); spv::Id vertex = b.createLoad(vertex_ptr); switch (instr.attributes.data_format) { case VertexFormat::k_8_8_8_8: case VertexFormat::k_2_10_10_10: case VertexFormat::k_16_16: case VertexFormat::k_16_16_16_16: case VertexFormat::k_16_16_FLOAT: case VertexFormat::k_16_16_16_16_FLOAT: case VertexFormat::k_32: case VertexFormat::k_32_32: case VertexFormat::k_32_32_32_32: case VertexFormat::k_32_FLOAT: case VertexFormat::k_32_32_FLOAT: case VertexFormat::k_32_32_32_FLOAT: case VertexFormat::k_32_32_32_32_FLOAT: { // These are handled, for now. } break; case VertexFormat::k_10_11_11: { // This needs to be converted. bool is_signed = instr.attributes.is_signed; bool is_integer = instr.attributes.is_integer; auto op = is_signed ? spv::Op::OpBitFieldSExtract : spv::Op::OpBitFieldUExtract; auto comp_type = is_signed ? int_type_ : uint_type_; assert_true(comp_type == b.getTypeId(vertex)); spv::Id components[3] = {0}; /* components[2] = b.createTriOp( op, comp_type, vertex, b.makeUintConstant(0), b.makeUintConstant(10)); components[1] = b.createTriOp(op, comp_type, vertex, b.makeUintConstant(10), b.makeUintConstant(11)); components[0] = b.createTriOp(op, comp_type, vertex, b.makeUintConstant(21), b.makeUintConstant(11)); */ // Workaround until NVIDIA fixes their compiler :| components[0] = BitfieldExtract(comp_type, vertex, is_signed, 00, 11); components[1] = BitfieldExtract(comp_type, vertex, is_signed, 11, 11); components[2] = BitfieldExtract(comp_type, vertex, is_signed, 22, 10); op = is_signed ? spv::Op::OpConvertSToF : spv::Op::OpConvertUToF; for (int i = 0; i < 3; i++) { components[i] = b.createUnaryOp(op, float_type_, components[i]); } if (!is_integer) { components[0] = ConvertNormVar(components[0], float_type_, 11, is_signed); components[1] = ConvertNormVar(components[1], float_type_, 11, is_signed); components[2] = ConvertNormVar(components[2], float_type_, 10, is_signed); } vertex = b.createCompositeConstruct( vec3_float_type_, std::vector({components[0], components[1], components[2]})); } break; case VertexFormat::k_11_11_10: { // This needs to be converted. bool is_signed = instr.attributes.is_signed; bool is_integer = instr.attributes.is_integer; auto op = is_signed ? spv::Op::OpBitFieldSExtract : spv::Op::OpBitFieldUExtract; auto comp_type = is_signed ? int_type_ : uint_type_; spv::Id components[3] = {0}; /* components[2] = b.createTriOp( op, comp_type, vertex, b.makeUintConstant(0), b.makeUintConstant(11)); components[1] = b.createTriOp(op, comp_type, vertex, b.makeUintConstant(11), b.makeUintConstant(11)); components[0] = b.createTriOp(op, comp_type, vertex, b.makeUintConstant(22), b.makeUintConstant(10)); */ // Workaround until NVIDIA fixes their compiler :| components[0] = BitfieldExtract(comp_type, vertex, is_signed, 00, 10); components[1] = BitfieldExtract(comp_type, vertex, is_signed, 10, 11); components[2] = BitfieldExtract(comp_type, vertex, is_signed, 21, 11); op = is_signed ? spv::Op::OpConvertSToF : spv::Op::OpConvertUToF; for (int i = 0; i < 3; i++) { components[i] = b.createUnaryOp(op, float_type_, components[i]); } if (!is_integer) { components[0] = ConvertNormVar(components[0], float_type_, 11, is_signed); components[1] = ConvertNormVar(components[1], float_type_, 11, is_signed); components[2] = ConvertNormVar(components[2], float_type_, 10, is_signed); } vertex = b.createCompositeConstruct( vec3_float_type_, std::vector({components[0], components[1], components[2]})); } break; case VertexFormat::kUndefined: break; } // Convert any integers to floats. auto scalar_type = b.getScalarTypeId(b.getTypeId(vertex)); if (scalar_type == int_type_ || scalar_type == uint_type_) { auto op = scalar_type == int_type_ ? spv::Op::OpConvertSToF : spv::Op::OpConvertUToF; spv::Id vtx_type; switch (vertex_components) { case 1: vtx_type = float_type_; break; case 2: vtx_type = vec2_float_type_; break; case 3: vtx_type = vec3_float_type_; break; case 4: vtx_type = vec4_float_type_; break; } vertex = b.createUnaryOp(op, vtx_type, vertex); } vertex = b.createTriOp(spv::Op::OpSelect, b.getTypeId(vertex), cond, vertex, alt_vertex); StoreToResult(vertex, instr.result); } void SpirvShaderTranslator::ProcessTextureFetchInstruction( const ParsedTextureFetchInstruction& instr) { auto& b = *builder_; // Close the open predicated block if this instr isn't predicated or the // conditions do not match. if (open_predicated_block_ && (!instr.is_predicated || instr.predicate_condition != predicated_block_cond_)) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } if (!open_predicated_block_ && instr.is_predicated) { Id pred_cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.predicate_condition)); auto block = &b.makeNewBlock(); open_predicated_block_ = true; predicated_block_cond_ = instr.predicate_condition; predicated_block_end_ = &b.makeNewBlock(); b.createSelectionMerge(predicated_block_end_, spv::SelectionControlMaskNone); b.createConditionalBranch(pred_cond, block, predicated_block_end_); b.setBuildPoint(block); } // Operand 0 is the offset // Operand 1 is the sampler index Id dest = vec4_float_zero_; Id src = LoadFromOperand(instr.operands[0]); assert_not_zero(src); uint32_t dim_idx = 0; switch (instr.dimension) { case TextureDimension::k1D: case TextureDimension::k2D: { dim_idx = 0; } break; case TextureDimension::k3D: { dim_idx = 1; } break; case TextureDimension::kCube: { dim_idx = 2; } break; default: assert_unhandled_case(instr.dimension); } switch (instr.opcode) { case FetchOpcode::kTextureFetch: { auto texture_index = b.makeUintConstant(instr.operands[1].storage_index); auto texture_ptr = b.createAccessChain(spv::StorageClass::StorageClassUniformConstant, tex_[dim_idx], std::vector({texture_index})); auto texture = b.createLoad(texture_ptr); spv::Id size = 0; if (instr.attributes.offset_x || instr.attributes.offset_y) { auto image = b.createUnaryOp(spv::OpImage, b.getImageType(texture), texture); spv::Builder::TextureParameters params; std::memset(¶ms, 0, sizeof(params)); params.sampler = image; params.lod = b.makeIntConstant(0); size = b.createTextureQueryCall(spv::Op::OpImageQuerySizeLod, params, false); if (instr.dimension == TextureDimension::k1D) { size = b.createUnaryOp(spv::Op::OpConvertSToF, float_type_, size); } else if (instr.dimension == TextureDimension::k2D) { size = b.createUnaryOp(spv::Op::OpConvertSToF, vec2_float_type_, size); } else if (instr.dimension == TextureDimension::k3D) { size = b.createUnaryOp(spv::Op::OpConvertSToF, vec3_float_type_, size); } else if (instr.dimension == TextureDimension::kCube) { size = b.createUnaryOp(spv::Op::OpConvertSToF, vec4_float_type_, size); } } if (instr.dimension == TextureDimension::k1D) { src = b.createCompositeExtract(src, float_type_, 0); if (instr.attributes.offset_x) { auto offset = b.makeFloatConstant(instr.attributes.offset_x + 0.5f); offset = b.createBinOp(spv::Op::OpFDiv, float_type_, offset, size); src = b.createBinOp(spv::Op::OpFAdd, float_type_, src, offset); } // https://msdn.microsoft.com/en-us/library/windows/desktop/bb944006.aspx // "Because the runtime does not support 1D textures, the compiler will // use a 2D texture with the knowledge that the y-coordinate is // unimportant." src = b.createCompositeConstruct( vec2_float_type_, std::vector({src, b.makeFloatConstant(0.0f)})); } else if (instr.dimension == TextureDimension::k2D) { src = b.createRvalueSwizzle(spv::NoPrecision, vec2_float_type_, src, std::vector({0, 1})); if (instr.attributes.offset_x || instr.attributes.offset_y) { auto offset = b.makeCompositeConstant( vec2_float_type_, std::vector( {b.makeFloatConstant(instr.attributes.offset_x + 0.5f), b.makeFloatConstant(instr.attributes.offset_y + 0.5f)})); offset = b.createBinOp(spv::Op::OpFDiv, vec2_float_type_, offset, size); src = b.createBinOp(spv::Op::OpFAdd, vec2_float_type_, src, offset); } } spv::Builder::TextureParameters params = {0}; params.coords = src; params.sampler = texture; dest = b.createTextureCall(spv::NoPrecision, vec4_float_type_, false, false, false, false, false, params); } break; case FetchOpcode::kGetTextureGradients: { auto texture_index = b.makeUintConstant(instr.operands[2].storage_index); auto texture_ptr = b.createAccessChain(spv::StorageClass::StorageClassUniformConstant, tex_[dim_idx], std::vector({texture_index})); auto texture = b.createLoad(texture_ptr); Id grad = LoadFromOperand(instr.operands[1]); Id gradX = b.createCompositeExtract(grad, float_type_, 0); Id gradY = b.createCompositeExtract(grad, float_type_, 1); spv::Builder::TextureParameters params = {0}; params.coords = src; params.sampler = texture; params.gradX = gradX; params.gradY = gradY; dest = b.createTextureCall(spv::NoPrecision, vec4_float_type_, false, false, false, false, false, params); } break; case FetchOpcode::kGetTextureWeights: { // fract(src0 * textureSize); auto texture_index = b.makeUintConstant(instr.operands[1].storage_index); auto texture_ptr = b.createAccessChain(spv::StorageClass::StorageClassUniformConstant, tex_[dim_idx], std::vector({texture_index})); auto texture = b.createLoad(texture_ptr); auto image = b.createUnaryOp(spv::OpImage, b.getImageType(texture), texture); switch (instr.dimension) { case TextureDimension::k1D: case TextureDimension::k2D: { spv::Builder::TextureParameters params; std::memset(¶ms, 0, sizeof(params)); params.sampler = image; params.lod = b.makeIntConstant(0); auto size = b.createTextureQueryCall(spv::Op::OpImageQuerySizeLod, params, true); size = b.createUnaryOp(spv::Op::OpConvertUToF, vec2_float_type_, size); auto weight = b.createBinOp(spv::Op::OpFMul, vec2_float_type_, size, src); weight = CreateGlslStd450InstructionCall( spv::NoPrecision, vec2_float_type_, spv::GLSLstd450::kFract, {weight}); dest = b.createOp(spv::Op::OpVectorShuffle, vec4_float_type_, {weight, vec4_float_zero_, 0, 1, 2, 2}); } break; default: // TODO(DrChat): The rest of these. assert_unhandled_case(instr.dimension); break; } } break; case FetchOpcode::kSetTextureLod: { // = src1.x (MIP level) // ... immediately after // tfetch UseRegisterLOD=true } break; default: // TODO: the rest of these assert_unhandled_case(instr.opcode); break; } if (dest) { b.createStore(dest, pv_); StoreToResult(dest, instr.result); } } void SpirvShaderTranslator::ProcessAluInstruction( const ParsedAluInstruction& instr) { auto& b = *builder_; switch (instr.type) { case ParsedAluInstruction::Type::kNop: b.createNoResultOp(spv::Op::OpNop); break; case ParsedAluInstruction::Type::kVector: ProcessVectorAluInstruction(instr); break; case ParsedAluInstruction::Type::kScalar: ProcessScalarAluInstruction(instr); break; } } spv::Function* SpirvShaderTranslator::CreateCubeFunction() { auto& b = *builder_; spv::Block* function_block = nullptr; auto function = b.makeFunctionEntry(spv::NoPrecision, vec4_float_type_, "cube", {vec4_float_type_}, {{spv::NoPrecision}}, &function_block); auto src = function->getParamId(0); auto face_id = b.createVariable(spv::StorageClass::StorageClassFunction, float_type_, "face_id"); auto sc = b.createVariable(spv::StorageClass::StorageClassFunction, float_type_, "sc"); auto tc = b.createVariable(spv::StorageClass::StorageClassFunction, float_type_, "tc"); auto ma = b.createVariable(spv::StorageClass::StorageClassFunction, float_type_, "ma"); // Pseudocode: /* vec4 cube(vec4 src1) { vec3 src = vec3(src1.y, src1.x, src1.z); vec3 abs_src = abs(src); int face_id; float sc; float tc; float ma; if (abs_src.x > abs_src.y && abs_src.x > abs_src.z) { if (src.x > 0.0) { face_id = 0; sc = -abs_src.z; tc = -abs_src.y; ma = abs_src.x; } else { face_id = 1; sc = abs_src.z; tc = -abs_src.y; ma = abs_src.x; } } else if (abs_src.y > abs_src.x && abs_src.y > abs_src.z) { if (src.y > 0.0) { face_id = 2; sc = abs_src.x; tc = abs_src.z; ma = abs_src.y; } else { face_id = 3; sc = abs_src.x; tc = -abs_src.z; ma = abs_src.y; } } else { if (src.z > 0.0) { face_id = 4; sc = abs_src.x; tc = -abs_src.y; ma = abs_src.z; } else { face_id = 5; sc = -abs_src.x; tc = -abs_src.y; ma = abs_src.z; } } float s = (sc / ma + 1.0) / 2.0; float t = (tc / ma + 1.0) / 2.0; return vec4(t, s, 2.0 * ma, float(face_id)); } */ auto abs_src = CreateGlslStd450InstructionCall( spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFAbs, {src}); auto abs_src_x = b.createCompositeExtract(abs_src, float_type_, 0); auto abs_src_y = b.createCompositeExtract(abs_src, float_type_, 1); auto abs_src_z = b.createCompositeExtract(abs_src, float_type_, 2); auto neg_src_x = b.createUnaryOp(spv::Op::OpFNegate, float_type_, abs_src_x); auto neg_src_y = b.createUnaryOp(spv::Op::OpFNegate, float_type_, abs_src_y); auto neg_src_z = b.createUnaryOp(spv::Op::OpFNegate, float_type_, abs_src_z); // Case 1: abs(src).x > abs(src).yz { auto x_gt_y = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_x, abs_src_y); auto x_gt_z = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_x, abs_src_z); auto c1 = b.createBinOp(spv::Op::OpLogicalAnd, bool_type_, x_gt_y, x_gt_z); spv::Builder::If if1(c1, 0, b); // sc = abs(src).y b.createStore(abs_src_y, sc); // ma = abs(src).x b.createStore(abs_src_x, ma); auto src_x = b.createCompositeExtract(src, float_type_, 0); auto c2 = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, src_x, b.makeFloatConstant(0)); // src.x > 0: // face_id = 2 // tc = -abs(src).z // src.x <= 0: // face_id = 3 // tc = abs(src).z auto tmp_face_id = b.createTriOp(spv::Op::OpSelect, float_type_, c2, b.makeFloatConstant(2), b.makeFloatConstant(3)); auto tmp_tc = b.createTriOp(spv::Op::OpSelect, float_type_, c2, neg_src_z, abs_src_z); b.createStore(tmp_face_id, face_id); b.createStore(tmp_tc, tc); if1.makeEndIf(); } // Case 2: abs(src).y > abs(src).xz { auto y_gt_x = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_y, abs_src_x); auto y_gt_z = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_y, abs_src_z); auto c1 = b.createBinOp(spv::Op::OpLogicalAnd, bool_type_, y_gt_x, y_gt_z); spv::Builder::If if1(c1, 0, b); // tc = -abs(src).x b.createStore(neg_src_x, tc); // ma = abs(src).y b.createStore(abs_src_y, ma); auto src_y = b.createCompositeExtract(src, float_type_, 1); auto c2 = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, src_y, b.makeFloatConstant(0)); // src.y > 0: // face_id = 0 // sc = -abs(src).z // src.y <= 0: // face_id = 1 // sc = abs(src).z auto tmp_face_id = b.createTriOp(spv::Op::OpSelect, float_type_, c2, b.makeFloatConstant(0), b.makeFloatConstant(1)); auto tmp_sc = b.createTriOp(spv::Op::OpSelect, float_type_, c2, neg_src_z, abs_src_z); b.createStore(tmp_face_id, face_id); b.createStore(tmp_sc, sc); if1.makeEndIf(); } // Case 3: abs(src).z > abs(src).yx { auto z_gt_x = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_z, abs_src_x); auto z_gt_y = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, abs_src_z, abs_src_y); auto c1 = b.createBinOp(spv::Op::OpLogicalAnd, bool_type_, z_gt_x, z_gt_y); spv::Builder::If if1(c1, 0, b); // tc = -abs(src).x b.createStore(neg_src_x, tc); // ma = abs(src).z b.createStore(abs_src_z, ma); auto src_z = b.createCompositeExtract(src, float_type_, 2); auto c2 = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, src_z, b.makeFloatConstant(0)); // src.z > 0: // face_id = 4 // sc = -abs(src).y // src.z <= 0: // face_id = 5 // sc = abs(src).y auto tmp_face_id = b.createTriOp(spv::Op::OpSelect, float_type_, c2, b.makeFloatConstant(4), b.makeFloatConstant(5)); auto tmp_sc = b.createTriOp(spv::Op::OpSelect, float_type_, c2, neg_src_y, abs_src_y); b.createStore(tmp_face_id, face_id); b.createStore(tmp_sc, sc); if1.makeEndIf(); } // s = (sc / ma + 1.0) / 2.0 auto s = b.createBinOp(spv::Op::OpFDiv, float_type_, b.createLoad(sc), b.createLoad(ma)); s = b.createBinOp(spv::Op::OpFAdd, float_type_, s, b.makeFloatConstant(1.0)); s = b.createBinOp(spv::Op::OpFDiv, float_type_, s, b.makeFloatConstant(2.0)); // t = (tc / ma + 1.0) / 2.0 auto t = b.createBinOp(spv::Op::OpFDiv, float_type_, b.createLoad(tc), b.createLoad(ma)); t = b.createBinOp(spv::Op::OpFAdd, float_type_, t, b.makeFloatConstant(1.0)); t = b.createBinOp(spv::Op::OpFDiv, float_type_, t, b.makeFloatConstant(2.0)); auto ma_times_two = b.createBinOp(spv::Op::OpFMul, float_type_, b.createLoad(ma), b.makeFloatConstant(2.0)); // dest = vec4(t, s, 2.0 * ma, face_id) auto ret = b.createCompositeConstruct( vec4_float_type_, std::vector({t, s, ma_times_two, b.createLoad(face_id)})); b.makeReturn(false, ret); return function; } void SpirvShaderTranslator::ProcessVectorAluInstruction( const ParsedAluInstruction& instr) { auto& b = *builder_; // Close the open predicated block if this instr isn't predicated or the // conditions do not match. if (open_predicated_block_ && (!instr.is_predicated || instr.predicate_condition != predicated_block_cond_)) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } if (!open_predicated_block_ && instr.is_predicated) { Id pred_cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.predicate_condition)); auto block = &b.makeNewBlock(); open_predicated_block_ = true; predicated_block_cond_ = instr.predicate_condition; predicated_block_end_ = &b.makeNewBlock(); b.createSelectionMerge(predicated_block_end_, spv::SelectionControlMaskNone); b.createConditionalBranch(pred_cond, block, predicated_block_end_); b.setBuildPoint(block); } // TODO: If we have identical operands, reuse previous one. Id sources[3] = {0}; Id dest = vec4_float_zero_; for (size_t i = 0; i < instr.operand_count; i++) { sources[i] = LoadFromOperand(instr.operands[i]); } bool close_predicated_block = false; switch (instr.vector_opcode) { case AluVectorOpcode::kAdd: { dest = b.createBinOp(spv::Op::OpFAdd, vec4_float_type_, sources[0], sources[1]); } break; case AluVectorOpcode::kCndEq: { // dest = src0 == 0.0 ? src1 : src2; auto c = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], vec4_float_zero_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, sources[1], sources[2]); } break; case AluVectorOpcode::kCndGe: { // dest = src0 >= 0.0 ? src1 : src2; auto c = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, vec4_bool_type_, sources[0], vec4_float_zero_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, sources[1], sources[2]); } break; case AluVectorOpcode::kCndGt: { // dest = src0 > 0.0 ? src1 : src2; auto c = b.createBinOp(spv::Op::OpFOrdGreaterThan, vec4_bool_type_, sources[0], vec4_float_zero_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, sources[1], sources[2]); } break; case AluVectorOpcode::kCube: { dest = b.createFunctionCall(cube_function_, std::vector({sources[1]})); } break; case AluVectorOpcode::kDst: { auto src0_y = b.createCompositeExtract(sources[0], float_type_, 1); auto src1_y = b.createCompositeExtract(sources[1], float_type_, 1); auto dst_y = b.createBinOp(spv::Op::OpFMul, float_type_, src0_y, src1_y); auto src0_z = b.createCompositeExtract(sources[0], float_type_, 3); auto src1_w = b.createCompositeExtract(sources[1], float_type_, 4); dest = b.createCompositeConstruct( vec4_float_type_, std::vector({b.makeFloatConstant(1.f), dst_y, src0_z, src1_w})); } break; case AluVectorOpcode::kDp2Add: { auto src0_xy = b.createOp(spv::Op::OpVectorShuffle, vec2_float_type_, {sources[0], sources[0], 0, 1}); auto src1_xy = b.createOp(spv::Op::OpVectorShuffle, vec2_float_type_, {sources[1], sources[1], 0, 1}); auto src2_x = b.createCompositeExtract(sources[2], float_type_, 0); dest = b.createBinOp(spv::Op::OpDot, float_type_, src0_xy, src1_xy); dest = b.createBinOp(spv::Op::OpFAdd, float_type_, dest, src2_x); dest = b.smearScalar(spv::NoPrecision, dest, vec4_float_type_); } break; case AluVectorOpcode::kDp3: { auto src0_xyz = b.createOp(spv::Op::OpVectorShuffle, vec3_float_type_, {sources[0], sources[0], 0, 1, 2}); auto src1_xyz = b.createOp(spv::Op::OpVectorShuffle, vec3_float_type_, {sources[1], sources[1], 0, 1, 2}); dest = b.createBinOp(spv::Op::OpDot, float_type_, src0_xyz, src1_xyz); dest = b.smearScalar(spv::NoPrecision, dest, vec4_float_type_); } break; case AluVectorOpcode::kDp4: { dest = b.createBinOp(spv::Op::OpDot, float_type_, sources[0], sources[1]); dest = b.smearScalar(spv::NoPrecision, dest, vec4_float_type_); } break; case AluVectorOpcode::kFloor: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFloor, {sources[0]}); } break; case AluVectorOpcode::kFrc: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFract, {sources[0]}); } break; case AluVectorOpcode::kKillEq: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], sources[1]); cond = b.createUnaryOp(spv::Op::OpAny, bool_type_, cond); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = vec4_float_zero_; } break; case AluVectorOpcode::kKillGe: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, vec4_bool_type_, sources[0], sources[1]); cond = b.createUnaryOp(spv::Op::OpAny, bool_type_, cond); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = vec4_float_zero_; } break; case AluVectorOpcode::kKillGt: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThan, vec4_bool_type_, sources[0], sources[1]); cond = b.createUnaryOp(spv::Op::OpAny, bool_type_, cond); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = vec4_float_zero_; } break; case AluVectorOpcode::kKillNe: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdNotEqual, vec4_bool_type_, sources[0], sources[1]); cond = b.createUnaryOp(spv::Op::OpAny, bool_type_, cond); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = vec4_float_zero_; } break; case AluVectorOpcode::kMad: { dest = b.createBinOp(spv::Op::OpFMul, vec4_float_type_, sources[0], sources[1]); dest = b.createBinOp(spv::Op::OpFAdd, vec4_float_type_, dest, sources[2]); } break; case AluVectorOpcode::kMax4: { auto src0_x = b.createCompositeExtract(sources[0], float_type_, 0); auto src0_y = b.createCompositeExtract(sources[0], float_type_, 1); auto src0_z = b.createCompositeExtract(sources[0], float_type_, 2); auto src0_w = b.createCompositeExtract(sources[0], float_type_, 3); auto max_xy = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kFMax, {src0_x, src0_y}); auto max_zw = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kFMax, {src0_z, src0_w}); auto max_xyzw = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kFMax, {max_xy, max_zw}); // FIXME: Docs say this only updates pv.x? dest = b.smearScalar(spv::NoPrecision, max_xyzw, vec4_float_type_); } break; case AluVectorOpcode::kMaxA: { // a0 = clamp(floor(src0.w + 0.5), -256, 255) auto addr = b.createCompositeExtract(sources[0], float_type_, 3); addr = b.createBinOp(spv::Op::OpFAdd, float_type_, addr, b.makeFloatConstant(0.5f)); addr = b.createUnaryOp(spv::Op::OpConvertFToS, int_type_, addr); addr = CreateGlslStd450InstructionCall( spv::NoPrecision, int_type_, spv::GLSLstd450::kSClamp, {addr, b.makeIntConstant(-256), b.makeIntConstant(255)}); b.createStore(addr, a0_); // dest = src0 >= src1 ? src0 : src1 dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFMax, {sources[0], sources[1]}); } break; case AluVectorOpcode::kMax: { if (sources[0] == sources[1]) { // mov dst, src dest = sources[0]; break; } dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFMax, {sources[0], sources[1]}); } break; case AluVectorOpcode::kMin: { if (sources[0] == sources[1]) { // mov dst, src dest = sources[0]; break; } dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, spv::GLSLstd450::kFMin, {sources[0], sources[1]}); } break; case AluVectorOpcode::kMul: { dest = b.createBinOp(spv::Op::OpFMul, vec4_float_type_, sources[0], sources[1]); } break; case AluVectorOpcode::kSetpEqPush: { auto c0 = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], vec4_float_zero_); auto c1 = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[1], vec4_float_zero_); auto c_and = b.createBinOp(spv::Op::OpLogicalAnd, vec4_bool_type_, c0, c1); auto c_and_x = b.createCompositeExtract(c_and, bool_type_, 0); c_and_x = b.smearScalar(spv::NoPrecision, c_and_x, vec4_bool_type_); auto c_and_w = b.createCompositeExtract(c_and, bool_type_, 3); // p0 b.createStore(c_and_w, p0_); close_predicated_block = true; // dest auto s0_x = b.createCompositeExtract(sources[0], float_type_, 0); s0_x = b.createBinOp(spv::Op::OpFAdd, float_type_, s0_x, b.makeFloatConstant(1.f)); auto s0 = b.smearScalar(spv::NoPrecision, s0_x, vec4_float_type_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c_and_x, vec4_float_zero_, s0); } break; case AluVectorOpcode::kSetpGePush: { auto c0 = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], vec4_float_zero_); auto c1 = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, vec4_bool_type_, sources[1], vec4_float_zero_); auto c_and = b.createBinOp(spv::Op::OpLogicalAnd, vec4_bool_type_, c0, c1); auto c_and_x = b.createCompositeExtract(c_and, bool_type_, 0); c_and_x = b.smearScalar(spv::NoPrecision, c_and_x, vec4_bool_type_); auto c_and_w = b.createCompositeExtract(c_and, bool_type_, 3); // p0 b.createStore(c_and_w, p0_); close_predicated_block = true; // dest auto s0_x = b.createCompositeExtract(sources[0], float_type_, 0); s0_x = b.createBinOp(spv::Op::OpFAdd, float_type_, s0_x, b.makeFloatConstant(1.f)); auto s0 = b.smearScalar(spv::NoPrecision, s0_x, vec4_float_type_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c_and_x, vec4_float_zero_, s0); } break; case AluVectorOpcode::kSetpGtPush: { auto c0 = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], vec4_float_zero_); auto c1 = b.createBinOp(spv::Op::OpFOrdGreaterThan, vec4_bool_type_, sources[1], vec4_float_zero_); auto c_and = b.createBinOp(spv::Op::OpLogicalAnd, vec4_bool_type_, c0, c1); auto c_and_x = b.createCompositeExtract(c_and, bool_type_, 0); c_and_x = b.smearScalar(spv::NoPrecision, c_and_x, vec4_bool_type_); auto c_and_w = b.createCompositeExtract(c_and, bool_type_, 3); // p0 b.createStore(c_and_w, p0_); close_predicated_block = true; // dest auto s0_x = b.createCompositeExtract(sources[0], float_type_, 0); s0_x = b.createBinOp(spv::Op::OpFAdd, float_type_, s0_x, b.makeFloatConstant(1.f)); auto s0 = b.smearScalar(spv::NoPrecision, s0_x, vec4_float_type_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c_and_x, vec4_float_zero_, s0); } break; case AluVectorOpcode::kSetpNePush: { auto c0 = b.createBinOp(spv::Op::OpFOrdNotEqual, vec4_bool_type_, sources[0], vec4_float_zero_); auto c1 = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[1], vec4_float_zero_); auto c_and = b.createBinOp(spv::Op::OpLogicalAnd, vec4_bool_type_, c0, c1); auto c_and_x = b.createCompositeExtract(c_and, bool_type_, 0); c_and_x = b.smearScalar(spv::NoPrecision, c_and_x, vec4_bool_type_); auto c_and_w = b.createCompositeExtract(c_and, bool_type_, 3); // p0 b.createStore(c_and_w, p0_); close_predicated_block = true; // dest auto s0_x = b.createCompositeExtract(sources[0], float_type_, 0); s0_x = b.createBinOp(spv::Op::OpFAdd, float_type_, s0_x, b.makeFloatConstant(1.f)); auto s0 = b.smearScalar(spv::NoPrecision, s0_x, vec4_float_type_); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c_and_x, vec4_float_zero_, s0); } break; case AluVectorOpcode::kSeq: { // foreach(el) src0 == src1 ? 1.0 : 0.0 auto c = b.createBinOp(spv::Op::OpFOrdEqual, vec4_bool_type_, sources[0], sources[1]); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, vec4_float_one_, vec4_float_zero_); } break; case AluVectorOpcode::kSge: { // foreach(el) src0 >= src1 ? 1.0 : 0.0 auto c = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, vec4_bool_type_, sources[0], sources[1]); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, vec4_float_one_, vec4_float_zero_); } break; case AluVectorOpcode::kSgt: { // foreach(el) src0 > src1 ? 1.0 : 0.0 auto c = b.createBinOp(spv::Op::OpFOrdGreaterThan, vec4_bool_type_, sources[0], sources[1]); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, vec4_float_one_, vec4_float_zero_); } break; case AluVectorOpcode::kSne: { // foreach(el) src0 != src1 ? 1.0 : 0.0 auto c = b.createBinOp(spv::Op::OpFOrdNotEqual, vec4_bool_type_, sources[0], sources[1]); dest = b.createTriOp(spv::Op::OpSelect, vec4_float_type_, c, vec4_float_one_, vec4_float_zero_); } break; case AluVectorOpcode::kTrunc: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, vec4_float_type_, GLSLstd450::kTrunc, {sources[0]}); } break; default: assert_unhandled_case(instr.vector_opcode); break; } assert_not_zero(dest); assert_true(b.getTypeId(dest) == vec4_float_type_); if (dest) { b.createStore(dest, pv_); StoreToResult(dest, instr.result); } if (close_predicated_block && open_predicated_block_) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } } void SpirvShaderTranslator::ProcessScalarAluInstruction( const ParsedAluInstruction& instr) { auto& b = *builder_; // Close the open predicated block if this instr isn't predicated or the // conditions do not match. if (open_predicated_block_ && (!instr.is_predicated || instr.predicate_condition != predicated_block_cond_)) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } if (!open_predicated_block_ && instr.is_predicated) { Id pred_cond = b.createBinOp(spv::Op::OpLogicalEqual, bool_type_, b.createLoad(p0_), b.makeBoolConstant(instr.predicate_condition)); auto block = &b.makeNewBlock(); open_predicated_block_ = true; predicated_block_cond_ = instr.predicate_condition; predicated_block_end_ = &b.makeNewBlock(); b.createSelectionMerge(predicated_block_end_, spv::SelectionControlMaskNone); b.createConditionalBranch(pred_cond, block, predicated_block_end_); b.setBuildPoint(block); } // TODO: If we have identical operands, reuse previous one. Id sources[3] = {0}; Id dest = b.makeFloatConstant(0); for (size_t i = 0, x = 0; i < instr.operand_count; i++) { auto src = LoadFromOperand(instr.operands[i]); // Pull components out of the vector operands and use them as sources. if (instr.operands[i].component_count > 1) { for (int j = 0; j < instr.operands[i].component_count; j++) { sources[x++] = b.createCompositeExtract(src, float_type_, j); } } else { sources[x++] = src; } } bool close_predicated_block = false; switch (instr.scalar_opcode) { case AluScalarOpcode::kAdds: case AluScalarOpcode::kAddsc0: case AluScalarOpcode::kAddsc1: { // dest = src0 + src1 dest = b.createBinOp(spv::Op::OpFAdd, float_type_, sources[0], sources[1]); } break; case AluScalarOpcode::kAddsPrev: { // dest = src0 + ps dest = b.createBinOp(spv::Op::OpFAdd, float_type_, sources[0], b.createLoad(ps_)); } break; case AluScalarOpcode::kCos: { // dest = cos(src0) dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kCos, {sources[0]}); } break; case AluScalarOpcode::kExp: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kExp2, {sources[0]}); } break; case AluScalarOpcode::kFloors: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kFloor, {sources[0]}); } break; case AluScalarOpcode::kFrcs: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kFract, {sources[0]}); } break; case AluScalarOpcode::kKillsEq: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = b.makeFloatConstant(0.f); } break; case AluScalarOpcode::kKillsGe: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = b.makeFloatConstant(0.f); } break; case AluScalarOpcode::kKillsGt: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, sources[0], b.makeFloatConstant(0.f)); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = b.makeFloatConstant(0.f); } break; case AluScalarOpcode::kKillsNe: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdNotEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = b.makeFloatConstant(0.f); } break; case AluScalarOpcode::kKillsOne: { auto continue_block = &b.makeNewBlock(); auto kill_block = &b.makeNewBlock(); auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(1.f)); b.createConditionalBranch(cond, kill_block, continue_block); b.setBuildPoint(kill_block); b.createNoResultOp(spv::Op::OpKill); b.setBuildPoint(continue_block); dest = b.makeFloatConstant(0.f); } break; case AluScalarOpcode::kLogc: { auto t = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kLog2, {sources[0]}); // FIXME: We don't check to see if t == -INF, we just check for INF auto c = b.createUnaryOp(spv::Op::OpIsInf, bool_type_, t); dest = b.createTriOp(spv::Op::OpSelect, float_type_, c, b.makeFloatConstant(-FLT_MAX), t); } break; case AluScalarOpcode::kLog: { dest = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kLog2, {sources[0]}); } break; case AluScalarOpcode::kMaxAsf: { auto addr = b.createUnaryOp(spv::Op::OpConvertFToS, int_type_, sources[0]); addr = CreateGlslStd450InstructionCall( spv::NoPrecision, int_type_, spv::GLSLstd450::kSClamp, {addr, b.makeIntConstant(-256), b.makeIntConstant(255)}); b.createStore(addr, a0_); // dest = src0 >= src1 ? src0 : src1 dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, spv::GLSLstd450::kFMax, {sources[0], sources[1]}); } break; case AluScalarOpcode::kMaxAs: { // a0 = clamp(floor(src0 + 0.5), -256, 255) auto addr = b.createBinOp(spv::Op::OpFAdd, float_type_, sources[0], b.makeFloatConstant(0.5f)); addr = b.createUnaryOp(spv::Op::OpConvertFToS, int_type_, addr); addr = CreateGlslStd450InstructionCall( spv::NoPrecision, int_type_, spv::GLSLstd450::kSClamp, {addr, b.makeIntConstant(-256), b.makeIntConstant(255)}); b.createStore(addr, a0_); // dest = src0 >= src1 ? src0 : src1 dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, spv::GLSLstd450::kFMax, {sources[0], sources[1]}); } break; case AluScalarOpcode::kMaxs: { if (sources[0] == sources[1]) { // mov dst, src dest = sources[0]; } // dest = max(src0, src1) dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kFMax, {sources[0], sources[1]}); } break; case AluScalarOpcode::kMins: { if (sources[0] == sources[1]) { // mov dst, src dest = sources[0]; } // dest = min(src0, src1) dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kFMin, {sources[0], sources[1]}); } break; case AluScalarOpcode::kMuls: case AluScalarOpcode::kMulsc0: case AluScalarOpcode::kMulsc1: { // dest = src0 * src1 dest = b.createBinOp(spv::Op::OpFMul, float_type_, sources[0], sources[1]); } break; case AluScalarOpcode::kMulsPrev: { // dest = src0 * ps dest = b.createBinOp(spv::Op::OpFMul, float_type_, sources[0], b.createLoad(ps_)); } break; case AluScalarOpcode::kMulsPrev2: { // TODO: Uh... see GLSL translator for impl. } break; case AluScalarOpcode::kRcpc: { dest = b.createBinOp(spv::Op::OpFDiv, float_type_, b.makeFloatConstant(1.f), sources[0]); dest = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kFClamp, {dest, b.makeFloatConstant(-FLT_MAX), b.makeFloatConstant(FLT_MAX)}); } break; case AluScalarOpcode::kRcpf: { dest = b.createBinOp(spv::Op::OpFDiv, float_type_, b.makeFloatConstant(1.f), sources[0]); auto c = b.createUnaryOp(spv::Op::OpIsInf, bool_type_, dest); dest = b.createTriOp(spv::Op::OpSelect, float_type_, c, b.makeFloatConstant(0.f), dest); } break; case AluScalarOpcode::kRcp: { // dest = src0 != 0.0 ? 1.0 / src0 : 0.0; auto c = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); auto d = b.createBinOp(spv::Op::OpFDiv, float_type_, b.makeFloatConstant(1.f), sources[0]); dest = b.createTriOp(spv::Op::OpSelect, float_type_, c, b.makeFloatConstant(0.f), d); } break; case AluScalarOpcode::kRsqc: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, spv::GLSLstd450::kInverseSqrt, {sources[0]}); dest = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, spv::GLSLstd450::kFClamp, {dest, b.makeFloatConstant(-FLT_MAX), b.makeFloatConstant(FLT_MAX)}); } break; case AluScalarOpcode::kRsqf: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, spv::GLSLstd450::kInverseSqrt, {sources[0]}); auto c1 = b.createUnaryOp(spv::Op::OpIsInf, bool_type_, dest); auto c2 = b.createUnaryOp(spv::Op::OpIsNan, bool_type_, dest); auto c = b.createBinOp(spv::Op::OpLogicalOr, bool_type_, c1, c2); dest = b.createTriOp(spv::Op::OpSelect, float_type_, c, b.makeFloatConstant(0.f), dest); } break; case AluScalarOpcode::kRsq: { // dest = src0 > 0.0 ? inversesqrt(src0) : 0.0; auto c = b.createBinOp(spv::Op::OpFOrdLessThanEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); auto d = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, spv::GLSLstd450::kInverseSqrt, {sources[0]}); dest = b.createTriOp(spv::Op::OpSelect, float_type_, c, b.makeFloatConstant(0.f), d); } break; case AluScalarOpcode::kSeqs: { // dest = src0 == 0.0 ? 1.0 : 0.0; auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(1.f), b.makeFloatConstant(0.f)); } break; case AluScalarOpcode::kSges: { // dest = src0 >= 0.0 ? 1.0 : 0.0; auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(1.f), b.makeFloatConstant(0.f)); } break; case AluScalarOpcode::kSgts: { // dest = src0 > 0.0 ? 1.0 : 0.0; auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, sources[0], b.makeFloatConstant(0.f)); dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(1.f), b.makeFloatConstant(0.f)); } break; case AluScalarOpcode::kSnes: { // dest = src0 != 0.0 ? 1.0 : 0.0; auto cond = b.createBinOp(spv::Op::OpFOrdNotEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(1.f), b.makeFloatConstant(0.f)); } break; case AluScalarOpcode::kSetpClr: { b.createStore(b.makeBoolConstant(false), p0_); close_predicated_block = true; dest = b.makeFloatConstant(FLT_MAX); } break; case AluScalarOpcode::kSetpEq: { auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); // p0 = cond b.createStore(cond, p0_); close_predicated_block = true; // dest = cond ? 0.f : 1.f; dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)); } break; case AluScalarOpcode::kSetpGe: { auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThanEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); // p0 = cond b.createStore(cond, p0_); close_predicated_block = true; // dest = cond ? 0.f : 1.f; dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)); } break; case AluScalarOpcode::kSetpGt: { auto cond = b.createBinOp(spv::Op::OpFOrdGreaterThan, bool_type_, sources[0], b.makeFloatConstant(0.f)); // p0 = cond b.createStore(cond, p0_); close_predicated_block = true; // dest = cond ? 0.f : 1.f; dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)); } break; case AluScalarOpcode::kSetpInv: { // p0 = src0 == 1.0 auto cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(1.f)); b.createStore(cond, p0_); close_predicated_block = true; // if (!cond) dest = src0 == 0.0 ? 1.0 : src0; auto dst_cond = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); auto dst_false = b.createTriOp(spv::Op::OpSelect, float_type_, dst_cond, b.makeFloatConstant(1.f), sources[0]); dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(0.f), dst_false); } break; case AluScalarOpcode::kSetpNe: { auto cond = b.createBinOp(spv::Op::OpFOrdNotEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); // p0 = cond b.createStore(cond, p0_); close_predicated_block = true; // dest = cond ? 0.f : 1.f; dest = b.createTriOp(spv::Op::OpSelect, float_type_, cond, b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)); } break; case AluScalarOpcode::kSetpPop: { auto src = b.createBinOp(spv::Op::OpFSub, float_type_, sources[0], b.makeFloatConstant(1.f)); auto c = b.createBinOp(spv::Op::OpFOrdLessThanEqual, bool_type_, src, b.makeFloatConstant(0.f)); b.createStore(c, p0_); close_predicated_block = true; dest = CreateGlslStd450InstructionCall( spv::NoPrecision, float_type_, GLSLstd450::kFMax, {sources[0], b.makeFloatConstant(0.f)}); } break; case AluScalarOpcode::kSetpRstr: { auto c = b.createBinOp(spv::Op::OpFOrdEqual, bool_type_, sources[0], b.makeFloatConstant(0.f)); b.createStore(c, p0_); close_predicated_block = true; dest = sources[0]; } break; case AluScalarOpcode::kSin: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kSin, {sources[0]}); } break; case AluScalarOpcode::kSqrt: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kSqrt, {sources[0]}); } break; case AluScalarOpcode::kSubs: case AluScalarOpcode::kSubsc0: case AluScalarOpcode::kSubsc1: { dest = b.createBinOp(spv::Op::OpFSub, float_type_, sources[0], sources[1]); } break; case AluScalarOpcode::kSubsPrev: { dest = b.createBinOp(spv::Op::OpFSub, float_type_, sources[0], b.createLoad(ps_)); } break; case AluScalarOpcode::kTruncs: { dest = CreateGlslStd450InstructionCall(spv::NoPrecision, float_type_, GLSLstd450::kTrunc, {sources[0]}); } break; default: assert_unhandled_case(instr.scalar_opcode); break; } assert_not_zero(dest); assert_true(b.getTypeId(dest) == float_type_); if (dest) { b.createStore(dest, ps_); StoreToResult(dest, instr.result); } if (close_predicated_block && open_predicated_block_) { b.createBranch(predicated_block_end_); b.setBuildPoint(predicated_block_end_); open_predicated_block_ = false; predicated_block_cond_ = false; predicated_block_end_ = nullptr; } } Id SpirvShaderTranslator::CreateGlslStd450InstructionCall( spv::Decoration precision, Id result_type, GLSLstd450 instruction_ordinal, std::vector args) { return builder_->createBuiltinCall(result_type, glsl_std_450_instruction_set_, static_cast(instruction_ordinal), args); } Id SpirvShaderTranslator::LoadFromOperand(const InstructionOperand& op) { auto& b = *builder_; Id storage_pointer = 0; Id storage_type = vec4_float_type_; spv::StorageClass storage_class; Id storage_index = 0; // Storage index at lowest level std::vector storage_offsets; // Offsets in nested arrays -> storage // Out of the 512 constant registers pixel shaders get the last 256. uint32_t storage_base = 0; if (op.storage_source == InstructionStorageSource::kConstantFloat) { storage_base = is_pixel_shader() ? 256 : 0; } switch (op.storage_addressing_mode) { case InstructionStorageAddressingMode::kStatic: { storage_index = b.makeUintConstant(storage_base + op.storage_index); } break; case InstructionStorageAddressingMode::kAddressAbsolute: { // storage_index + a0 storage_index = b.createBinOp(spv::Op::OpIAdd, uint_type_, b.createLoad(a0_), b.makeUintConstant(storage_base + op.storage_index)); } break; case InstructionStorageAddressingMode::kAddressRelative: { // storage_index + aL.x auto idx = b.createCompositeExtract(b.createLoad(aL_), uint_type_, 0); storage_index = b.createBinOp(spv::Op::OpIAdd, uint_type_, idx, b.makeUintConstant(storage_base + op.storage_index)); } break; default: assert_always(); break; } switch (op.storage_source) { case InstructionStorageSource::kRegister: storage_pointer = registers_ptr_; storage_class = spv::StorageClass::StorageClassFunction; storage_type = vec4_float_type_; storage_offsets.push_back(storage_index); assert_true(uint32_t(op.storage_index) < register_count()); break; case InstructionStorageSource::kConstantFloat: storage_pointer = consts_; storage_class = spv::StorageClass::StorageClassUniform; storage_type = vec4_float_type_; storage_offsets.push_back(b.makeUintConstant(0)); storage_offsets.push_back(storage_index); break; case InstructionStorageSource::kVertexFetchConstant: case InstructionStorageSource::kTextureFetchConstant: // Should not reach this. assert_always(); break; default: assert_always(); break; } if (!storage_pointer) { return b.createUndefined(vec4_float_type_); } storage_pointer = b.createAccessChain(storage_class, storage_pointer, storage_offsets); auto storage_value = b.createLoad(storage_pointer); assert_true(b.getTypeId(storage_value) == vec4_float_type_); if (op.component_count == 1) { // Don't bother handling constant 0/1 fetches, as they're invalid in scalar // opcodes. uint32_t index = 0; switch (op.components[0]) { case SwizzleSource::kX: index = 0; break; case SwizzleSource::kY: index = 1; break; case SwizzleSource::kZ: index = 2; break; case SwizzleSource::kW: index = 3; break; case SwizzleSource::k0: assert_always(); break; case SwizzleSource::k1: assert_always(); break; } storage_value = b.createCompositeExtract(storage_value, float_type_, index); storage_type = float_type_; } if (op.is_absolute_value) { storage_value = CreateGlslStd450InstructionCall( spv::NoPrecision, storage_type, GLSLstd450::kFAbs, {storage_value}); } if (op.is_negated) { storage_value = b.createUnaryOp(spv::Op::OpFNegate, storage_type, storage_value); } // swizzle if (op.component_count > 1 && !op.is_standard_swizzle()) { std::vector operands; operands.push_back(storage_value); operands.push_back(b.makeCompositeConstant( vec2_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)}))); // Components start from left and are duplicated rightwards // e.g. count = 1, xxxx / count = 2, xyyy ... for (int i = 0; i < 4; i++) { auto swiz = op.components[i]; if (i > op.component_count - 1) { swiz = op.components[op.component_count - 1]; } switch (swiz) { case SwizzleSource::kX: operands.push_back(0); break; case SwizzleSource::kY: operands.push_back(1); break; case SwizzleSource::kZ: operands.push_back(2); break; case SwizzleSource::kW: operands.push_back(3); break; case SwizzleSource::k0: operands.push_back(4); break; case SwizzleSource::k1: operands.push_back(5); break; } } storage_value = b.createOp(spv::Op::OpVectorShuffle, storage_type, operands); } return storage_value; } void SpirvShaderTranslator::StoreToResult(Id source_value_id, const InstructionResult& result) { auto& b = *builder_; if (result.storage_target == InstructionStorageTarget::kNone) { // No-op? return; } if (!result.has_any_writes()) { return; } Id storage_pointer = 0; Id storage_type = vec4_float_type_; spv::StorageClass storage_class; Id storage_index = 0; // Storage index at lowest level std::vector storage_offsets; // Offsets in nested arrays -> storage switch (result.storage_addressing_mode) { case InstructionStorageAddressingMode::kStatic: { storage_index = b.makeUintConstant(result.storage_index); } break; case InstructionStorageAddressingMode::kAddressAbsolute: { // storage_index + a0 storage_index = b.createBinOp(spv::Op::OpIAdd, uint_type_, b.createLoad(a0_), b.makeUintConstant(result.storage_index)); } break; case InstructionStorageAddressingMode::kAddressRelative: { // storage_index + aL.x auto idx = b.createCompositeExtract(b.createLoad(aL_), uint_type_, 0); storage_index = b.createBinOp(spv::Op::OpIAdd, uint_type_, idx, b.makeUintConstant(result.storage_index)); } break; default: assert_always(); return; } bool storage_array; switch (result.storage_target) { case InstructionStorageTarget::kRegister: storage_pointer = registers_ptr_; storage_class = spv::StorageClass::StorageClassFunction; storage_type = vec4_float_type_; storage_offsets.push_back(storage_index); storage_array = true; assert_true(uint32_t(result.storage_index) < register_count()); break; case InstructionStorageTarget::kInterpolant: assert_true(is_vertex_shader()); storage_pointer = interpolators_; storage_class = spv::StorageClass::StorageClassOutput; storage_type = vec4_float_type_; storage_offsets.push_back(storage_index); storage_array = true; break; case InstructionStorageTarget::kPosition: assert_true(is_vertex_shader()); assert_not_zero(pos_); storage_pointer = pos_; storage_class = spv::StorageClass::StorageClassOutput; storage_type = vec4_float_type_; storage_offsets.push_back(0); storage_array = false; break; case InstructionStorageTarget::kPointSize: assert_true(is_vertex_shader()); storage_pointer = point_size_; storage_class = spv::StorageClass::StorageClassOutput; storage_type = float_type_; storage_offsets.push_back(0); storage_array = false; break; case InstructionStorageTarget::kColorTarget: assert_true(is_pixel_shader()); assert_not_zero(frag_outputs_); storage_pointer = frag_outputs_; storage_class = spv::StorageClass::StorageClassOutput; storage_type = vec4_float_type_; storage_offsets.push_back(storage_index); storage_array = true; break; case InstructionStorageTarget::kDepth: assert_true(is_pixel_shader()); storage_pointer = frag_depth_; storage_class = spv::StorageClass::StorageClassOutput; storage_type = float_type_; storage_offsets.push_back(0); storage_array = false; break; case InstructionStorageTarget::kNone: assert_unhandled_case(result.storage_target); break; } if (!storage_pointer) { assert_always(); return; } if (storage_array) { storage_pointer = b.createAccessChain(storage_class, storage_pointer, storage_offsets); } bool source_is_scalar = b.isScalar(source_value_id); bool storage_is_scalar = b.isScalarType(b.getDerefTypeId(storage_pointer)); spv::Id source_type = b.getTypeId(source_value_id); // Only load from storage if we need it later. Id storage_value = 0; if ((source_is_scalar && !storage_is_scalar) || !result.has_all_writes()) { storage_value = b.createLoad(storage_pointer); } // Clamp the input value. if (result.is_clamped) { source_value_id = CreateGlslStd450InstructionCall( spv::NoPrecision, source_type, spv::GLSLstd450::kFClamp, {source_value_id, b.makeFloatConstant(0.0), b.makeFloatConstant(1.0)}); } // swizzle if (!result.is_standard_swizzle() && !source_is_scalar) { std::vector operands; operands.push_back(source_value_id); operands.push_back(b.makeCompositeConstant( vec2_float_type_, std::vector({b.makeFloatConstant(0.f), b.makeFloatConstant(1.f)}))); // Components start from left and are duplicated rightwards // e.g. count = 1, xxxx / count = 2, xyyy ... uint32_t source_components = b.getNumComponents(source_value_id); for (int i = 0; i < b.getNumTypeComponents(storage_type); i++) { if (!result.write_mask[i]) { // Undefined / don't care. operands.push_back(0); continue; } auto swiz = result.components[i]; switch (swiz) { case SwizzleSource::kX: operands.push_back(0); break; case SwizzleSource::kY: operands.push_back(1); break; case SwizzleSource::kZ: operands.push_back(2); break; case SwizzleSource::kW: operands.push_back(3); break; case SwizzleSource::k0: operands.push_back(source_components + 0); break; case SwizzleSource::k1: operands.push_back(source_components + 1); break; } } source_value_id = b.createOp(spv::Op::OpVectorShuffle, storage_type, operands); } // write mask if (!result.has_all_writes() && !source_is_scalar) { std::vector operands; operands.push_back(source_value_id); operands.push_back(storage_value); for (int i = 0; i < b.getNumTypeComponents(storage_type); i++) { operands.push_back( result.write_mask[i] ? i : b.getNumComponents(source_value_id) + i); } source_value_id = b.createOp(spv::Op::OpVectorShuffle, storage_type, operands); } else if (source_is_scalar && !storage_is_scalar) { assert_true(result.num_writes() >= 1); if (result.has_all_writes()) { source_value_id = b.smearScalar(spv::NoPrecision, source_value_id, storage_type); } else { // Find first enabled component uint32_t index = 0; for (uint32_t i = 0; i < 4; i++) { if (result.write_mask[i]) { index = i; break; } } source_value_id = b.createCompositeInsert(source_value_id, storage_value, storage_type, index); } } // Perform store into the pointer. assert_true(b.getNumComponents(source_value_id) == b.getNumTypeComponents(storage_type)); assert_true(b.getTypeId(source_value_id) == b.getDerefTypeId(storage_pointer)); b.createStore(source_value_id, storage_pointer); } } // namespace gpu } // namespace xe