feat(shader): add spirv validation and opt via spirv-tools

This commit is contained in:
boykopovar 2026-09-22 10:43:54 +03:00
parent 798e38f47e
commit 1da8521ffc
6 changed files with 116 additions and 3 deletions

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}