Add builtin unlit surface path

This commit is contained in:
2026-04-03 17:18:46 +08:00
parent 1ac2afb0bb
commit 308b3b061c
14 changed files with 466 additions and 14 deletions

View File

@@ -342,8 +342,12 @@ Unity-like Shader Authoring (.shader)
- builtin object-id shader 已显式声明 `PerObject` 资源合约 - builtin object-id shader 已显式声明 `PerObject` 资源合约
- `BuiltinObjectIdPass` 已改为消费通用 binding plan不再硬编码 `set0/binding0` 常量布局 - `BuiltinObjectIdPass` 已改为消费通用 binding plan不再硬编码 `set0/binding0` 常量布局
- 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径 - 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径
-验证:`rendering_unit_tests` 59/59`shader_tests` 26/26 -完成builtin `Unlit` shader / pipeline 主线接入共享执行边界
- 下一步:把同一套共享执行边界继续推到 `Unlit`,并评估是否抽出 forward/object-id 共用的 pass layout 构建与 descriptor set 组装骨架 - 新增 builtin `unlit` shader 资产与 `BuiltinResources` 入口
- `BuiltinForwardPipeline` 现在会在 `ForwardLit + Unlit` 之间按 material/shader metadata 解析目标 pass
- `Unlit``ForwardLit` 现在共用同一套 input layout、material schema、binding plan 与 descriptor 组装路径
- 已验证:`rendering_unit_tests` 61/61`shader_tests` 27/27`material_tests` 51/51
- 下一步:评估是否抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout 构建与 descriptor set 组装骨架,并继续推进 `DepthOnly / ShadowCaster`
### 阶段 D扩展 AssetDatabase / Library Artifact 能力 ### 阶段 D扩展 AssetDatabase / Library Artifact 能力

View File

@@ -0,0 +1,24 @@
// XC_BUILTIN_UNLIT_OPENGL_PS
#version 430
layout(binding = 1) uniform sampler2D uBaseColorTexture;
layout(std140, binding = 1) uniform PerObjectConstants {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
mat4 gNormalMatrix;
vec4 gMainLightDirectionAndIntensity;
vec4 gMainLightColorAndFlags;
};
layout(std140, binding = 2) uniform MaterialConstants {
vec4 gBaseColorFactor;
};
in vec2 vTexCoord;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor;
}

View File

@@ -0,0 +1,25 @@
// XC_BUILTIN_UNLIT_VULKAN_PS
#version 450
layout(set = 3, binding = 0) uniform texture2D uBaseColorTexture;
layout(set = 4, binding = 0) uniform sampler uLinearSampler;
layout(set = 1, binding = 0, std140) uniform PerObjectConstants {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
mat4 gNormalMatrix;
vec4 gMainLightDirectionAndIntensity;
vec4 gMainLightColorAndFlags;
};
layout(set = 2, binding = 0, std140) uniform MaterialConstants {
vec4 gBaseColorFactor;
};
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor;
}

View File

@@ -0,0 +1,25 @@
// XC_BUILTIN_UNLIT_D3D12_PS
Texture2D gBaseColorTexture : register(t1);
SamplerState gLinearSampler : register(s1);
cbuffer PerObjectConstants : register(b1) {
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gNormalMatrix;
float4 gMainLightDirectionAndIntensity;
float4 gMainLightColorAndFlags;
};
cbuffer MaterialConstants : register(b2) {
float4 gBaseColorFactor;
};
struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
float4 MainPS(PSInput input) : SV_TARGET {
return gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor;
}

View File

