diff --git a/engine/assets/builtin/shaders/unlit/unlit.shader b/engine/assets/builtin/shaders/unlit/unlit.shader index 56ba188c..24973227 100644 --- a/engine/assets/builtin/shaders/unlit/unlit.shader +++ b/engine/assets/builtin/shaders/unlit/unlit.shader @@ -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 } } diff --git a/engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp b/engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp index 4ac64df5..1ca123d7 100644 --- a/engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp +++ b/engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp @@ -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 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(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& 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 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) {