Formalize builtin fullscreen shaders

This commit is contained in:
2026-04-07 04:30:26 +08:00
parent 7f0d1f0b08
commit 5bfe484f5d
26 changed files with 746 additions and 713 deletions

View File

@@ -455,7 +455,7 @@ TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesShadowCasterAlphaVariant
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderDeclaresExplicitEnvironmentResourceContract) {
TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderUsesUnityStyleSingleSourceContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath());
ASSERT_TRUE(result);
@@ -466,7 +466,11 @@ TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderDeclaresExplicitEnvironment
const ShaderPass* pass = shader->FindPass("Skybox");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->resources.Size(), 5u);
EXPECT_TRUE(pass->resources.Empty());
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
const ShaderPropertyDesc* tint = shader->FindProperty("_Tint");
ASSERT_NE(tint, nullptr);
@@ -493,35 +497,37 @@ TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderDeclaresExplicitEnvironment
EXPECT_EQ(cubemap->type, ShaderPropertyType::TextureCube);
EXPECT_EQ(cubemap->semantic, "SkyboxTexture");
EXPECT_EQ(pass->resources[0].semantic, "Environment");
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[0].set, 0u);
EXPECT_EQ(pass->resources[0].binding, 0u);
delete shader;
}
EXPECT_EQ(pass->resources[1].semantic, "Material");
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[1].set, 1u);
EXPECT_EQ(pass->resources[1].binding, 0u);
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitSkyboxShaderContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
EXPECT_EQ(pass->resources[2].semantic, "SkyboxPanoramicTexture");
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D);
EXPECT_EQ(pass->resources[2].set, 2u);
EXPECT_EQ(pass->resources[2].binding, 0u);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
EXPECT_EQ(pass->resources[3].semantic, "SkyboxTexture");
EXPECT_EQ(pass->resources[3].type, ShaderResourceType::TextureCube);
EXPECT_EQ(pass->resources[3].set, 3u);
EXPECT_EQ(pass->resources[3].binding, 0u);
const ShaderPass* pass = shader->FindPass("Skybox");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources[4].semantic, "LinearClampSampler");
EXPECT_EQ(pass->resources[4].type, ShaderResourceType::Sampler);
EXPECT_EQ(pass->resources[4].set, 4u);
EXPECT_EQ(pass->resources[4].binding, 0u);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 5u);
EXPECT_TRUE(plan.environment.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.skyboxPanoramicTexture.IsValid());
EXPECT_TRUE(plan.skyboxTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 5u);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderDeclaresExplicitFullscreenResourceContract) {
TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderUsesUnityStyleSingleSourceContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
ASSERT_TRUE(result);
@@ -532,7 +538,11 @@ TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderDeclaresExplicitFullscr
const ShaderPass* pass = shader->FindPass("FinalColor");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->resources.Size(), 3u);
EXPECT_TRUE(pass->resources.Empty());
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always);
const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale");
ASSERT_NE(colorScale, nullptr);
@@ -550,20 +560,307 @@ TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderDeclaresExplicitFullscr
ASSERT_NE(toneMappingMode, nullptr);
EXPECT_EQ(toneMappingMode->type, ShaderPropertyType::Float);
EXPECT_STREQ(pass->resources[0].name.CStr(), "FinalColorConstants");
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[0].set, 0u);
EXPECT_EQ(pass->resources[0].binding, 0u);
delete shader;
}
EXPECT_STREQ(pass->resources[1].name.CStr(), "SourceColorTexture");
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::Texture2D);
EXPECT_EQ(pass->resources[1].set, 1u);
EXPECT_EQ(pass->resources[1].binding, 0u);
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitFinalColorShaderContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
EXPECT_STREQ(pass->resources[2].name.CStr(), "LinearClampSampler");
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Sampler);
EXPECT_EQ(pass->resources[2].set, 2u);
EXPECT_EQ(pass->resources[2].binding, 0u);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("FinalColor");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 3u);
EXPECT_TRUE(plan.passConstants.IsValid());
EXPECT_TRUE(plan.sourceColorTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 3u);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinColorScalePostProcessShaderUsesUnityStyleSingleSourceContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("ColorScale");
ASSERT_NE(pass, nullptr);
EXPECT_TRUE(pass->resources.Empty());
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always);
const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale");
ASSERT_NE(colorScale, nullptr);
EXPECT_EQ(colorScale->type, ShaderPropertyType::Color);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitColorScalePostProcessShaderContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("ColorScale");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 3u);
EXPECT_TRUE(plan.passConstants.IsValid());
EXPECT_TRUE(plan.sourceColorTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 3u);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesUnityStyleFinalColorBindingsToDescriptorSpaces) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("FinalColor");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
"FinalColor",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment,
d3d12CompileDesc);
const std::string d3d12Source(
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
d3d12CompileDesc.source.size());
EXPECT_NE(d3d12Source.find("cbuffer FinalColorConstants : register(b0)"), std::string::npos);
EXPECT_NE(d3d12Source.find("Texture2D gSourceColorTexture : register(t0)"), std::string::npos);
EXPECT_NE(d3d12Source.find("SamplerState gLinearClampSampler : register(s0)"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
"FinalColor",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::Vulkan);
ASSERT_NE(vulkanFragment, nullptr);
const std::string runtimeSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment);
EXPECT_NE(
runtimeSource.find("cbuffer FinalColorConstants : register(b0, space0)"),
std::string::npos);
EXPECT_NE(
runtimeSource.find("Texture2D gSourceColorTexture : register(t0, space1)"),
std::string::npos);
EXPECT_NE(
runtimeSource.find("SamplerState gLinearClampSampler : register(s0, space2)"),
std::string::npos);
ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment,
vulkanCompileDesc);
const std::string vulkanSource(
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
vulkanCompileDesc.source.size());
EXPECT_NE(vulkanSource.find("ApplyLinearToSRGB"), std::string::npos);
EXPECT_NE(vulkanSource.find("ApplyToneMapping"), std::string::npos);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesFinalColorVariantToCombinedSourceSampler) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("FinalColor");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* openGLFragment = shader->FindVariant(
"FinalColor",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::OpenGL);
ASSERT_NE(openGLFragment, nullptr);
ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::OpenGL,
*openGLFragment,
compileDesc);
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
std::string errorMessage;
ASSERT_TRUE(
XCEngine::RHI::CompileSpirvShader(
compileDesc,
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
spirvShader,
&errorMessage))
<< errorMessage;
std::string glslSource;
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
<< errorMessage;
EXPECT_NE(glslSource.find("uniform sampler2D SPIRV_Cross_Combined"), std::string::npos);
EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos);
EXPECT_NE(glslSource.find("FinalColorConstants"), std::string::npos);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesUnityStyleColorScaleBindingsToDescriptorSpaces) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("ColorScale");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
"ColorScale",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::D3D12,
*d3d12Fragment,
d3d12CompileDesc);
const std::string d3d12Source(
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
d3d12CompileDesc.source.size());
EXPECT_NE(d3d12Source.find("cbuffer PostProcessConstants : register(b0)"), std::string::npos);
EXPECT_NE(d3d12Source.find("Texture2D gSourceColorTexture : register(t0)"), std::string::npos);
EXPECT_NE(d3d12Source.find("SamplerState gLinearClampSampler : register(s0)"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
"ColorScale",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::Vulkan);
ASSERT_NE(vulkanFragment, nullptr);
const std::string runtimeSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment);
EXPECT_NE(
runtimeSource.find("cbuffer PostProcessConstants : register(b0, space0)"),
std::string::npos);
EXPECT_NE(
runtimeSource.find("Texture2D gSourceColorTexture : register(t0, space1)"),
std::string::npos);
EXPECT_NE(
runtimeSource.find("SamplerState gLinearClampSampler : register(s0, space2)"),
std::string::npos);
ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment,
vulkanCompileDesc);
const std::string vulkanSource(
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
vulkanCompileDesc.source.size());
EXPECT_NE(vulkanSource.find("gSourceColorTexture.Sample"), std::string::npos);
EXPECT_NE(vulkanSource.find("gColorScale"), std::string::npos);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesColorScaleVariantToCombinedSourceSampler) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("ColorScale");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* openGLFragment = shader->FindVariant(
"ColorScale",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::OpenGL);
ASSERT_NE(openGLFragment, nullptr);
ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::OpenGL,
*openGLFragment,
compileDesc);
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
std::string errorMessage;
ASSERT_TRUE(
XCEngine::RHI::CompileSpirvShader(
compileDesc,
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
spirvShader,
&errorMessage))
<< errorMessage;
std::string glslSource;
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
<< errorMessage;
EXPECT_NE(glslSource.find("uniform sampler2D SPIRV_Cross_Combined"), std::string::npos);
EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos);
EXPECT_NE(glslSource.find("PostProcessConstants"), std::string::npos);
delete shader;
}