diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 6ebe2fb05..27e3a628e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -2,6 +2,7 @@ #include "ControlFlow/RequestSerializer.hpp" #include "Optimization/RequestMemoryView.hpp" #include "Optimization/ResourceProgram.hpp" +#include "SpirvBackend/SpirvOptimizer.hpp" #include #include #include @@ -40,6 +41,23 @@ void verifyResult(const ShaderRecompiler::RecompileResult& first, const ShaderRe int main() { try { using namespace ShaderRecompiler; + const std::vector minimalSpirv{ + 0x07230203u, 0x00010000u, 0u, 5u, 0u, + 0x00020011u, 1u, + 0x0003000eu, 0u, 1u, + 0x0005000fu, 5u, 3u, 0x6e69616du, 0u, + 0x00060010u, 3u, 17u, 1u, 1u, 1u, + 0x00020013u, 1u, + 0x00030021u, 2u, 1u, + 0x00050036u, 1u, 3u, 0u, 2u, + 0x000200f8u, 4u, + 0x00010000u, + 0x000100fdu, + 0x00010038u + }; + const auto optimizedSpirv = ValidateAndOptimizeSpirv(minimalSpirv, 0x00401001u, 0x00010000u); + require(optimizedSpirv.size() < minimalSpirv.size(), "SPIR-V optimization did not remove the no-op"); + require(optimizedSpirv == ValidateAndOptimizeSpirv(minimalSpirv, 0x00401001u, 0x00010000u), "SPIR-V optimization is not deterministic"); const std::array code{0xf4040004u, 0xfa000000u, 0xf4000080u, 0xfa000000u, 0x7e000202u, 0xf80008cfu, 0u, 0xbf810000u}; std::uint32_t payload = 0x3f800000u; std::uint64_t table = reinterpret_cast(&payload); @@ -64,6 +82,11 @@ int main() { request.context.memory = regions; const auto first = Recompile(request); require(!first.spirv.empty(), "empty compiled shader"); + auto invalidSpirv = first.spirv; + invalidSpirv[0] = 0; + expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(invalidSpirv, request.target.vulkanVersion, request.target.spirvVersion)); }, "SPIR-V validation before optimization failed", "invalid SPIR-V passed validation"); + expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted"); + expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted"); const auto serialized = RequestSerializer{}.Serialize(request); table = 0; payload = 0xdeadbeefu; diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index af024f2d5..14dde68f8 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -3,6 +3,16 @@ function(add_shader_recompiler target runtimeTarget) message(FATAL_ERROR "Shader recompiler requires an existing runtime target: ${runtimeTarget}") endif() + if(NOT TARGET SPIRV-Tools-opt) + set(SPIRV-Headers_SOURCE_DIR "${PROJECT_SOURCE_DIR}/3rdparty/SPIRV-Headers") + set(SPIRV_SKIP_EXECUTABLES ON) + set(SPIRV_SKIP_TESTS ON) + set(SPIRV_TOOLS_BUILD_STATIC ON) + set(SPIRV_WERROR OFF) + set(BUILD_SHARED_LIBS OFF) + add_subdirectory("${PROJECT_SOURCE_DIR}/3rdparty/SPIRV-Tools" "${CMAKE_BINARY_DIR}/3rdparty/SPIRV-Tools" EXCLUDE_FROM_ALL) + endif() + if(NOT TARGET glslang) set(BUILD_SHARED_LIBS OFF) set(BUILD_EXTERNAL OFF) @@ -108,6 +118,7 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvBdaFault.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvBdaRead.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvEmitter.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvOptimizer.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvBufferFormat.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -147,5 +158,7 @@ function(add_shader_recompiler target runtimeTarget) ${runtimeTarget} glslang SPIRV + SPIRV-Tools-opt + SPIRV-Tools-static ) endfunction() diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 579a8ba6f..e1a9a0452 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -181,7 +181,7 @@ struct ShaderPixelInputInfo { ShaderStageRuntime stage; bool HasPositionInput() const { - throw std::runtime_error("shader input helper not implemented"); + return psPosX || psPosY || psPosZ || psPosW; } }; @@ -193,4 +193,4 @@ struct ShaderStageInputInfo { } -#endif \ No newline at end of file +#endif diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 83c9599aa..d2b5555f9 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -16,9 +16,11 @@ #include "Optimization/include/Optimization/SrtWalker.hpp" #include "Optimization/include/Optimization/SsaBuilder.hpp" #include "SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp" +#include "SpirvBackend/SpirvOptimizer.hpp" #include "Translation/include/Translation/InstructionTranslator.hpp" #include "Translation/include/Translation/ShaderInputInfoBuilder.hpp" #include +#include #include #include #include @@ -156,11 +158,13 @@ RecompileResult RecompileImpl(const RecompileRequest& request) { const auto spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions); RecompileResult result; - result.spirv = spirv; + result.spirv = ValidateAndOptimizeSpirv(spirv, request.target.vulkanVersion, request.target.spirvVersion); result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u; result.bindings = bindings.bindings; result.pushConstants = bindings.pushConstants; + std::fprintf(stdout, "[RecompileImpl] shader recompiled\n"); + std::fflush(stdout); return result; } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp new file mode 100644 index 000000000..f162e6bc4 --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_SHADER_RECOMPILER_SPIRVBACKEND_SPIRVOPTIMIZER_HPP +#define CORE_SHADER_RECOMPILER_SPIRVBACKEND_SPIRVOPTIMIZER_HPP + +#include +#include +#include + +namespace ShaderRecompiler { + +[[nodiscard]] std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion); + +} + +#endif diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp new file mode 100644 index 000000000..ae013026a --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp @@ -0,0 +1,59 @@ +#include "SpirvBackend/SpirvOptimizer.hpp" +#include +#include +#include +#include + +namespace ShaderRecompiler { + +std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion) { + spv_target_env environment; + const auto apiVersion = vulkanVersion & ~0xfffu; + std::uint32_t maxSpirvVersion = 0; + switch (apiVersion) { + case 0x00400000u: environment = SPV_ENV_VULKAN_1_0; maxSpirvVersion = 0x00010000u; break; + case 0x00401000u: environment = spirvVersion == 0x00010400u ? SPV_ENV_VULKAN_1_1_SPIRV_1_4 : SPV_ENV_VULKAN_1_1; maxSpirvVersion = 0x00010400u; break; + case 0x00402000u: environment = SPV_ENV_VULKAN_1_2; maxSpirvVersion = 0x00010500u; break; + case 0x00403000u: environment = SPV_ENV_VULKAN_1_3; maxSpirvVersion = 0x00010600u; break; + case 0x00404000u: environment = SPV_ENV_VULKAN_1_4; maxSpirvVersion = 0x00010600u; break; + default: throw std::runtime_error("SPIRV-Tools: unsupported Vulkan target"); + } + if (spirvVersion < 0x00010000u || spirvVersion > maxSpirvVersion || (spirvVersion & 0xffu) != 0) { + throw std::runtime_error("SPIRV-Tools: unsupported Vulkan/SPIR-V target"); + } + if (spirv.size() >= 5 && spirv[1] > spirvVersion) { + throw std::runtime_error("SPIRV-Tools: module version exceeds requested SPIR-V target"); + } + std::string diagnostics; + const auto consumer = [&diagnostics](spv_message_level_t level, const char* source, const spv_position_t& position, const char* message) { + if (!diagnostics.empty()) diagnostics += '\n'; + diagnostics += "level=" + std::to_string(static_cast(level)) + " word=" + std::to_string(position.index); + if (source != nullptr && *source != '\0') diagnostics += " source=" + std::string(source); + if (message != nullptr) diagnostics += ": " + std::string(message); + }; + spvtools::SpirvTools tools(environment); + if (!tools.IsValid()) throw std::runtime_error("SPIRV-Tools: cannot create validator"); + tools.SetMessageConsumer(consumer); + if (!tools.Validate(spirv.data(), spirv.size())) { + throw std::runtime_error("SPIR-V validation before optimization failed:\n" + diagnostics); + } + spvtools::Optimizer optimizer(environment); + optimizer.SetMessageConsumer(consumer); + optimizer.RegisterPerformancePasses(true); + optimizer.SetValidateAfterAll(true); + spvtools::OptimizerOptions options; + options.set_preserve_bindings(true); + options.set_preserve_spec_constants(true); + std::vector optimized; + diagnostics.clear(); + if (!optimizer.Run(spirv.data(), spirv.size(), &optimized, options)) { + throw std::runtime_error("SPIR-V optimization failed:\n" + diagnostics); + } + diagnostics.clear(); + if (!tools.Validate(optimized.data(), optimized.size())) { + throw std::runtime_error("SPIR-V validation after optimization failed:\n" + diagnostics); + } + return optimized; +} + +}