Slang seperation
All checks were successful
CI / React UI Build (push) Successful in 12s
CI / Native Windows Build And Tests (push) Successful in 2m21s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-30 21:05:30 +10:00
parent 067c606092
commit fbc2851ccb
7 changed files with 322 additions and 36 deletions

View File

@@ -101,6 +101,8 @@ cmake --preset vs2022-x64-debug `
-DDECKLINK_SDK_ROOT="D:/SDKs/Blackmagic DeckLink SDK 16.0"
```
At runtime, Slang compilation follows the same root order: `SLANG_ROOT`, `THIRD_PARTY_ROOT`, repo `video-io-3rdParty/`, then repo or packaged `3rdParty/`. The packaged layout uses `3rdParty/slang/bin/slangc.exe`; development bundles can use `slang-2026.8-windows-x86_64/bin/slangc.exe`.
## Build
Configure and build the native app:

View File

@@ -66,7 +66,7 @@ Do not move DeckLink, NDI, file I/O, shader compilation, or control handling int
Before cutting a long-lived fork, fix or decide these items:
- Keep `runtimeShaderId` empty in checked-in config unless this repo intentionally wants a default startup shader again.
- Align remaining runtime third-party discovery with CMake. Font atlas generation now checks `MSDF_ATLAS_GEN_ROOT`, `THIRD_PARTY_ROOT`, `3rdParty`, and `video-io-3rdParty`; shader compiler lookup still needs the same treatment for Slang.
- Keep runtime third-party discovery aligned with CMake. Font atlas generation and Slang compilation now both check explicit tool roots, `THIRD_PARTY_ROOT`, the private `video-io-3rdParty` bundle, and legacy or packaged `3rdParty` layouts.
- Make `config/runtime-host.json` portable. Current checked-in defaults include a local NDI source name and DeckLink output.
- Decide whether the fork keeps the Slang shader package contract. If not, retire or clearly isolate `shaders/SHADER_CONTRACT.md`, shader package UI, and shader manifest tests.
- Mark older docs that reference `apps/LoopThroughWithOpenGLCompositing` as historical, or update them to point at the current `src/` implementation.
@@ -75,13 +75,13 @@ Before cutting a long-lived fork, fix or decide these items:
## Verification Snapshot
Last checked locally on 2026-05-30 after the font-builder lookup fix:
Last checked locally on 2026-05-30 after the Slang lookup alignment:
- Worktree was clean before documentation changes.
- UI production build passed with `npm.cmd run build`.
- Native debug build passed with `cmake --build --preset build-debug --parallel`.
- Native tests passed 24 of 24 with `ctest --test-dir build\vs2022-x64-debug -C Debug --output-on-failure`.
- Native debug build passed for `RenderCadenceCompositor`, `ShaderSlangValidationTests`, and `ShaderCompilerLookupTests`.
- Native tests passed 26 of 26 with `ctest --test-dir build\vs2022-x64-debug -C Debug --output-on-failure`.
- `FontAtlasBuilderTests` now passes with `msdf-atlas-gen.exe` supplied by the private `video-io-3rdParty/msdf-atlas-gen` bundle.
- `ShaderCompilerLookupTests` covers `SLANG_ROOT`, `THIRD_PARTY_ROOT`, repo `video-io-3rdParty`, legacy `3rdParty`, and packaged `3rdParty/slang` Slang layouts.
The generated Visual Studio `RUN_TESTS` target did not build missing test executables by itself during the first local run; building the debug preset first produced the test binaries.

View File

@@ -368,6 +368,8 @@ On startup the app first tries to restore `runtime/runtime_state.json`. Valid sa
The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. Once a completed shader artifact is published, the render-thread-owned runtime scene queues changed layers to a shared-context GL prepare worker. That worker compiles/links runtime shader programs off the cadence thread. The render thread only swaps in an already-prepared GL program at a frame boundary. If either the Slang build or GL preparation fails, the app keeps rendering the current renderer or simple motion fallback.
The Slang compiler lookup matches the CMake third-party layout: `SLANG_ROOT` first, then `THIRD_PARTY_ROOT`, then repo `video-io-3rdParty/`, then repo or packaged `3rdParty/`. Packaged builds use `3rdParty/slang/bin/slangc.exe`; development bundles can use `slang-2026.8-windows-x86_64/bin/slangc.exe`.
`POST /api/reload` rescans `shaders/`, re-reads manifests, refreshes supported shader metadata, reconciles active layer parameters against changed definitions, and queues recompilation for every catalog-valid layer in the active stack. It does not compile every package in the shader library; packages are compiled when they are part of the active stack.
Current runtime shader support is deliberately limited to stateless full-frame packages:

