Fix Vulkan unlit single-source HLSL compilation

This commit is contained in:
2026-04-07 02:37:23 +08:00
parent 6c90bb4eca
commit 503ffbc4ff
2 changed files with 199 additions and 12 deletions

View File

@@ -11,19 +11,56 @@ Shader "Builtin Unlit"
{
Name "Unlit"
Tags { "LightMode" = "Unlit" }
Resources
{
PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)]
MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)]
BaseColorTexture (Texture2D, 2, 0) [Semantic(BaseColorTexture)]
LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)]
}
Cull Back
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma target 4.5
#pragma vertex MainVS
#pragma fragment MainPS
#pragma backend D3D12 HLSL "unlit.vs.hlsl" "unlit.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "unlit.vert.glsl" "unlit.frag.glsl"
#pragma backend Vulkan GLSL "unlit.vert.vk.glsl" "unlit.frag.vk.glsl"
cbuffer PerObjectConstants : register(b0)
{
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gNormalMatrix;
};
cbuffer MaterialConstants : register(b1)
{
float4 gBaseColorFactor;
};
Texture2D BaseColorTexture : register(t0);
SamplerState LinearClampSampler : register(s0);
struct VSInput
{
float3 position : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct PSInput
{
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
PSInput MainVS(VSInput input)
{
PSInput output;
const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f));
const float4 positionVS = mul(gViewMatrix, positionWS);
output.position = mul(gProjectionMatrix, positionVS);
output.texcoord = input.texcoord;
return output;
}
float4 MainPS(PSInput input) : SV_TARGET
{
return BaseColorTexture.Sample(LinearClampSampler, input.texcoord) * gBaseColorFactor;
}
ENDHLSL
}
}

View File

@@ -329,6 +329,49 @@ bool FindGlslangValidator(std::wstring& outPath) {
return false;
}
bool FindDxcCompiler(std::wstring& outPath) {
const std::wstring vulkanSdk = GetEnvironmentVariableValue(L"VULKAN_SDK");
if (!vulkanSdk.empty()) {
const std::filesystem::path sdkCandidate = std::filesystem::path(vulkanSdk) / L"Bin" / L"dxc.exe";
if (std::filesystem::exists(sdkCandidate)) {
outPath = sdkCandidate.wstring();
return true;
}
}
std::vector<std::filesystem::path> candidates;
const std::filesystem::path sdkRoot = L"D:/VulkanSDK";
if (std::filesystem::exists(sdkRoot) && std::filesystem::is_directory(sdkRoot)) {
for (const auto& entry : std::filesystem::directory_iterator(sdkRoot)) {
if (!entry.is_directory()) {
continue;
}
const std::filesystem::path candidate = entry.path() / L"Bin" / L"dxc.exe";
if (std::filesystem::exists(candidate)) {
candidates.push_back(candidate);
}
}
}
if (!candidates.empty()) {
std::sort(candidates.begin(), candidates.end(), [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) {
return lhs.wstring() > rhs.wstring();
});
outPath = candidates.front().wstring();
return true;
}
wchar_t buffer[MAX_PATH] = {};
const DWORD length = SearchPathW(nullptr, L"dxc.exe", nullptr, MAX_PATH, buffer, nullptr);
if (length > 0 && length < MAX_PATH) {
outPath = buffer;
return true;
}
return false;
}
bool FindSpirvCross(std::wstring& outPath) {
const std::wstring vulkanSdk = GetEnvironmentVariableValue(L"VULKAN_SDK");
if (!vulkanSdk.empty()) {
@@ -486,8 +529,35 @@ std::string InjectMacrosIntoSource(const std::string& source, const std::vector<
macroBlock += '\n';
}
if (source.rfind("#version", 0) == 0) {
const size_t lineEnd = source.find('\n');
size_t scan = 0;
while (scan < source.size()) {
while (scan < source.size() && std::isspace(static_cast<unsigned char>(source[scan])) != 0) {
++scan;
}
if (scan + 1 < source.size() && source[scan] == '/' && source[scan + 1] == '/') {
const size_t lineEnd = source.find('\n', scan);
if (lineEnd == std::string::npos) {
return source + '\n' + macroBlock;
}
scan = lineEnd + 1;
continue;
}
if (scan + 1 < source.size() && source[scan] == '/' && source[scan + 1] == '*') {
const size_t commentEnd = source.find("*/", scan + 2);
if (commentEnd == std::string::npos) {
break;
}
scan = commentEnd + 2;
continue;
}
break;
}
if (source.compare(scan, 8, "#version") == 0) {
const size_t lineEnd = source.find('\n', scan);
if (lineEnd != std::string::npos) {
std::string result;
result.reserve(source.size() + macroBlock.size());
@@ -650,6 +720,86 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
const std::string& entryPoint,
std::vector<uint32_t>& spirvWords,
std::string* errorMessage) {
auto tryCompileWithDxc = [&]() -> bool {
if (targetEnvironment != SpirvTargetEnvironment::Vulkan) {
return false;
}
std::wstring dxcPath;
if (!FindDxcCompiler(dxcPath)) {
return false;
}
std::wstring tempSourcePath;
std::wstring tempOutputPath;
if (!CreateTemporaryPath(ShaderSourceExtension(type, true).c_str(), tempSourcePath) ||
!CreateTemporaryPath(L".spv", tempOutputPath)) {
if (errorMessage != nullptr) {
*errorMessage = "Failed to allocate temporary shader compiler files.";
}
return false;
}
const std::string sourceWithMacros = InjectMacrosIntoSource(sourceText, desc.macros);
const bool wroteSource = WriteTextFile(tempSourcePath, sourceWithMacros);
if (!wroteSource) {
DeleteFileW(tempSourcePath.c_str());
DeleteFileW(tempOutputPath.c_str());
if (errorMessage != nullptr) {
*errorMessage = "Failed to write temporary HLSL shader file.";
}
return false;
}
const std::string profile = NarrowAscii(desc.profile);
const std::wstring targetProfile =
WidenAscii(profile.empty() ? "ps_6_0" : profile.c_str());
const std::wstring arguments =
L"-spirv -Zpc -fvk-use-dx-layout -fspv-target-env=vulkan1.0 " +
std::wstring(L"-T ") + targetProfile +
L" -E " + WidenAscii(entryPoint.c_str()) +
L" -Fo \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
DWORD exitCode = 0;
std::string compilerOutput;
const bool ranProcess = RunProcessAndCapture(dxcPath, arguments, exitCode, compilerOutput);
std::vector<uint8_t> bytes;
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
DeleteFileW(tempSourcePath.c_str());
DeleteFileW(tempOutputPath.c_str());
if (!ranProcess) {
return false;
}
if (!loadedOutput) {
if (errorMessage != nullptr) {
*errorMessage = compilerOutput.empty()
? std::string("dxc.exe failed to compile HLSL to SPIR-V for Vulkan.")
: compilerOutput;
}
return false;
}
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
if (errorMessage != nullptr) {
*errorMessage = "Compiled SPIR-V payload size is invalid.";
}
return false;
}
spirvWords.resize(bytes.size() / sizeof(uint32_t));
std::memcpy(spirvWords.data(), bytes.data(), bytes.size());
return true;
};
if (tryCompileWithDxc()) {
return true;
}
std::wstring validatorPath;
if (!FindGlslangValidator(validatorPath)) {
if (errorMessage != nullptr) {