@@ -0,0 +1,99 @@
{
"name": "Builtin Unlit",
"properties": [
{
"name": "_BaseColor",
"displayName": "Base Color",
"type": "Color",
"defaultValue": "(1,1,1,1)",
"semantic": "BaseColor"
},
{
"name": "_MainTex",
"displayName": "Base Map",
"type": "2D",
"defaultValue": "white",
"semantic": "BaseColorTexture"
}
],
"passes": [
{
"name": "Unlit",
"tags": {
"LightMode": "Unlit"
},
"resources": [
{
"name": "PerObjectConstants",
"type": "ConstantBuffer",
"set": 1,
"binding": 0,
"semantic": "PerObject"
},
{
"name": "MaterialConstants",
"type": "ConstantBuffer",
"set": 2,
"binding": 0,
"semantic": "Material"
},
{
"name": "BaseColorTexture",
"type": "Texture2D",
"set": 3,
"binding": 0,
"semantic": "BaseColorTexture"
},
{
"name": "LinearClampSampler",
"type": "Sampler",
"set": 4,
"binding": 0,
"semantic": "LinearClampSampler"
}
],
"variants": [
{
"stage": "Vertex",
"backend": "D3D12",
"language": "HLSL",
"source": "unlit.vs.hlsl",
"entryPoint": "MainVS",
"profile": "vs_5_0"
},
{
"stage": "Fragment",
"backend": "D3D12",
"language": "HLSL",
"source": "unlit.ps.hlsl",
"entryPoint": "MainPS",
"profile": "ps_5_0"
},
{
"stage": "Vertex",
"backend": "OpenGL",
"language": "GLSL",
"source": "unlit.vert.glsl"
},
{
"stage": "Fragment",
"backend": "OpenGL",
"language": "GLSL",
"source": "unlit.frag.glsl"
},
{
"stage": "Vertex",
"backend": "Vulkan",
"language": "GLSL",
"source": "unlit.vert.vk.glsl"
},
{
"stage": "Fragment",
"backend": "Vulkan",
"language": "GLSL",
"source": "unlit.frag.vk.glsl"
}
]
}
]
}

View File

@@ -0,0 +1,23 @@
// XC_BUILTIN_UNLIT_OPENGL_VS
#version 430
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;
layout(std140, binding = 1) uniform PerObjectConstants {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
mat4 gNormalMatrix;
vec4 gMainLightDirectionAndIntensity;
vec4 gMainLightColorAndFlags;
};
out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -0,0 +1,23 @@
// XC_BUILTIN_UNLIT_VULKAN_VS
#version 450
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;
layout(set = 1, binding = 0, std140) uniform PerObjectConstants {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
mat4 gNormalMatrix;
vec4 gMainLightDirectionAndIntensity;
vec4 gMainLightColorAndFlags;
};
layout(location = 0) out vec2 vTexCoord;
void main() {
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
vec4 positionVS = gViewMatrix * positionWS;
gl_Position = gProjectionMatrix * positionVS;
vTexCoord = aTexCoord;
}

View File

@@ -0,0 +1,29 @@
// XC_BUILTIN_UNLIT_D3D12_VS
cbuffer PerObjectConstants : register(b1) {
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gNormalMatrix;
float4 gMainLightDirectionAndIntensity;
float4 gMainLightColorAndFlags;
};
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;
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f));
float4 positionVS = mul(gViewMatrix, positionWS);
output.position = mul(gProjectionMatrix, positionVS);
output.texcoord = input.texcoord;
return output;
}

View File

@@ -180,7 +180,7 @@ private:
bool EnsureInitialized(const RenderContext& context); bool EnsureInitialized(const RenderContext& context);
bool CreatePipelineResources(const RenderContext& context); bool CreatePipelineResources(const RenderContext& context);
void DestroyPipelineResources(); void DestroyPipelineResources();
ResolvedShaderPass ResolveForwardShaderPass(const Resources::Material* material) const; ResolvedShaderPass ResolveSurfaceShaderPass(const Resources::Material* material) const;
PassResourceLayout* GetOrCreatePassResourceLayout( PassResourceLayout* GetOrCreatePassResourceLayout(
const RenderContext& context, const RenderContext& context,
const ResolvedShaderPass& resolvedShaderPass); const ResolvedShaderPass& resolvedShaderPass);
@@ -217,6 +217,7 @@ private:
RHI::RHIType m_backendType = RHI::RHIType::D3D12; RHI::RHIType m_backendType = RHI::RHIType::D3D12;
bool m_initialized = false; bool m_initialized = false;
Resources::ResourceHandle<Resources::Shader> m_builtinForwardShader; Resources::ResourceHandle<Resources::Shader> m_builtinForwardShader;
Resources::ResourceHandle<Resources::Shader> m_builtinUnlitShader;
RenderResourceCache m_resourceCache; RenderResourceCache m_resourceCache;