View File

@@ -3,8 +3,9 @@
#include "NativeHandles.h"
#include <fstream>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <regex>
#include <sstream>
#include <vector>
@@ -150,6 +151,90 @@ std::string BuildHistorySwitchCases(const std::string& samplerPrefix, unsigned h
source << "\tcase " << index << ": return " << samplerPrefix << index << ".Sample(tc);\n";
return source.str();
}
bool UseExecutableIfPresent(const std::filesystem::path& candidate, std::filesystem::path& compilerPath)
{
if (!std::filesystem::exists(candidate) || std::filesystem::is_directory(candidate))
return false;
compilerPath = candidate;
return true;
}
bool UseSlangRootIfPresent(const std::filesystem::path& root, std::filesystem::path& compilerPath)
{
if (root.empty())
return false;
if (UseExecutableIfPresent(root, compilerPath))
return true;
if (UseExecutableIfPresent(root / "slangc.exe", compilerPath))
return true;
return UseExecutableIfPresent(root / "bin" / "slangc.exe", compilerPath);
}
std::filesystem::path EnvironmentPath(const char* variableName)
{
#if defined(_MSC_VER)
char* value = nullptr;
std::size_t size = 0;
if (_dupenv_s(&value, &size, variableName) != 0 || value == nullptr)
return std::filesystem::path();
std::string text(value);
std::free(value);
if (text.empty())
return std::filesystem::path();
return std::filesystem::path(text);
#else
const char* value = std::getenv(variableName);
if (value == nullptr || *value == '\0')
return std::filesystem::path();
return std::filesystem::path(value);
#endif
}
bool UseEnvironmentSlangRoot(const char* variableName, std::filesystem::path& compilerPath)
{
return UseSlangRootIfPresent(EnvironmentPath(variableName), compilerPath);
}
bool UseThirdPartyRootIfPresent(const std::filesystem::path& thirdPartyRoot, std::filesystem::path& compilerPath)
{
if (thirdPartyRoot.empty())
return false;
const std::vector<std::filesystem::path> preferredRoots = {
thirdPartyRoot / "slang-2026.8-windows-x86_64",
thirdPartyRoot / "slang",
thirdPartyRoot
};
for (const std::filesystem::path& root : preferredRoots)
{
if (UseSlangRootIfPresent(root, compilerPath))
return true;
}
std::error_code directoryError;
if (!std::filesystem::exists(thirdPartyRoot, directoryError) || !std::filesystem::is_directory(thirdPartyRoot, directoryError))
return false;
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyRoot, directoryError))
{
if (directoryError)
break;
std::error_code entryError;
if (!entry.is_directory(entryError))
continue;
if (UseSlangRootIfPresent(entry.path(), compilerPath))
return true;
}
return false;
}
}
ShaderCompiler::ShaderCompiler(
@@ -218,38 +303,25 @@ 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<DWORD>(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 FindSlangCompilerPath(mRepoRoot, compilerPath, error);
}
bool ShaderCompiler::FindSlangCompilerPath(const std::filesystem::path& repoRoot, std::filesystem::path& compilerPath, std::string& error)
{
if (UseEnvironmentSlangRoot("SLANG_ROOT", compilerPath))
return true;
}
}
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
if (!std::filesystem::exists(thirdPartyRoot))
{
error = "3rdParty directory was not found under the repository root.";
return false;
}
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyRoot))
{
if (!entry.is_directory())
continue;
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
if (std::filesystem::exists(candidate))
{
compilerPath = candidate;
const std::filesystem::path thirdPartyRoot = EnvironmentPath("THIRD_PARTY_ROOT");
if (UseThirdPartyRootIfPresent(thirdPartyRoot, compilerPath))
return true;
}
}
error = "Could not find slangc.exe under 3rdParty.";
if (UseThirdPartyRootIfPresent(repoRoot / "video-io-3rdParty", compilerPath))
return true;
if (UseThirdPartyRootIfPresent(repoRoot / "3rdParty", compilerPath))
return true;
error = "Could not find slangc.exe. Set SLANG_ROOT, set THIRD_PARTY_ROOT to a bundle containing slang-2026.8-windows-x86_64, or place Slang under video-io-3rdParty or 3rdParty.";
return false;
}

