rendering: split shader loader authoring modes

This commit is contained in:
2026-04-06 17:21:40 +08:00
parent 9015b461bb
commit 806ef74226
2 changed files with 865 additions and 43 deletions

View File

@@ -237,7 +237,7 @@ TEST(ShaderLoader, LoadShaderManifestBuildsMultiPassBackendVariants) {
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityLikeShaderAuthoringBuildsRuntimeContract) {
TEST(ShaderLoader, LoadLegacyBackendSplitShaderAuthoringBuildsRuntimeContract) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_test";
@@ -382,6 +382,196 @@ TEST(ShaderLoader, LoadUnityLikeShaderAuthoringBuildsRuntimeContract) {
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVariants) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_test";
const fs::path includeRoot = shaderRoot / "shaderlib";
const fs::path shaderPath = shaderRoot / "single_source.shader";
fs::remove_all(shaderRoot);
fs::create_directories(includeRoot);
WriteTextFile(
includeRoot / "shared.hlsl",
R"(// XC_SINGLE_SOURCE_SHARED_INCLUDE
#define XC_SINGLE_SOURCE_SHARED_VALUE 1
)");
WriteTextFile(
shaderPath,
R"(Shader "SingleSourceLit"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)]
}
HLSLINCLUDE
#include "shaderlib/shared.hlsl"
struct VSInput
{
float3 positionOS : POSITION;
};
ENDHLSL
SubShader
{
Tags { "Queue" = "Geometry" }
LOD 200
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "ForwardLit" }
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma shader_feature_local _ XC_ALPHA_TEST
float4 Vert(VSInput input) : SV_POSITION
{
return float4(input.positionOS, 1.0);
}
float4 Frag() : SV_TARGET
{
return float4(1.0, 0.0, 0.0, 1.0); // XC_SINGLE_SOURCE_FRAG_BODY
}
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
ASSERT_TRUE(shader->IsValid());
EXPECT_EQ(shader->GetName(), "SingleSourceLit");
ASSERT_EQ(shader->GetProperties().Size(), 1u);
ASSERT_EQ(shader->GetPassCount(), 1u);
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->tags.Size(), 2u);
EXPECT_EQ(pass->tags[0].name, "Queue");
EXPECT_EQ(pass->tags[0].value, "Geometry");
EXPECT_EQ(pass->tags[1].name, "LightMode");
EXPECT_EQ(pass->tags[1].value, "ForwardLit");
EXPECT_TRUE(pass->resources.Empty());
ASSERT_EQ(pass->variants.Size(), 2u);
const ShaderStageVariant* vertexVariant =
shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12);
ASSERT_NE(vertexVariant, nullptr);
EXPECT_EQ(vertexVariant->backend, ShaderBackend::Generic);
EXPECT_EQ(vertexVariant->language, ShaderLanguage::HLSL);
EXPECT_EQ(vertexVariant->entryPoint, "Vert");
EXPECT_EQ(vertexVariant->profile, "vs_5_0");
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("#include \"shaderlib/shared.hlsl\""), std::string::npos);
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("XC_SINGLE_SOURCE_FRAG_BODY"), std::string::npos);
const ShaderStageVariant* fragmentVariant =
shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12);
ASSERT_NE(fragmentVariant, nullptr);
EXPECT_EQ(fragmentVariant->backend, ShaderBackend::Generic);
EXPECT_EQ(fragmentVariant->language, ShaderLanguage::HLSL);
EXPECT_EQ(fragmentVariant->entryPoint, "Frag");
EXPECT_EQ(fragmentVariant->profile, "ps_5_0");
Array<String> dependencies;
ASSERT_TRUE(loader.CollectSourceDependencies(shaderPath.string().c_str(), dependencies));
ASSERT_EQ(dependencies.Size(), 1u);
EXPECT_EQ(
fs::path(dependencies[0].CStr()).lexically_normal(),
(includeRoot / "shared.hlsl").lexically_normal());
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringRejectsBackendPragma) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_reject_backend";
const fs::path shaderPath = shaderRoot / "invalid_backend.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "InvalidBackend"
{
SubShader
{
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#pragma backend D3D12 HLSL "forward.vs.hlsl" "forward.ps.hlsl"
float4 Vert() : SV_POSITION { return 0; }
float4 Frag() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
EXPECT_FALSE(result);
EXPECT_NE(std::string(result.errorMessage.CStr()).find("must not use #pragma backend"), std::string::npos);
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringRejectsResourcesBlock) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_reject_resources";
const fs::path shaderPath = shaderRoot / "invalid_resources.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "InvalidResources"
{
SubShader
{
Pass
{
Name "ForwardLit"
Resources
{
MaterialConstants (ConstantBuffer, 0, 0)
}
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
float4 Vert() : SV_POSITION { return 0; }
float4 Frag() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
EXPECT_FALSE(result);
EXPECT_NE(std::string(result.errorMessage.CStr()).find("must not declare Resources blocks"), std::string::npos);
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, ResourceManagerLoadsShaderManifestRelativeToResourceRoot) {
namespace fs = std::filesystem;