View File

@@ -24,6 +24,7 @@ const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType);
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType); Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType);
Containers::String GetBuiltinDefaultPrimitiveMaterialPath(); Containers::String GetBuiltinDefaultPrimitiveMaterialPath();
Containers::String GetBuiltinForwardLitShaderPath(); Containers::String GetBuiltinForwardLitShaderPath();
Containers::String GetBuiltinUnlitShaderPath();
Containers::String GetBuiltinObjectIdShaderPath(); Containers::String GetBuiltinObjectIdShaderPath();
Containers::String GetBuiltinDefaultPrimitiveTexturePath(); Containers::String GetBuiltinDefaultPrimitiveTexturePath();

View File

@@ -118,9 +118,26 @@ bool BindingNumberExists(
return false; return false;
} }
const Resources::ShaderPass* FindForwardCompatiblePass( bool TryResolveSurfacePassType(
const Resources::Material* material,
BuiltinMaterialPass& outPass) {
if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) {
outPass = BuiltinMaterialPass::Unlit;
return true;
}
if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) {
outPass = BuiltinMaterialPass::ForwardLit;
return true;
}
return false;
}
const Resources::ShaderPass* FindCompatibleSurfacePass(
const Resources::Shader& shader, const Resources::Shader& shader,
const Resources::Material* material, const Resources::Material* material,
BuiltinMaterialPass pass,
Resources::ShaderBackend backend) { Resources::ShaderBackend backend) {
if (material != nullptr && !material->GetShaderPass().Empty()) { if (material != nullptr && !material->GetShaderPass().Empty()) {
const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetShaderPass()); const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetShaderPass());
@@ -131,12 +148,16 @@ const Resources::ShaderPass* FindForwardCompatiblePass(
} }
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ForwardLit) && if (ShaderPassMatchesBuiltinPass(shaderPass, pass) &&
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) {
return &shaderPass; return &shaderPass;
} }
} }
if (pass != BuiltinMaterialPass::ForwardLit) {
return nullptr;
}
const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit"); const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit");
if (defaultPass != nullptr && if (defaultPass != nullptr &&
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) {
@@ -331,7 +352,8 @@ bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& p
RHI::RHIPipelineState* currentPipelineState = nullptr; RHI::RHIPipelineState* currentPipelineState = nullptr;
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) { for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Material* material = ResolveMaterial(visibleItem);
if (!MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
if (!TryResolveSurfacePassType(material, pass)) {
continue; continue;
} }
@@ -389,6 +411,15 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex
return false; return false;
} }
m_builtinUnlitShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
Resources::GetBuiltinUnlitShaderPath());
if (!m_builtinUnlitShader.IsValid()) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline failed to load builtin unlit shader resource");
return false;
}
RHI::SamplerDesc samplerDesc = {}; RHI::SamplerDesc samplerDesc = {};
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear); samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp); samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
@@ -475,17 +506,23 @@ void BuiltinForwardPipeline::DestroyPipelineResources() {
m_device = nullptr; m_device = nullptr;
m_initialized = false; m_initialized = false;
m_builtinForwardShader.Reset(); m_builtinForwardShader.Reset();
m_builtinUnlitShader.Reset();
} }
BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwardShaderPass( BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass(
const Resources::Material* material) const { const Resources::Material* material) const {
ResolvedShaderPass resolved = {}; ResolvedShaderPass resolved = {};
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
if (!TryResolveSurfacePassType(material, pass)) {
return resolved;
}
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
if (material != nullptr && material->GetShader() != nullptr) { if (material != nullptr && material->GetShader() != nullptr) {
const Resources::Shader* materialShader = material->GetShader(); const Resources::Shader* materialShader = material->GetShader();
if (const Resources::ShaderPass* shaderPass = if (const Resources::ShaderPass* shaderPass =
FindForwardCompatiblePass(*materialShader, material, backend)) { FindCompatibleSurfacePass(*materialShader, material, pass, backend)) {
resolved.shader = materialShader; resolved.shader = materialShader;
resolved.pass = shaderPass; resolved.pass = shaderPass;
resolved.passName = shaderPass->name; resolved.passName = shaderPass->name;
@@ -493,10 +530,12 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwar
} }
} }
if (m_builtinForwardShader.IsValid()) { const Resources::ResourceHandle<Resources::Shader>* builtinShaderHandle =
const Resources::Shader* builtinShader = m_builtinForwardShader.Get(); pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader;
if (builtinShaderHandle->IsValid()) {
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
if (const Resources::ShaderPass* shaderPass = if (const Resources::ShaderPass* shaderPass =
FindForwardCompatiblePass(*builtinShader, nullptr, backend)) { FindCompatibleSurfacePass(*builtinShader, nullptr, pass, backend)) {
resolved.shader = builtinShader; resolved.shader = builtinShader;
resolved.pass = shaderPass; resolved.pass = shaderPass;
resolved.passName = shaderPass->name; resolved.passName = shaderPass->name;
@@ -672,11 +711,11 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP
RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
const RenderContext& context, const RenderContext& context,
const Resources::Material* material) { const Resources::Material* material) {
const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
Debug::Logger::Get().Error( Debug::Logger::Get().Error(
Debug::LogCategory::Rendering, Debug::LogCategory::Rendering,
"BuiltinForwardPipeline could not resolve a valid ForwardLit shader pass"); "BuiltinForwardPipeline could not resolve a valid surface shader pass");
return nullptr; return nullptr;
} }
@@ -919,7 +958,7 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
}; };
const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Material* material = ResolveMaterial(visibleItem);
const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material);
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
return false; return false;
} }