View File

@@ -15,6 +15,8 @@ public:
const std::filesystem::path& patchedGlslPath,
unsigned maxTemporalHistoryFrames);
static bool FindSlangCompilerPath(const std::filesystem::path& repoRoot, std::filesystem::path& compilerPath, std::string& error);
bool BuildPassFragmentShaderSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& fragmentShaderSource, std::string& error) const;
private:

View File

@@ -137,6 +137,11 @@ add_video_shader_test(ShaderSlangValidationTests
"${TEST_DIR}/ShaderSlangValidationTests.cpp"
)
add_video_shader_test(ShaderCompilerLookupTests
"${SRC_DIR}/shader/ShaderCompiler.cpp"
"${TEST_DIR}/ShaderCompilerLookupTests.cpp"
)
add_video_shader_test(Std140BufferTests
"${TEST_DIR}/Std140BufferTests.cpp"
)
@@ -169,5 +174,5 @@ set_tests_properties(RenderCadenceCompositorLoggerTests PROPERTIES
)
set_tests_properties(ShaderSlangValidationTests PROPERTIES
ENVIRONMENT "SLANG_ROOT=${SLANG_ROOT}"
ENVIRONMENT "SLANG_ROOT=${SLANG_ROOT};THIRD_PARTY_ROOT=${THIRD_PARTY_ROOT}"
)

View File

