Refactor
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-06 09:28:46 +10:00
parent 437199f3f0
commit 0bfffa6552
41 changed files with 1592 additions and 1368 deletions

View File

@@ -0,0 +1,302 @@
#include "stdafx.h"
#include "ShaderCompiler.h"
#include "NativeHandles.h"
#include <fstream>
#include <cctype>
#include <regex>
#include <sstream>
#include <vector>
namespace
{
std::string ReplaceAll(std::string text, const std::string& from, const std::string& to)
{
std::size_t offset = 0;
while ((offset = text.find(from, offset)) != std::string::npos)
{
text.replace(offset, from.length(), to);
offset += to.length();
}
return text;
}
std::string SlangCBufferTypeForParameter(ShaderParameterType type)
{
switch (type)
{
case ShaderParameterType::Float: return "float";
case ShaderParameterType::Vec2: return "float2";
case ShaderParameterType::Color: return "float4";
case ShaderParameterType::Boolean: return "bool";
case ShaderParameterType::Enum: return "int";
case ShaderParameterType::Text: return "";
}
return "float";
}
std::string CapitalizeIdentifier(const std::string& identifier)
{
if (identifier.empty())
return identifier;
std::string text = identifier;
text[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(text[0])));
return text;
}
std::string BuildParameterUniforms(const std::vector<ShaderParameterDefinition>& parameters)
{
std::ostringstream source;
for (const ShaderParameterDefinition& definition : parameters)
{
if (definition.type == ShaderParameterType::Text)
continue;
source << "\t" << SlangCBufferTypeForParameter(definition.type) << " " << definition.id << ";\n";
}
return source.str();
}
std::string BuildHistorySamplerDeclarations(const std::string& samplerPrefix, unsigned historyLength)
{
std::ostringstream source;
for (unsigned index = 0; index < historyLength; ++index)
source << "Sampler2D<float4> " << samplerPrefix << index << ";\n";
return source.str();
}
std::string BuildTextureSamplerDeclarations(const std::vector<ShaderTextureAsset>& textureAssets)
{
std::ostringstream source;
for (const ShaderTextureAsset& textureAsset : textureAssets)
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
if (!textureAssets.empty())
source << "\n";
return source.str();
}
std::string BuildTextSamplerDeclarations(const std::vector<ShaderParameterDefinition>& parameters)
{
std::ostringstream source;
for (const ShaderParameterDefinition& definition : parameters)
{
if (definition.type != ShaderParameterType::Text)
continue;
source << "Sampler2D<float4> " << definition.id << "Texture;\n";
}
if (source.tellp() > 0)
source << "\n";
return source.str();
}
std::string BuildTextHelpers(const std::vector<ShaderParameterDefinition>& parameters)
{
std::ostringstream source;
for (const ShaderParameterDefinition& definition : parameters)
{
if (definition.type != ShaderParameterType::Text)
continue;
const std::string suffix = CapitalizeIdentifier(definition.id);
source
<< "float sample" << suffix << "(float2 uv)\n"
<< "{\n"
<< "\tif (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)\n"
<< "\t\treturn 0.0;\n"
<< "\treturn " << definition.id << "Texture.Sample(uv).r;\n"
<< "}\n\n"
<< "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n"
<< "{\n"
<< "\tfloat alpha = sample" << suffix << "(uv) * fillColor.a;\n"
<< "\treturn float4(fillColor.rgb * alpha, alpha);\n"
<< "}\n\n";
}
return source.str();
}
std::string BuildHistorySwitchCases(const std::string& samplerPrefix, unsigned historyLength)
{
std::ostringstream source;
for (unsigned index = 0; index < historyLength; ++index)
source << "\tcase " << index << ": return " << samplerPrefix << index << ".Sample(tc);\n";
return source.str();
}
}
ShaderCompiler::ShaderCompiler(
const std::filesystem::path& repoRoot,
const std::filesystem::path& wrapperPath,
const std::filesystem::path& generatedGlslPath,
const std::filesystem::path& patchedGlslPath,
unsigned maxTemporalHistoryFrames)
: mRepoRoot(repoRoot),
mWrapperPath(wrapperPath),
mGeneratedGlslPath(generatedGlslPath),
mPatchedGlslPath(patchedGlslPath),
mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
{
}
bool ShaderCompiler::BuildLayerFragmentShaderSource(const ShaderPackage& shaderPackage, std::string& fragmentShaderSource, std::string& error) const
{
std::string wrapperSource;
if (!BuildWrapperSlangSource(shaderPackage, wrapperSource, error))
return false;
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
return false;
if (!RunSlangCompiler(mWrapperPath, mGeneratedGlslPath, error))
return false;
fragmentShaderSource = ReadTextFile(mGeneratedGlslPath, error);
if (fragmentShaderSource.empty())
return false;
if (!PatchGeneratedGlsl(fragmentShaderSource, error))
return false;
if (!WriteTextFile(mPatchedGlslPath, fragmentShaderSource, error))
return false;
return true;
}
bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, std::string& wrapperSource, std::string& error) const
{
const std::filesystem::path templatePath = mRepoRoot / "runtime" / "templates" / "shader_wrapper.slang.in";
wrapperSource = ReadTextFile(templatePath, error);
if (wrapperSource.empty())
return false;
wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters));
const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", shaderPackage.shaderPath.generic_string());
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", shaderPackage.entryPoint + "(context)");
return true;
}
bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
{
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;
return true;
}
}
error = "Could not find slangc.exe under 3rdParty.";
return false;
}
bool ShaderCompiler::RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const
{
std::filesystem::path compilerPath;
if (!FindSlangCompiler(compilerPath, error))
return false;
std::string commandLine = "\"" + compilerPath.string() + "\" \"" + wrapperPath.string()
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + outputPath.string() + "\"";
STARTUPINFOA startupInfo = {};
PROCESS_INFORMATION processInfo = {};
startupInfo.cb = sizeof(startupInfo);
std::vector<char> mutableCommandLine(commandLine.begin(), commandLine.end());
mutableCommandLine.push_back('\0');
if (!CreateProcessA(NULL, mutableCommandLine.data(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, mRepoRoot.string().c_str(), &startupInfo, &processInfo))
{
error = "Failed to launch slangc.exe.";
return false;
}
UniqueHandle processHandle(processInfo.hProcess);
UniqueHandle threadHandle(processInfo.hThread);
WaitForSingleObject(processHandle.get(), INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(processHandle.get(), &exitCode);
if (exitCode != 0)
{
error = "slangc.exe returned a non-zero exit code while compiling the active shader package.";
return false;
}
return true;
}
bool ShaderCompiler::PatchGeneratedGlsl(std::string& shaderText, std::string& error) const
{
if (shaderText.find("#version 450") == std::string::npos)
{
error = "Generated GLSL did not include the expected version header.";
return false;
}
shaderText = ReplaceAll(shaderText, "#version 450", "#version 430 core");
shaderText = std::regex_replace(shaderText, std::regex(R"(#extension GL_EXT_samplerless_texture_functions : require\r?\n)"), "");
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) uniform;\r?\n)"), "");
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) buffer;\r?\n)"), "");
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(location = 0\)\s*in vec2 ([A-Za-z0-9_]+);)"), "in vec2 vTexCoord;");
shaderText = ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
std::smatch match;
std::regex outRegex(R"(layout\(location = 0\)\s*out vec4 ([A-Za-z0-9_]+);)");
if (std::regex_search(shaderText, match, outRegex))
{
const std::string outputName = match[1].str();
shaderText = std::regex_replace(shaderText, outRegex, "layout(location = 0) out vec4 fragColor;");
shaderText = ReplaceAll(shaderText, outputName + " =", "fragColor =");
}
return true;
}
std::string ShaderCompiler::ReadTextFile(const std::filesystem::path& path, std::string& error) const
{
std::ifstream input(path, std::ios::binary);
if (!input)
{
error = "Could not open file: " + path.string();
return std::string();
}
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
bool ShaderCompiler::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
{
std::error_code fsError;
std::filesystem::create_directories(path.parent_path(), fsError);
std::ofstream output(path, std::ios::binary);
if (!output)
{
error = "Could not write file: " + path.string();
return false;
}
output << contents;
return output.good();
}