View File

@@ -28,6 +28,7 @@ constexpr const char* kBuiltinShaderPrefix = "builtin://shaders/";
constexpr const char* kBuiltinTexturePrefix = "builtin://textures/"; constexpr const char* kBuiltinTexturePrefix = "builtin://textures/";
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive"; constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit"; constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit";
constexpr const char* kBuiltinUnlitShaderPath = "builtin://shaders/unlit";
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id"; constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo"; constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
constexpr float kPi = 3.14159265358979323846f; constexpr float kPi = 3.14159265358979323846f;
@@ -41,6 +42,8 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader);
constexpr const char* kBuiltinForwardLitShaderManifestRelativePath = constexpr const char* kBuiltinForwardLitShaderManifestRelativePath =
"engine/assets/builtin/shaders/forward-lit/forward-lit.shader"; "engine/assets/builtin/shaders/forward-lit/forward-lit.shader";
constexpr const char* kBuiltinUnlitShaderManifestRelativePath =
"engine/assets/builtin/shaders/unlit/unlit.shader";
constexpr const char* kBuiltinObjectIdShaderManifestRelativePath = constexpr const char* kBuiltinObjectIdShaderManifestRelativePath =
"engine/assets/builtin/shaders/object-id/object-id.shader"; "engine/assets/builtin/shaders/object-id/object-id.shader";
@@ -109,6 +112,9 @@ const char* GetBuiltinShaderManifestRelativePath(const Containers::String& built
if (builtinShaderPath == Containers::String(kBuiltinForwardLitShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinForwardLitShaderPath)) {
return kBuiltinForwardLitShaderManifestRelativePath; return kBuiltinForwardLitShaderManifestRelativePath;
} }
if (builtinShaderPath == Containers::String(kBuiltinUnlitShaderPath)) {
return kBuiltinUnlitShaderManifestRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) {
return kBuiltinObjectIdShaderManifestRelativePath; return kBuiltinObjectIdShaderManifestRelativePath;
} }
@@ -636,6 +642,10 @@ Shader* BuildBuiltinForwardLitShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path); return TryLoadBuiltinShaderFromManifest(path);
} }
Shader* BuildBuiltinUnlitShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
}
Shader* BuildBuiltinObjectIdShader(const Containers::String& path) { Shader* BuildBuiltinObjectIdShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path); return TryLoadBuiltinShaderFromManifest(path);
} }
@@ -742,6 +752,10 @@ Containers::String GetBuiltinForwardLitShaderPath() {
return Containers::String(kBuiltinForwardLitShaderPath); return Containers::String(kBuiltinForwardLitShaderPath);
} }
Containers::String GetBuiltinUnlitShaderPath() {
return Containers::String(kBuiltinUnlitShaderPath);
}
Containers::String GetBuiltinObjectIdShaderPath() { Containers::String GetBuiltinObjectIdShaderPath() {
return Containers::String(kBuiltinObjectIdShaderPath); return Containers::String(kBuiltinObjectIdShaderPath);
} }
@@ -836,6 +850,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
Shader* shader = nullptr; Shader* shader = nullptr;
if (path == GetBuiltinForwardLitShaderPath()) { if (path == GetBuiltinForwardLitShaderPath()) {
shader = BuildBuiltinForwardLitShader(path); shader = BuildBuiltinForwardLitShader(path);
} else if (path == GetBuiltinUnlitShaderPath()) {
shader = BuildBuiltinUnlitShader(path);
} else if (path == GetBuiltinObjectIdShaderPath()) { } else if (path == GetBuiltinObjectIdShaderPath()) {
shader = BuildBuiltinObjectIdShader(path); shader = BuildBuiltinObjectIdShader(path);
} else { } else {

View File

@@ -78,6 +78,42 @@ TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardRes
delete shader; delete shader;
} }
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderDeclaresExplicitSurfaceResourceContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("Unlit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->resources.Size(), 4u);
EXPECT_EQ(pass->resources[0].semantic, "PerObject");
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[0].set, 1u);
EXPECT_EQ(pass->resources[0].binding, 0u);
EXPECT_EQ(pass->resources[1].semantic, "Material");
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[1].set, 2u);
EXPECT_EQ(pass->resources[1].binding, 0u);
EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture");
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D);
EXPECT_EQ(pass->resources[2].set, 3u);
EXPECT_EQ(pass->resources[2].binding, 0u);
EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler");
EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler);
EXPECT_EQ(pass->resources[3].set, 4u);
EXPECT_EQ(pass->resources[3].binding, 0u);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) { TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) {
ShaderLoader loader; ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
@@ -106,6 +142,34 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic
delete shader; delete shader;
} }
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitUnlitResources) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("Unlit");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.baseColorTexture.IsValid());
EXPECT_TRUE(plan.linearClampSampler.IsValid());
EXPECT_EQ(plan.firstDescriptorSet, 1u);
EXPECT_EQ(plan.descriptorSetCount, 4u);
EXPECT_TRUE(plan.usesConstantBuffers);
EXPECT_TRUE(plan.usesTextures);
EXPECT_TRUE(plan.usesSamplers);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacyFallbackResources) { TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacyFallbackResources) {
const Array<ShaderResourceBindingDesc> bindings = BuildLegacyBuiltinForwardPassResourceBindings(); const Array<ShaderResourceBindingDesc> bindings = BuildLegacyBuiltinForwardPassResourceBindings();
ASSERT_EQ(bindings.Size(), 4u); ASSERT_EQ(bindings.Size(), 4u);

View File

@@ -43,6 +43,7 @@ TEST(ShaderLoader, CanLoad) {
EXPECT_TRUE(loader.CanLoad("test.hlsl")); EXPECT_TRUE(loader.CanLoad("test.hlsl"));
EXPECT_TRUE(loader.CanLoad("test.xcshader")); EXPECT_TRUE(loader.CanLoad("test.xcshader"));
EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath()));
EXPECT_TRUE(loader.CanLoad(GetBuiltinUnlitShaderPath()));
EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath()));
EXPECT_FALSE(loader.CanLoad("test.txt")); EXPECT_FALSE(loader.CanLoad("test.txt"));
EXPECT_FALSE(loader.CanLoad("test.png")); EXPECT_FALSE(loader.CanLoad("test.png"));
@@ -495,6 +496,74 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) {
delete shader; delete shader;
} }
TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
ASSERT_TRUE(shader->IsValid());
const ShaderPass* pass = shader->FindPass("Unlit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(shader->GetProperties().Size(), 2u);
ASSERT_EQ(pass->variants.Size(), 6u);
ASSERT_EQ(pass->tags.Size(), 1u);
ASSERT_EQ(pass->resources.Size(), 4u);
EXPECT_EQ(pass->tags[0].name, "LightMode");
EXPECT_EQ(pass->tags[0].value, "Unlit");
const ShaderPropertyDesc* baseColorProperty = shader->FindProperty("_BaseColor");
ASSERT_NE(baseColorProperty, nullptr);
EXPECT_EQ(baseColorProperty->type, ShaderPropertyType::Color);
EXPECT_EQ(baseColorProperty->semantic, "BaseColor");
const ShaderPropertyDesc* baseMapProperty = shader->FindProperty("_MainTex");
ASSERT_NE(baseMapProperty, nullptr);
EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D);
EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture");
const ShaderResourceBindingDesc* perObjectBinding =
shader->FindPassResourceBinding("Unlit", "PerObjectConstants");
ASSERT_NE(perObjectBinding, nullptr);
EXPECT_EQ(perObjectBinding->type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(perObjectBinding->set, 1u);
EXPECT_EQ(perObjectBinding->binding, 0u);
EXPECT_EQ(perObjectBinding->semantic, "PerObject");
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr);
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr);
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr);
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr);
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::Vulkan), nullptr);
EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::Vulkan), nullptr);
const ShaderStageVariant* d3d12Vertex = shader->FindVariant(
"Unlit",
ShaderType::Vertex,
ShaderBackend::D3D12);
ASSERT_NE(d3d12Vertex, nullptr);
EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_D3D12_VS"), std::string::npos);
const ShaderStageVariant* openglFragment = shader->FindVariant(
"Unlit",
ShaderType::Fragment,
ShaderBackend::OpenGL);
ASSERT_NE(openglFragment, nullptr);
EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_OPENGL_PS"), std::string::npos);
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
"Unlit",
ShaderType::Fragment,
ShaderBackend::Vulkan);
ASSERT_NE(vulkanFragment, nullptr);
EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_VULKAN_PS"), std::string::npos);
delete shader;
}
TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) {
ShaderLoader loader; ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath());
@@ -557,6 +626,10 @@ TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) {
ASSERT_TRUE(shaderHandle.IsValid()); ASSERT_TRUE(shaderHandle.IsValid());
ASSERT_NE(shaderHandle->FindPass("ForwardLit"), nullptr); ASSERT_NE(shaderHandle->FindPass("ForwardLit"), nullptr);
ResourceHandle<Shader> unlitShaderHandle = manager.Load<Shader>(GetBuiltinUnlitShaderPath());
ASSERT_TRUE(unlitShaderHandle.IsValid());
ASSERT_NE(unlitShaderHandle->FindPass("Unlit"), nullptr);
ResourceHandle<Shader> objectIdShaderHandle = manager.Load<Shader>(GetBuiltinObjectIdShaderPath()); ResourceHandle<Shader> objectIdShaderHandle = manager.Load<Shader>(GetBuiltinObjectIdShaderPath());
ASSERT_TRUE(objectIdShaderHandle.IsValid()); ASSERT_TRUE(objectIdShaderHandle.IsValid());
ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr); ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr);
@@ -604,6 +677,12 @@ TEST(ShaderLoader, ResourceManagerLoadsBuiltinShadersOutsideProjectWorkingDirect
ShaderType::Vertex, ShaderType::Vertex,
ShaderBackend::D3D12, ShaderBackend::D3D12,
"XC_BUILTIN_FORWARD_LIT_D3D12_VS"); "XC_BUILTIN_FORWARD_LIT_D3D12_VS");
expectBuiltinShader(
GetBuiltinUnlitShaderPath(),
"Unlit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
"XC_BUILTIN_UNLIT_OPENGL_PS");
expectBuiltinShader( expectBuiltinShader(
GetBuiltinObjectIdShaderPath(), GetBuiltinObjectIdShaderPath(),
"ObjectId", "ObjectId",