diff --git a/.gitmodules b/.gitmodules index 0b1f9ce9c..2338fce50 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,5 +57,7 @@ url = https://github.com/microsoft/DirectXShaderCompiler.git [submodule "third_party/glslang"] path = third_party/glslang - url = https://github.com/Triang3l/glslang.git - branch = patch-1 + url = https://github.com/KhronosGroup/glslang.git +[submodule "third_party/SPIRV-Tools"] + path = third_party/SPIRV-Tools + url = https://github.com/KhronosGroup/SPIRV-Tools.git diff --git a/src/xenia/gpu/premake5.lua b/src/xenia/gpu/premake5.lua index f4b2a08d8..5de398e94 100644 --- a/src/xenia/gpu/premake5.lua +++ b/src/xenia/gpu/premake5.lua @@ -30,6 +30,7 @@ project("xenia-gpu-shader-compiler") "glslang-spirv", "xenia-base", "xenia-gpu", + "xenia-ui-vulkan", }) defines({ }) diff --git a/src/xenia/gpu/shader_compiler_main.cc b/src/xenia/gpu/shader_compiler_main.cc index a342ecfdf..f79e36df0 100644 --- a/src/xenia/gpu/shader_compiler_main.cc +++ b/src/xenia/gpu/shader_compiler_main.cc @@ -23,6 +23,7 @@ #include "xenia/gpu/dxbc_shader_translator.h" #include "xenia/gpu/shader_translator.h" #include "xenia/gpu/spirv_shader_translator.h" +#include "xenia/ui/vulkan/spirv_tools_context.h" // For D3DDisassemble: #if XE_PLATFORM_WIN32 @@ -159,6 +160,17 @@ int shader_compiler_main(const std::vector& args) { source_data_size / sizeof(unsigned int)); spv::Disassemble(spirv_disasm_stream, spirv_source); spirv_disasm = std::move(spirv_disasm_stream.str()); + ui::vulkan::SpirvToolsContext spirv_tools_context; + if (spirv_tools_context.Initialize()) { + std::string spirv_validation_error; + spirv_tools_context.Validate( + reinterpret_cast(spirv_source.data()), + spirv_source.size(), &spirv_validation_error); + if (!spirv_validation_error.empty()) { + spirv_disasm.append(1, '\n'); + spirv_disasm.append(spirv_validation_error); + } + } source_data = spirv_disasm.c_str(); source_data_size = spirv_disasm.size(); } diff --git a/src/xenia/gpu/shader_translator.cc b/src/xenia/gpu/shader_translator.cc index 304acf602..f2bf35bf5 100644 --- a/src/xenia/gpu/shader_translator.cc +++ b/src/xenia/gpu/shader_translator.cc @@ -101,7 +101,6 @@ bool ShaderTranslator::TranslateInternal( // Each control flow instruction is executed sequentially until the final // ending instruction. uint32_t max_cf_dword_index = static_cast(ucode_dword_count_); - std::vector cf_instructions; for (uint32_t i = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; ControlFlowInstruction cf_b; @@ -121,8 +120,6 @@ bool ShaderTranslator::TranslateInternal( // Translators may need this before they start codegen. GatherInstructionInformation(cf_a); GatherInstructionInformation(cf_b); - cf_instructions.push_back(cf_a); - cf_instructions.push_back(cf_b); } if (constant_register_map_.float_dynamic_addressing) { @@ -159,8 +156,6 @@ bool ShaderTranslator::TranslateInternal( StartTranslation(); - PreProcessControlFlowInstructions(cf_instructions); - // Translate all instructions. for (uint32_t i = 0, cf_index = 0; i < max_cf_dword_index; i += 3) { ControlFlowInstruction cf_a; diff --git a/src/xenia/gpu/shader_translator.h b/src/xenia/gpu/shader_translator.h index 3d4fa208d..73ab4f6c0 100644 --- a/src/xenia/gpu/shader_translator.h +++ b/src/xenia/gpu/shader_translator.h @@ -136,10 +136,6 @@ class ShaderTranslator { shader->host_disassembly_ = std::move(value); } - // Pre-process a control-flow instruction before anything else. - virtual void PreProcessControlFlowInstructions( - std::vector instrs) {} - // Handles translation for control flow label addresses. // This is triggered once for each label required (due to control flow // operations) before any of the instructions within the target exec. diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index ce232a7ad..d423ae509 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -10,9 +10,11 @@ #include "xenia/gpu/spirv_shader_translator.h" #include +#include #include #include "third_party/glslang/SPIRV/GLSL.std.450.h" +#include "xenia/base/assert.h" namespace xe { namespace gpu { @@ -26,13 +28,15 @@ void SpirvShaderTranslator::Reset() { ShaderTranslator::Reset(); builder_.reset(); + + // main_switch_cases_.reset(); } void SpirvShaderTranslator::StartTranslation() { - // TODO(Triang3l): Once tool ID (likely 26) is registered in SPIRV-Headers, - // use it instead. + // Tool ID 26 "Xenia Emulator Microcode Translator". + // https://github.com/KhronosGroup/SPIRV-Headers/blob/c43a43c7cc3af55910b9bec2a71e3e8a622443cf/include/spirv/spir-v.xml#L79 // TODO(Triang3l): Logger. - builder_ = std::make_unique(0x10000, 0xFFFF0001, nullptr); + builder_ = std::make_unique(1 << 16, (26 << 16) | 1, nullptr); builder_->addCapability(IsSpirvTessEvalShader() ? spv::CapabilityTessellation : spv::CapabilityShader); @@ -42,11 +46,29 @@ void SpirvShaderTranslator::StartTranslation() { builder_->setSource(spv::SourceLanguageUnknown, 0); type_void_ = builder_->makeVoidType(); + type_bool_ = builder_->makeBoolType(); + type_int_ = builder_->makeIntType(32); + type_int4_ = builder_->makeVectorType(type_int_, 4); type_float_ = builder_->makeFloatType(32); type_float2_ = builder_->makeVectorType(type_float_, 2); type_float3_ = builder_->makeVectorType(type_float_, 3); type_float4_ = builder_->makeVectorType(type_float_, 4); - type_int_ = builder_->makeIntType(32); + + const_int_0_ = builder_->makeIntConstant(0); + id_vector_temp_.clear(); + id_vector_temp_.reserve(4); + for (uint32_t i = 0; i < 4; ++i) { + id_vector_temp_.push_back(const_int_0_); + } + const_int4_0_ = builder_->makeCompositeConstant(type_int4_, id_vector_temp_); + const_float_0_ = builder_->makeFloatConstant(0.0f); + id_vector_temp_.clear(); + id_vector_temp_.reserve(4); + for (uint32_t i = 0; i < 4; ++i) { + id_vector_temp_.push_back(const_float_0_); + } + const_float4_0_ = + builder_->makeCompositeConstant(type_float4_, id_vector_temp_); if (IsSpirvVertexOrTessEvalShader()) { StartVertexOrTessEvalShaderBeforeMain(); @@ -55,28 +77,131 @@ void SpirvShaderTranslator::StartTranslation() { // Begin the main function. std::vector main_param_types; std::vector> main_precisions; - spv::Block* main_entry; - builder_->makeFunctionEntry(spv::NoPrecision, type_void_, "main", - main_param_types, main_precisions, &main_entry); + spv::Block* function_main_entry; + function_main_ = builder_->makeFunctionEntry( + spv::NoPrecision, type_void_, "main", main_param_types, main_precisions, + &function_main_entry); - // Begin ucode translation. - if (register_count()) { + // Begin ucode translation. Initialize everything, even without defined + // defaults, for safety. + var_main_predicate_ = builder_->createVariable( + spv::NoPrecision, spv::StorageClassFunction, type_bool_, + "xe_var_predicate", builder_->makeBoolConstant(false)); + var_main_address_absolute_ = builder_->createVariable( + spv::NoPrecision, spv::StorageClassFunction, type_int_, + "xe_var_address_absolute", const_int_0_); + var_main_address_relative_ = builder_->createVariable( + spv::NoPrecision, spv::StorageClassFunction, type_int4_, + "xe_var_address_relative", const_int4_0_); + uint32_t register_array_size = register_count(); + if (register_array_size) { + id_vector_temp_.clear(); + id_vector_temp_.reserve(register_array_size); + // TODO(Triang3l): In PS, only initialize starting from the interpolators, + // probably manually. But not very important. + for (uint32_t i = 0; i < register_array_size; ++i) { + id_vector_temp_.push_back(const_float4_0_); + } + spv::Id type_register_array = builder_->makeArrayType( + type_float4_, builder_->makeUintConstant(register_array_size), 0); var_main_registers_ = builder_->createVariable( - spv::NoPrecision, spv::StorageClassFunction, - builder_->makeArrayType( - type_float4_, builder_->makeUintConstant(register_count()), 0), - "xe_r"); + spv::NoPrecision, spv::StorageClassFunction, type_register_array, + "xe_var_registers", + builder_->makeCompositeConstant(type_register_array, id_vector_temp_)); } + + // Write the execution model-specific prologue with access to variables in the + // main function. + if (IsSpirvVertexOrTessEvalShader()) { + StartVertexOrTessEvalShaderInMain(); + } + + // Open the main loop. + + spv::Block* main_loop_pre_header = builder_->getBuildPoint(); + main_loop_header_ = &builder_->makeNewBlock(); + spv::Block& main_loop_body = builder_->makeNewBlock(); + // Added later because the body has nested control flow, but according to the + // specification: + // "The order of blocks in a function must satisfy the rule that blocks appear + // before all blocks they dominate." + main_loop_continue_ = + new spv::Block(builder_->getUniqueId(), *function_main_); + main_loop_merge_ = new spv::Block(builder_->getUniqueId(), *function_main_); + builder_->createBranch(main_loop_header_); + + // Main loop header - based on whether it's the first iteration (entered from + // the function or from the continuation), choose the program counter. + builder_->setBuildPoint(main_loop_header_); + id_vector_temp_.clear(); + id_vector_temp_.reserve(4); + id_vector_temp_.push_back(const_int_0_); + id_vector_temp_.push_back(main_loop_pre_header->getId()); + main_loop_pc_next_ = builder_->getUniqueId(); + id_vector_temp_.push_back(main_loop_pc_next_); + id_vector_temp_.push_back(main_loop_continue_->getId()); + spv::Id main_loop_pc_current = + builder_->createOp(spv::OpPhi, type_int_, id_vector_temp_); + uint_vector_temp_.clear(); + builder_->createLoopMerge(main_loop_merge_, main_loop_continue_, + spv::LoopControlDontUnrollMask, uint_vector_temp_); + builder_->createBranch(&main_loop_body); + + // Main loop body. + builder_->setBuildPoint(&main_loop_body); + // TODO(Triang3l): Create the switch, add the block for the case 0 and set the + // build point to it. } std::vector SpirvShaderTranslator::CompleteTranslation() { + // Close the main loop. + // Break from the body after falling through the end or breaking. + builder_->createBranch(main_loop_merge_); + // Main loop continuation - choose the program counter based on the path + // taken (-1 if not from a jump as a safe fallback, which would result in not + // hitting any switch case and reaching the final break in the body). + function_main_->addBlock(main_loop_continue_); + builder_->setBuildPoint(main_loop_continue_); + { + std::unique_ptr main_loop_pc_next_op = + std::make_unique(main_loop_pc_next_, type_int_, + spv::OpCopyObject); + // TODO(Triang3l): Phi between the continues in the switch cases and the + // switch merge block. + main_loop_pc_next_op->addIdOperand(builder_->makeIntConstant(-1)); + builder_->getBuildPoint()->addInstruction(std::move(main_loop_pc_next_op)); + } + builder_->createBranch(main_loop_header_); + // Add the main loop merge block and go back to the function. + function_main_->addBlock(main_loop_merge_); + builder_->setBuildPoint(main_loop_merge_); + if (IsSpirvVertexOrTessEvalShader()) { CompleteVertexOrTessEvalShaderInMain(); } - // End the main function.. + // End the main function. builder_->leaveFunction(); + // Make the main function the entry point. + spv::ExecutionModel execution_model; + if (IsSpirvFragmentShader()) { + execution_model = spv::ExecutionModelFragment; + builder_->addExecutionMode(function_main_, + spv::ExecutionModeOriginUpperLeft); + } else { + assert_true(IsSpirvVertexOrTessEvalShader()); + execution_model = IsSpirvTessEvalShader() + ? spv::ExecutionModelTessellationEvaluation + : spv::ExecutionModelVertex; + } + spv::Instruction* entry_point = + builder_->addEntryPoint(execution_model, function_main_, "main"); + + if (IsSpirvVertexOrTessEvalShader()) { + CompleteVertexOrTessEvalShaderAfterMain(entry_point); + } + // TODO(Triang3l): Avoid copy? std::vector module_uints; builder_->dump(module_uints); @@ -92,14 +217,14 @@ std::vector SpirvShaderTranslator::CompleteTranslation() { void SpirvShaderTranslator::StartVertexOrTessEvalShaderBeforeMain() { // Create the inputs. if (IsSpirvTessEvalShader()) { - input_vertex_index_ = builder_->createVariable( + input_primitive_id_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassInput, type_int_, "gl_PrimitiveID"); - builder_->addDecoration(input_vertex_index_, spv::DecorationBuiltIn, + builder_->addDecoration(input_primitive_id_, spv::DecorationBuiltIn, spv::BuiltInPrimitiveId); } else { - input_primitive_id_ = builder_->createVariable( + input_vertex_index_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassInput, type_int_, "gl_VertexIndex"); - builder_->addDecoration(input_primitive_id_, spv::DecorationBuiltIn, + builder_->addDecoration(input_vertex_index_, spv::DecorationBuiltIn, spv::BuiltInVertexIndex); } @@ -145,7 +270,23 @@ void SpirvShaderTranslator::StartVertexOrTessEvalShaderBeforeMain() { type_struct_per_vertex, "xe_out_gl_PerVertex"); } +void SpirvShaderTranslator::StartVertexOrTessEvalShaderInMain() { + var_main_point_size_edge_flag_kill_vertex_ = builder_->createVariable( + spv::NoPrecision, spv::StorageClassFunction, type_float3_, + "xe_var_point_size_edge_flag_kill_vertex"); +} + void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() {} +void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderAfterMain( + spv::Instruction* entry_point) { + if (IsSpirvTessEvalShader()) { + entry_point->addIdOperand(input_primitive_id_); + } else { + entry_point->addIdOperand(input_vertex_index_); + } + entry_point->addIdOperand(output_per_vertex_); +} + } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index 5ef5dfc2c..1fa777271 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -46,24 +46,38 @@ class SpirvShaderTranslator : public ShaderTranslator { bool IsSpirvFragmentShader() const { return is_pixel_shader(); } void StartVertexOrTessEvalShaderBeforeMain(); + void StartVertexOrTessEvalShaderInMain(); void CompleteVertexOrTessEvalShaderInMain(); + void CompleteVertexOrTessEvalShaderAfterMain(spv::Instruction* entry_point); bool supports_clip_distance_; bool supports_cull_distance_; std::unique_ptr builder_; + std::vector id_vector_temp_; + std::vector uint_vector_temp_; + spv::Id ext_inst_glsl_std_450_; spv::Id type_void_; + spv::Id type_bool_; + spv::Id type_int_; + spv::Id type_int4_; + spv::Id type_uint_; spv::Id type_float_; spv::Id type_float2_; spv::Id type_float3_; spv::Id type_float4_; - spv::Id type_int_; - spv::Id type_uint_; + spv::Id const_int_0_; + spv::Id const_int4_0_; + spv::Id const_float_0_; + spv::Id const_float4_0_; + + // VS as VS only - int. spv::Id input_vertex_index_; + // VS as TES only - int. spv::Id input_primitive_id_; enum OutputPerVertexMember : unsigned int { @@ -75,8 +89,21 @@ class SpirvShaderTranslator : public ShaderTranslator { }; spv::Id output_per_vertex_; - spv::Id function_main_; + spv::Function* function_main_; + // bool. + spv::Id var_main_predicate_; + // int4. + spv::Id var_main_address_relative_; + // int. + spv::Id var_main_address_absolute_; + // float4[register_count()]. spv::Id var_main_registers_; + // VS only - float3 (special exports). + spv::Id var_main_point_size_edge_flag_kill_vertex_; + spv::Block* main_loop_header_; + spv::Block* main_loop_continue_; + spv::Block* main_loop_merge_; + spv::Id main_loop_pc_next_; }; } // namespace gpu diff --git a/src/xenia/ui/vulkan/spirv_tools_context.cc b/src/xenia/ui/vulkan/spirv_tools_context.cc new file mode 100644 index 000000000..01078ca08 --- /dev/null +++ b/src/xenia/ui/vulkan/spirv_tools_context.cc @@ -0,0 +1,113 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/vulkan/spirv_tools_context.h" + +#include +#include +#include + +#include "xenia/base/logging.h" +#include "xenia/base/platform.h" + +#if XE_PLATFORM_LINUX +#include +#elif XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif + +namespace xe { +namespace ui { +namespace vulkan { + +bool SpirvToolsContext::Initialize() { + const char* vulkan_sdk_env = std::getenv("VULKAN_SDK"); + if (!vulkan_sdk_env) { + XELOGE("SPIRV-Tools: Failed to get the VULKAN_SDK environment variable"); + Shutdown(); + return false; + } + std::filesystem::path vulkan_sdk_path(vulkan_sdk_env); +#if XE_PLATFORM_LINUX + library_ = dlopen((vulkan_sdk_path / "bin/libSPIRV-Tools-shared.so").c_str(), + RTLD_NOW | RTLD_LOCAL); + if (!library_) { + XELOGE( + "SPIRV-Tools: Failed to load $VULKAN_SDK/bin/libSPIRV-Tools-shared.so"); + Shutdown(); + return false; + } +#elif XE_PLATFORM_WIN32 + library_ = LoadLibraryW( + (vulkan_sdk_path / "Bin/SPIRV-Tools-shared.dll").wstring().c_str()); + if (!library_) { + XELOGE( + "SPIRV-Tools: Failed to load %VULKAN_SDK%/Bin/SPIRV-Tools-shared.dll"); + Shutdown(); + return false; + } +#else +#error No SPIRV-Tools library loading provided for the target platform. +#endif + if (!LoadLibraryFunction(fn_spvContextCreate_, "spvContextCreate") || + !LoadLibraryFunction(fn_spvContextDestroy_, "spvContextDestroy") || + !LoadLibraryFunction(fn_spvValidateBinary_, "spvValidateBinary") || + !LoadLibraryFunction(fn_spvDiagnosticDestroy_, "spvDiagnosticDestroy")) { + XELOGE("SPIRV-Tools: Failed to get library function pointers"); + Shutdown(); + return false; + } + context_ = fn_spvContextCreate_(SPV_ENV_VULKAN_1_0); + if (!context_) { + XELOGE("SPIRV-Tools: Failed to create a Vulkan 1.0 context"); + Shutdown(); + return false; + } + return true; +} + +void SpirvToolsContext::Shutdown() { + if (context_) { + fn_spvContextDestroy_(context_); + context_ = nullptr; + } + if (library_) { +#if XE_PLATFORM_LINUX + dlclose(library_); +#elif XE_PLATFORM_WIN32 + FreeLibrary(library_); +#endif + library_ = nullptr; + } +} + +spv_result_t SpirvToolsContext::Validate(const uint32_t* words, + size_t num_words, + std::string* error) const { + if (error) { + error->clear(); + } + if (!context_) { + return SPV_UNSUPPORTED; + } + spv_diagnostic diagnostic = nullptr; + spv_result_t result = + fn_spvValidateBinary_(context_, words, num_words, &diagnostic); + if (diagnostic) { + if (error && diagnostic && diagnostic->error) { + *error = diagnostic->error; + } + fn_spvDiagnosticDestroy_(diagnostic); + } + return result; +} + +} // namespace vulkan +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/vulkan/spirv_tools_context.h b/src/xenia/ui/vulkan/spirv_tools_context.h new file mode 100644 index 000000000..87680c1a4 --- /dev/null +++ b/src/xenia/ui/vulkan/spirv_tools_context.h @@ -0,0 +1,72 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_VULKAN_SPIRV_TOOLS_CONTEXT_H_ +#define XENIA_UI_VULKAN_SPIRV_TOOLS_CONTEXT_H_ + +#include +#include + +#include "third_party/SPIRV-Tools/include/spirv-tools/libspirv.h" +#include "xenia/base/platform.h" + +#if XE_PLATFORM_LINUX +#include +#elif XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif + +namespace xe { +namespace ui { +namespace vulkan { + +class SpirvToolsContext { + public: + SpirvToolsContext() {} + SpirvToolsContext(const SpirvToolsContext& context) = delete; + SpirvToolsContext& operator=(const SpirvToolsContext& context) = delete; + ~SpirvToolsContext() { Shutdown(); } + bool Initialize(); + void Shutdown(); + + spv_result_t Validate(const uint32_t* words, size_t num_words, + std::string* error) const; + + private: +#if XE_PLATFORM_LINUX + void* library_ = nullptr; +#elif XE_PLATFORM_WIN32 + HMODULE library_ = nullptr; +#endif + + template + bool LoadLibraryFunction(FunctionPointer& function, const char* name) { +#if XE_PLATFORM_LINUX + function = reinterpret_cast(dlsym(library_, name)); +#elif XE_PLATFORM_WIN32 + function = + reinterpret_cast(GetProcAddress(library_, name)); +#else +#error No SPIRV-Tools LoadLibraryFunction provided for the target platform. +#endif + return function != nullptr; + } + decltype(&spvContextCreate) fn_spvContextCreate_ = nullptr; + decltype(&spvContextDestroy) fn_spvContextDestroy_ = nullptr; + decltype(&spvValidateBinary) fn_spvValidateBinary_ = nullptr; + decltype(&spvDiagnosticDestroy) fn_spvDiagnosticDestroy_ = nullptr; + + spv_context context_ = nullptr; +}; + +} // namespace vulkan +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_VULKAN_SPIRV_TOOLS_CONTEXT_H_ diff --git a/third_party/SPIRV-Tools b/third_party/SPIRV-Tools new file mode 160000 index 000000000..dd534e877 --- /dev/null +++ b/third_party/SPIRV-Tools @@ -0,0 +1 @@ +Subproject commit dd534e877e725c9bb6f751c427442456a05384e4 diff --git a/third_party/glslang b/third_party/glslang index 5a9dfb674..f4f1d8a35 160000 --- a/third_party/glslang +++ b/third_party/glslang @@ -1 +1 @@ -Subproject commit 5a9dfb6741ca851f8bb57abc0fe808f5a0705fa2 +Subproject commit f4f1d8a352ca1908943aea2ad8c54b39b4879080