@@ -0,0 +1,203 @@
#include "ShaderCompiler.h"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
namespace
{
int gFailures = 0;
void Expect(bool condition, const std::string& message)
{
if (condition)
return;
++gFailures;
std::cerr << "FAIL: " << message << "\n";
}
std::filesystem::path MakeTestRoot(const std::string& name)
{
const std::filesystem::path root =
std::filesystem::temp_directory_path() /
("shader-compiler-lookup-" + name + "-" + std::to_string(std::filesystem::file_time_type::clock::now().time_since_epoch().count()));
std::filesystem::create_directories(root);
return root;
}
void WriteFakeSlangCompiler(const std::filesystem::path& path)
{
std::filesystem::create_directories(path.parent_path());
std::ofstream output(path, std::ios::binary);
output << "not a real compiler";
}
std::string ReadEnvironmentVariable(const char* name)
{
#if defined(_MSC_VER)
char* value = nullptr;
std::size_t size = 0;
if (_dupenv_s(&value, &size, name) != 0 || value == nullptr)
return std::string();
std::string text(value);
std::free(value);
return text;
#else
const char* value = std::getenv(name);
return value ? std::string(value) : std::string();
#endif
}
void WriteEnvironmentVariable(const char* name, const std::string& value)
{
#if defined(_MSC_VER)
_putenv_s(name, value.c_str());
#else
if (value.empty())
unsetenv(name);
else
setenv(name, value.c_str(), 1);
#endif
}
class ScopedEnvironmentVariable
{
public:
ScopedEnvironmentVariable(const char* name, const std::string& value) :
mName(name),
mOriginal(ReadEnvironmentVariable(name))
{
WriteEnvironmentVariable(name, value);
}
~ScopedEnvironmentVariable()
{
WriteEnvironmentVariable(mName.c_str(), mOriginal);
}
ScopedEnvironmentVariable(const ScopedEnvironmentVariable&) = delete;
ScopedEnvironmentVariable& operator=(const ScopedEnvironmentVariable&) = delete;
private:
std::string mName;
std::string mOriginal;
};
bool SamePath(const std::filesystem::path& left, const std::filesystem::path& right)
{
std::error_code leftError;
std::error_code rightError;
return std::filesystem::weakly_canonical(left, leftError) == std::filesystem::weakly_canonical(right, rightError);
}
void TestSlangRootWinsOverThirdPartyRoot()
{
const std::filesystem::path root = MakeTestRoot("slang-root");
const std::filesystem::path expected = root / "env-slang" / "bin" / "slangc.exe";
const std::filesystem::path thirdPartyCompiler = root / "third-party" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe";
WriteFakeSlangCompiler(expected);
WriteFakeSlangCompiler(thirdPartyCompiler);
ScopedEnvironmentVariable slangRoot("SLANG_ROOT", (root / "env-slang").string());
ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", (root / "third-party").string());
std::filesystem::path actual;
std::string error;
Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "SLANG_ROOT compiler is found");
Expect(SamePath(actual, expected), "SLANG_ROOT has priority over THIRD_PARTY_ROOT");
std::filesystem::remove_all(root);
}
void TestThirdPartyRootUsesCMakeBundleLayout()
{
const std::filesystem::path root = MakeTestRoot("third-party-root");
const std::filesystem::path thirdPartyRootPath = root / "video-io-3rdParty";
const std::filesystem::path expected = thirdPartyRootPath / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe";
WriteFakeSlangCompiler(expected);
ScopedEnvironmentVariable slangRoot("SLANG_ROOT", "");
ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", thirdPartyRootPath.string());
std::filesystem::path actual;
std::string error;
Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "THIRD_PARTY_ROOT compiler is found");
Expect(SamePath(actual, expected), "THIRD_PARTY_ROOT matches CMake's Slang bundle layout");
std::filesystem::remove_all(root);
}
void TestRepoVideoIoBundleWinsOverLegacyRepoBundle()
{
const std::filesystem::path root = MakeTestRoot("repo-video-io");
const std::filesystem::path expected = root / "video-io-3rdParty" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe";
const std::filesystem::path legacy = root / "3rdParty" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe";
WriteFakeSlangCompiler(expected);
WriteFakeSlangCompiler(legacy);
ScopedEnvironmentVariable slangRoot("SLANG_ROOT", "");
ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", "");
std::filesystem::path actual;
std::string error;
Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "repo video-io-3rdParty compiler is found");
Expect(SamePath(actual, expected), "repo video-io-3rdParty has priority over repo 3rdParty");
std::filesystem::remove_all(root);
}
void TestPackagedSlangFolderIsAccepted()
{
const std::filesystem::path root = MakeTestRoot("packaged");
const std::filesystem::path expected = root / "3rdParty" / "slang" / "bin" / "slangc.exe";
WriteFakeSlangCompiler(expected);
ScopedEnvironmentVariable slangRoot("SLANG_ROOT", "");
ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", "");
std::filesystem::path actual;
std::string error;
Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "packaged 3rdParty/slang compiler is found");
Expect(SamePath(actual, expected), "packaged Slang install layout is accepted");
std::filesystem::remove_all(root);
}
void TestLegacyThirdPartyScanStillAcceptsVersionedFolders()
{
const std::filesystem::path root = MakeTestRoot("legacy-scan");
const std::filesystem::path expected = root / "3rdParty" / "slang-custom-build" / "bin" / "slangc.exe";
WriteFakeSlangCompiler(expected);
ScopedEnvironmentVariable slangRoot("SLANG_ROOT", "");
ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", "");
std::filesystem::path actual;
std::string error;
Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "legacy 3rdParty scan finds versioned Slang folders");
Expect(SamePath(actual, expected), "legacy scan returns the discovered compiler path");
std::filesystem::remove_all(root);
}
}
int main()
{
TestSlangRootWinsOverThirdPartyRoot();
TestThirdPartyRootUsesCMakeBundleLayout();
TestRepoVideoIoBundleWinsOverLegacyRepoBundle();
TestPackagedSlangFolderIsAccepted();
TestLegacyThirdPartyScanStillAcceptsVersionedFolders();
if (gFailures != 0)
{
std::cerr << gFailures << " ShaderCompiler lookup test failure(s).\n";
return 1;
}
std::cout << "ShaderCompiler lookup tests passed.\n";
return 0;
}