rendering: thread global shader keywords into builtin variants

This commit is contained in:
2026-04-06 20:30:25 +08:00
parent 0761079b4c
commit c318f34f07
14 changed files with 119 additions and 26 deletions

View File

@@ -43,6 +43,9 @@ in vec3 vPositionWS;
layout(location = 0) out vec4 fragColor;
float ComputeShadowAttenuation(vec3 positionWS) {
#ifndef XC_MAIN_LIGHT_SHADOWS
return 1.0;
#else
if (gShadowOptions.x < 0.5) {
return 1.0;
}
@@ -66,6 +69,7 @@ float ComputeShadowAttenuation(vec3 positionWS) {
float receiverDepth = shadowNdc.z * 0.5 + 0.5 - gShadowBiasAndTexelSize.x;
float shadowStrength = clamp(gShadowBiasAndTexelSize.w, 0.0, 1.0);
return receiverDepth <= shadowDepth ? 1.0 : (1.0 - shadowStrength);
#endif
}
float ComputeRangeAttenuation(float distanceSq, float range) {

View File

@@ -45,6 +45,9 @@ layout(location = 2) in vec3 vPositionWS;
layout(location = 0) out vec4 fragColor;
float ComputeShadowAttenuation(vec3 positionWS) {
#ifndef XC_MAIN_LIGHT_SHADOWS
return 1.0;
#else
if (gShadowOptions.x < 0.5) {
return 1.0;
}
@@ -68,6 +71,7 @@ float ComputeShadowAttenuation(vec3 positionWS) {
float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x;
float shadowStrength = clamp(gShadowBiasAndTexelSize.w, 0.0, 1.0);
return receiverDepth <= shadowDepth ? 1.0 : (1.0 - shadowStrength);
#endif
}
float ComputeRangeAttenuation(float distanceSq, float range) {

View File

@@ -45,6 +45,9 @@ struct PSInput {
};
float ComputeShadowAttenuation(float3 positionWS) {
#ifndef XC_MAIN_LIGHT_SHADOWS
return 1.0f;
#else
if (gShadowOptions.x < 0.5f) {
return 1.0f;
}
@@ -68,6 +71,7 @@ float ComputeShadowAttenuation(float3 positionWS) {
const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x;
const float shadowStrength = saturate(gShadowBiasAndTexelSize.w);
return receiverDepth <= shadowDepth ? 1.0f : (1.0f - shadowStrength);
#endif
}
float ComputeRangeAttenuation(float distanceSq, float range) {

View File

@@ -25,6 +25,7 @@ Shader "Builtin Forward Lit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma backend D3D12 HLSL "forward-lit.vs.hlsl" "forward-lit.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "forward-lit.vert.glsl" "forward-lit.frag.glsl"
#pragma backend Vulkan GLSL "forward-lit.vert.vk.glsl" "forward-lit.frag.vk.glsl"

View File

@@ -5,6 +5,7 @@
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
#include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h>
#include <XCEngine/Rendering/FrameData/VisibleRenderItem.h>
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
#include <array>
#include <cstdint>
@@ -84,6 +85,7 @@ struct RenderSceneData {
RenderCameraData cameraData;
RenderEnvironmentData environment;
RenderLightingData lighting;
Resources::ShaderKeywordSet globalShaderKeywords;
std::vector<VisibleRenderItem> visibleItems;
bool HasCamera() const {

View File

@@ -139,7 +139,9 @@ private:
bool CreateResources(const RenderContext& context);
void DestroyResources();
ResolvedShaderPass ResolveSurfaceShaderPass(const Resources::Material* material) const;
ResolvedShaderPass ResolveSurfaceShaderPass(
const RenderSceneData& sceneData,
const Resources::Material* material) const;
bool TryBuildSupportedBindingPlan(
const Resources::ShaderPass& shaderPass,
BuiltinPassResourceBindingPlan& outPlan,
@@ -150,6 +152,7 @@ private:
RHI::RHIPipelineState* GetOrCreatePipelineState(
const RenderContext& context,
const RenderSurface& surface,
const RenderSceneData& sceneData,
const Resources::Material* material);
bool CreateOwnedDescriptorSet(
const BuiltinPassSetLayoutMetadata& setLayout,

View File

@@ -222,12 +222,15 @@ private:
static bool TryResolveSurfacePassType(
const Resources::Material* material,
BuiltinMaterialPass& outPass);
ResolvedShaderPass ResolveSurfaceShaderPass(const Resources::Material* material) const;
ResolvedShaderPass ResolveSurfaceShaderPass(
const RenderSceneData& sceneData,
const Resources::Material* material) const;
PassResourceLayout* GetOrCreatePassResourceLayout(
const RenderContext& context,
const ResolvedShaderPass& resolvedShaderPass);
RHI::RHIPipelineState* GetOrCreatePipelineState(
const RenderContext& context,
const RenderSceneData& sceneData,
const Resources::Material* material);
bool CreateOwnedDescriptorSet(
const BuiltinPassSetLayoutMetadata& setLayout,

View File

@@ -112,5 +112,17 @@ inline bool IsShaderKeywordSubset(
return true;
}
inline ShaderKeywordSet CombineShaderKeywordSets(
const ShaderKeywordSet& first,
const ShaderKeywordSet& second) {
ShaderKeywordSet combined = first;
for (const Containers::String& keyword : second.enabledKeywords) {
combined.enabledKeywords.PushBack(keyword);
}
NormalizeShaderKeywordSetInPlace(combined);
return combined;
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -45,6 +45,17 @@ std::unique_ptr<RenderPipeline> CreatePipelineFromAsset(
return std::make_unique<Pipelines::BuiltinForwardPipeline>();
}
Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords(
const RenderSceneData& sceneData) {
Resources::ShaderKeywordSet keywords = {};
if (sceneData.lighting.HasMainDirectionalShadow()) {
keywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
}
Resources::NormalizeShaderKeywordSetInPlace(keywords);
return keywords;
}
bool InitializeStandalonePass(
RenderPass* pass,
const RenderContext& context) {
@@ -563,6 +574,7 @@ bool CameraRenderer::BuildSceneDataForRequest(
outSceneData.lighting.mainDirectionalShadow =
BuildDirectionalShadowData(request.directionalShadow, shadowMapView);
}
outSceneData.globalShaderKeywords = BuildSceneGlobalShaderKeywords(outSceneData);
outSceneData.cameraData.clearFlags = request.clearFlags;
outSceneData.environment = BuildEnvironmentData(request);

View File

@@ -20,6 +20,14 @@ namespace Passes {
namespace {
Resources::ShaderKeywordSet ResolvePassKeywordSet(
const RenderSceneData& sceneData,
const Resources::Material* material) {
return Resources::CombineShaderKeywordSets(
sceneData.globalShaderKeywords,
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
}
bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) {
return bindingPlan.perObject.IsValid() &&
bindingPlan.bindings.Size() == 1u &&
@@ -57,6 +65,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
RHI::RHIPipelineLayout* pipelineLayout,
const Resources::Shader& shader,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet,
const Resources::Material* material,
const RenderSurface& surface,
const RHI::InputLayoutDesc& inputLayout) {
@@ -81,8 +90,6 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
const Resources::ShaderKeywordSet keywordSet =
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet();
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
@@ -154,12 +161,13 @@ void BuiltinDepthStylePassBase::DestroyResources() {
}
BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::ResolveSurfaceShaderPass(
const RenderSceneData& sceneData,
const Resources::Material* material) const {
ResolvedShaderPass resolved = {};
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
auto tryResolveFromShader =
[this, backend, &resolved](
[this, backend, &resolved, &sceneData](
const Resources::Shader* shader,
const Resources::Material* ownerMaterial) -> bool {
if (shader == nullptr) {
@@ -167,8 +175,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
}
const bool shaderHasExplicitBuiltinMetadata = ShaderHasExplicitBuiltinMetadata(*shader);
const Resources::ShaderKeywordSet keywordSet =
ownerMaterial != nullptr ? ownerMaterial->GetKeywordSet() : Resources::ShaderKeywordSet();
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, ownerMaterial);
auto tryAcceptPass =
[this, shader, &resolved](const Resources::ShaderPass& shaderPass) -> bool {
@@ -333,8 +340,10 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC
RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
const RenderContext& context,
const RenderSurface& surface,
const RenderSceneData& sceneData,
const Resources::Material* material) {
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return nullptr;
}
@@ -349,9 +358,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature =
::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
pipelineKey.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface);
pipelineKey.renderTargetFormat = static_cast<uint32_t>(ResolveSurfaceColorFormat(surface));
pipelineKey.depthStencilFormat = static_cast<uint32_t>(ResolveSurfaceDepthFormat(surface));
@@ -366,6 +373,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
passLayout->pipelineLayout,
*resolvedShaderPass.shader,
resolvedShaderPass.passName,
keywordSet,
material,
surface,
BuildCommonInputLayout());
@@ -480,7 +488,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
}
const Resources::Material* material = ResolveMaterial(visibleItem);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
@@ -502,7 +510,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
return false;
}
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, material);
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
if (pipelineState == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,

View File

@@ -506,7 +506,7 @@ bool BuiltinForwardPipeline::DrawVisibleItems(
continue;
}
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material);
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, sceneData, material);
if (pipelineState == nullptr) {
continue;
}

View File

@@ -23,14 +23,22 @@ namespace {
constexpr float kForwardAmbientIntensity = 0.28f;
constexpr float kSpotInnerAngleRatio = 0.8f;
Resources::ShaderKeywordSet ResolvePassKeywordSet(
const RenderSceneData& sceneData,
const Resources::Material* material) {
return Resources::CombineShaderKeywordSets(
sceneData.globalShaderKeywords,
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
}
const Resources::ShaderPass* FindCompatibleSurfacePass(
const Resources::Shader& shader,
const RenderSceneData& sceneData,
const Resources::Material* material,
BuiltinMaterialPass pass,
Resources::ShaderBackend backend) {
const bool shaderHasExplicitBuiltinMetadata = ShaderHasExplicitBuiltinMetadata(shader);
const Resources::ShaderKeywordSet keywordSet =
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet();
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
if (ShaderPassMatchesBuiltinPass(shaderPass, pass) &&
@@ -102,6 +110,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
RHI::RHIPipelineLayout* pipelineLayout,
const Resources::Shader& shader,
const Containers::String& passName,
const Resources::ShaderKeywordSet& keywordSet,
const Resources::Material* material) {
RHI::GraphicsPipelineDesc pipelineDesc = {};
pipelineDesc.pipelineLayout = pipelineLayout;
@@ -115,8 +124,6 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout();
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
const Resources::ShaderKeywordSet keywordSet =
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet();
const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet);
const Resources::ShaderStageVariant* fragmentVariant =
@@ -150,6 +157,7 @@ bool BuiltinForwardPipeline::TryResolveSurfacePassType(
}
BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass(
const RenderSceneData& sceneData,
const Resources::Material* material) const {
ResolvedShaderPass resolved = {};
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
@@ -162,7 +170,7 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac
if (material != nullptr && material->GetShader() != nullptr) {
const Resources::Shader* materialShader = material->GetShader();
if (const Resources::ShaderPass* shaderPass =
FindCompatibleSurfacePass(*materialShader, material, pass, backend)) {
FindCompatibleSurfacePass(*materialShader, sceneData, material, pass, backend)) {
resolved.shader = materialShader;
resolved.pass = shaderPass;
resolved.passName = shaderPass->name;
@@ -175,7 +183,7 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac
if (builtinShaderHandle->IsValid()) {
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
if (const Resources::ShaderPass* shaderPass =
FindCompatibleSurfacePass(*builtinShader, nullptr, pass, backend)) {
FindCompatibleSurfacePass(*builtinShader, sceneData, nullptr, pass, backend)) {
resolved.shader = builtinShader;
resolved.pass = shaderPass;
resolved.passName = shaderPass->name;
@@ -274,8 +282,10 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP
RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
const RenderContext& context,
const RenderSceneData& sceneData,
const Resources::Material* material) {
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
@@ -293,9 +303,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
pipelineKey.shader = resolvedShaderPass.shader;
pipelineKey.passName = resolvedShaderPass.passName;
pipelineKey.keywordSignature =
::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
const auto existing = m_pipelineStates.find(pipelineKey);
if (existing != m_pipelineStates.end()) {
@@ -308,6 +316,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
passLayout->pipelineLayout,
*resolvedShaderPass.shader,
resolvedShaderPass.passName,
keywordSet,
material);
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
if (pipelineState == nullptr || !pipelineState->IsValid()) {
@@ -665,7 +674,7 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
};
const Resources::Material* material = ResolveMaterial(visibleItem);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return false;
}

View File

@@ -12,6 +12,7 @@
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
#include <XCEngine/Scene/Scene.h>
#include <memory>
@@ -44,6 +45,7 @@ struct MockPipelineState {
XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr;
XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity();
XCEngine::Math::Vector4 lastShadowParams = XCEngine::Math::Vector4::Zero();
bool lastHasMainDirectionalShadowKeyword = false;
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
bool lastHasSkybox = false;
const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr;
@@ -320,6 +322,10 @@ public:
m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap;
m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection;
m_state->lastShadowParams = sceneData.lighting.mainDirectionalShadow.shadowParams;
m_state->lastHasMainDirectionalShadowKeyword =
XCEngine::Resources::ShaderKeywordSetContains(
sceneData.globalShaderKeywords,
"XC_MAIN_LIGHT_SHADOWS");
m_state->lastEnvironmentMode = sceneData.environment.mode;
m_state->lastHasSkybox = sceneData.environment.HasSkybox();
m_state->lastSkyboxMaterial = sceneData.environment.materialSkybox.material;
@@ -1281,6 +1287,7 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.y, 1.0f / 256.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.z, 1.0f / 128.0f);
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.w, 0.85f);
EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadowKeyword);
}
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);

View File

@@ -1269,7 +1269,7 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) {
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(shader->GetProperties().Size(), 2u);
ASSERT_EQ(pass->variants.Size(), 6u);
ASSERT_EQ(pass->variants.Size(), 12u);
ASSERT_EQ(pass->tags.Size(), 1u);
ASSERT_EQ(pass->resources.Size(), 8u);
EXPECT_EQ(pass->tags[0].name, "LightMode");
@@ -1324,6 +1324,13 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) {
EXPECT_EQ(shadowTextureBinding->set, 6u);
EXPECT_EQ(shadowTextureBinding->binding, 0u);
EXPECT_EQ(shadowTextureBinding->semantic, "ShadowMapTexture");
ASSERT_EQ(pass->keywordDeclarations.Size(), 1u);
EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile);
ASSERT_EQ(pass->keywordDeclarations[0].options.Size(), 2u);
EXPECT_EQ(pass->keywordDeclarations[0].options[0], "_");
EXPECT_EQ(pass->keywordDeclarations[0].options[1], "XC_MAIN_LIGHT_SHADOWS");
EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS"));
ASSERT_EQ(pass->variants.Size(), 12u);
EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr);
EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr);
@@ -1346,6 +1353,23 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) {
ASSERT_NE(d3d12Fragment, nullptr);
EXPECT_NE(std::string(d3d12Fragment->sourceCode.CStr()).find("gAdditionalLights"), std::string::npos);
EXPECT_NE(std::string(d3d12Fragment->sourceCode.CStr()).find("gLightingParams"), std::string::npos);
EXPECT_EQ(std::string(d3d12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"), std::string::npos);
ShaderKeywordSet shadowKeywords = {};
shadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
const ShaderStageVariant* shadowD3D12Fragment = shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::D3D12,
shadowKeywords);
ASSERT_NE(shadowD3D12Fragment, nullptr);
EXPECT_NE(
std::string(shadowD3D12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"),
std::string::npos);
EXPECT_NE(
std::string(shadowD3D12Fragment->sourceCode.CStr()).find("gShadowMapTexture.Sample"),
std::string::npos);
const ShaderStageVariant* openglFragment = shader->FindVariant(
"ForwardLit",