feat(shader): add spirv validation and opt via spirv-tools
This commit is contained in:
parent
798e38f47e
commit
1da8521ffc
|
|
@ -2,6 +2,7 @@
|
|||
#include "ControlFlow/RequestSerializer.hpp"
|
||||
#include "Optimization/RequestMemoryView.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
|
@ -40,6 +41,23 @@ void verifyResult(const ShaderRecompiler::RecompileResult& first, const ShaderRe
|
|||
int main() {
|
||||
try {
|
||||
using namespace ShaderRecompiler;
|
||||
const std::vector<std::uint32_t> 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<std::uint32_t, 8> code{0xf4040004u, 0xfa000000u, 0xf4000080u, 0xfa000000u, 0x7e000202u, 0xf80008cfu, 0u, 0xbf810000u};
|
||||
std::uint32_t payload = 0x3f800000u;
|
||||
std::uint64_t table = reinterpret_cast<std::uintptr_t>(&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<void>(ValidateAndOptimizeSpirv(invalidSpirv, request.target.vulkanVersion, request.target.spirvVersion)); }, "SPIR-V validation before optimization failed", "invalid SPIR-V passed validation");
|
||||
expectFailure([&] { static_cast<void>(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted");
|
||||
expectFailure([&] { static_cast<void>(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted");
|
||||
const auto serialized = RequestSerializer{}.Serialize(request);
|
||||
table = 0;
|
||||
payload = 0xdeadbeefu;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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 <exception>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <ControlFlow/RequestSerializer.hpp>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef CORE_SHADER_RECOMPILER_SPIRVBACKEND_SPIRVOPTIMIZER_HPP
|
||||
#define CORE_SHADER_RECOMPILER_SPIRVBACKEND_SPIRVOPTIMIZER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
[[nodiscard]] std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
#include <spirv-tools/libspirv.hpp>
|
||||
#include <spirv-tools/optimizer.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> 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<int>(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<std::uint32_t> 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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue