From 07a5c91427b1c25a58841acdf095ed6b5d1d2fc6 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 8 May 2026 16:46:03 +1000 Subject: [PATCH] shader validation checks --- .gitea/workflows/ci.yml | 2 +- CMakeLists.txt | 23 ++++ .../shader/ShaderCompiler.cpp | 12 ++ tests/ShaderSlangValidationTests.cpp | 106 ++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tests/ShaderSlangValidationTests.cpp diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 5fe35f2..979b740 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: shell: powershell run: cmake --build --preset build-debug - - name: Run Native Tests + - name: Run Native Tests And Shader Validation shell: powershell run: cmake --build --preset build-debug --target RUN_TESTS diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b61caf..a58323c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,6 +235,29 @@ endif() add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests) +add_executable(ShaderSlangValidationTests + "${APP_DIR}/runtime/RuntimeJson.cpp" + "${APP_DIR}/shader/ShaderCompiler.cpp" + "${APP_DIR}/shader/ShaderPackageRegistry.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp" +) + +target_include_directories(ShaderSlangValidationTests PRIVATE + "${APP_DIR}" + "${APP_DIR}/platform" + "${APP_DIR}/runtime" + "${APP_DIR}/shader" +) + +if(MSVC) + target_compile_options(ShaderSlangValidationTests PRIVATE /W3) +endif() + +add_test(NAME ShaderSlangValidationTests COMMAND ShaderSlangValidationTests) +set_tests_properties(ShaderSlangValidationTests PROPERTIES + ENVIRONMENT "SLANG_ROOT=${SLANG_ROOT}" +) + add_executable(OscServerTests "${APP_DIR}/control/OscServer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp index 64ceda3..f5b6425 100644 --- a/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderCompiler.cpp @@ -190,6 +190,18 @@ bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const { + char slangRootBuffer[MAX_PATH] = {}; + const DWORD slangRootLength = GetEnvironmentVariableA("SLANG_ROOT", slangRootBuffer, static_cast(sizeof(slangRootBuffer))); + if (slangRootLength > 0 && slangRootLength < sizeof(slangRootBuffer)) + { + std::filesystem::path candidate = std::filesystem::path(slangRootBuffer) / "bin" / "slangc.exe"; + if (std::filesystem::exists(candidate)) + { + compilerPath = candidate; + return true; + } + } + std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty"; if (!std::filesystem::exists(thirdPartyRoot)) { diff --git a/tests/ShaderSlangValidationTests.cpp b/tests/ShaderSlangValidationTests.cpp new file mode 100644 index 0000000..4acb5b8 --- /dev/null +++ b/tests/ShaderSlangValidationTests.cpp @@ -0,0 +1,106 @@ +#include "ShaderCompiler.h" +#include "ShaderPackageRegistry.h" + +#include +#include +#include +#include +#include + +namespace +{ +int gFailures = 0; + +void Fail(const std::string& message) +{ + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} + +std::filesystem::path FindRepoRoot() +{ + std::filesystem::path current = std::filesystem::current_path(); + for (;;) + { + if (std::filesystem::exists(current / "shaders") && + std::filesystem::exists(current / "runtime" / "templates" / "shader_wrapper.slang.in")) + { + return current; + } + + if (!current.has_parent_path() || current.parent_path() == current) + return std::filesystem::path(); + current = current.parent_path(); + } +} + +bool IsExpectedInvalidPackage(const ShaderPackageStatus& status) +{ + return status.id.find("broken-shader-example") != std::string::npos && + status.error.find("Unsupported parameter type") != std::string::npos; +} +} + +int main() +{ + const std::filesystem::path repoRoot = FindRepoRoot(); + if (repoRoot.empty()) + { + Fail("Could not locate repository root from current working directory."); + return 1; + } + + std::map packagesById; + std::vector packageOrder; + std::vector packageStatuses; + std::string error; + + ShaderPackageRegistry registry(12); + if (!registry.Scan(repoRoot / "shaders", packagesById, packageOrder, packageStatuses, error)) + { + Fail("Shader package scan failed: " + error); + return 1; + } + + for (const ShaderPackageStatus& status : packageStatuses) + { + if (status.available || IsExpectedInvalidPackage(status)) + continue; + Fail("Unexpected invalid shader package '" + status.id + "': " + status.error); + } + + const std::filesystem::path validationRoot = repoRoot / "runtime" / "shader_validation"; + const std::filesystem::path wrapperPath = validationRoot / "validation_shader_wrapper.slang"; + const std::filesystem::path generatedGlslPath = validationRoot / "validation_shader.raw.frag"; + const std::filesystem::path patchedGlslPath = validationRoot / "validation_shader.frag"; + + ShaderCompiler compiler(repoRoot, wrapperPath, generatedGlslPath, patchedGlslPath, 12); + for (const std::string& packageId : packageOrder) + { + auto packageIt = packagesById.find(packageId); + if (packageIt == packagesById.end()) + continue; + + std::string fragmentShaderSource; + std::string compileError; + if (!compiler.BuildLayerFragmentShaderSource(packageIt->second, fragmentShaderSource, compileError)) + { + Fail("Shader package '" + packageId + "' failed Slang validation: " + compileError); + continue; + } + if (fragmentShaderSource.find("#version 430 core") == std::string::npos) + Fail("Shader package '" + packageId + "' generated GLSL without the expected patched GLSL version header."); + } + + std::error_code removeError; + std::filesystem::remove_all(validationRoot, removeError); + + if (gFailures != 0) + { + std::cerr << gFailures << " shader Slang validation failure(s).\n"; + return 1; + } + + std::cout << "Validated " << packagesById.size() << " shader package(s) through Slang.\n"; + return 0; +}