feat: add unity-aligned shader contract metadata

This commit is contained in:
2026-04-03 00:01:31 +08:00
parent b43d4048b8
commit 6636834b35
9 changed files with 484 additions and 0 deletions

View File

@@ -1,11 +1,57 @@
{ {
"name": "Builtin Forward Lit", "name": "Builtin Forward Lit",
"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": [ "passes": [
{ {
"name": "ForwardLit", "name": "ForwardLit",
"tags": { "tags": {
"LightMode": "ForwardBase" "LightMode": "ForwardBase"
}, },
"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": [ "variants": [
{ {
"stage": "Vertex", "stage": "Vertex",

View File

@@ -122,11 +122,36 @@ struct BuiltinForwardMaterialData {
Math::Vector4 baseColorFactor = Math::Vector4::One(); Math::Vector4 baseColorFactor = Math::Vector4::One();
}; };
inline const Resources::ShaderPropertyDesc* FindShaderPropertyBySemantic(
const Resources::Material* material,
const Containers::String& semantic) {
if (material == nullptr || material->GetShader() == nullptr) {
return nullptr;
}
const Containers::String normalizedSemantic = NormalizeBuiltinPassMetadataValue(semantic);
for (const Resources::ShaderPropertyDesc& property : material->GetShader()->GetProperties()) {
if (NormalizeBuiltinPassMetadataValue(property.semantic) == normalizedSemantic) {
return &property;
}
}
return nullptr;
}
inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* material) { inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* material) {
if (material == nullptr) { if (material == nullptr) {
return Math::Vector4::One(); return Math::Vector4::One();
} }
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColor")) {
if (material->HasProperty(property->name) &&
(property->type == Resources::ShaderPropertyType::Color ||
property->type == Resources::ShaderPropertyType::Vector)) {
return material->GetFloat4(property->name);
}
}
static const char* kBaseColorPropertyNames[] = { static const char* kBaseColorPropertyNames[] = {
"baseColor", "baseColor",
"_BaseColor", "_BaseColor",
@@ -162,6 +187,13 @@ inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources:
return nullptr; return nullptr;
} }
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColorTexture")) {
const Resources::ResourceHandle<Resources::Texture> textureHandle = material->GetTexture(property->name);
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
return textureHandle.Get();
}
}
static const char* kTextureNames[] = { static const char* kTextureNames[] = {
"baseColorTexture", "baseColorTexture",
"_BaseColorTexture", "_BaseColorTexture",

View File

@@ -29,6 +29,25 @@ enum class ShaderBackend : Core::uint8 {
Vulkan Vulkan
}; };
// Keep shader property kinds close to Unity's public shader syntax so the
// runtime contract can be reused when ShaderLab-compatible parsing is added.
enum class ShaderPropertyType : Core::uint8 {
Float = 0,
Range,
Int,
Vector,
Color,
Texture2D,
TextureCube
};
enum class ShaderResourceType : Core::uint8 {
ConstantBuffer = 0,
Texture2D,
TextureCube,
Sampler
};
struct ShaderUniform { struct ShaderUniform {
Containers::String name; Containers::String name;
Core::uint32 location; Core::uint32 location;
@@ -48,6 +67,22 @@ struct ShaderPassTagEntry {
Containers::String value; Containers::String value;
}; };
struct ShaderPropertyDesc {
Containers::String name;
Containers::String displayName;
ShaderPropertyType type = ShaderPropertyType::Float;
Containers::String defaultValue;
Containers::String semantic;
};
struct ShaderResourceBindingDesc {
Containers::String name;
ShaderResourceType type = ShaderResourceType::ConstantBuffer;
Core::uint32 set = 0;
Core::uint32 binding = 0;
Containers::String semantic;
};
struct ShaderStageVariant { struct ShaderStageVariant {
ShaderType stage = ShaderType::Fragment; ShaderType stage = ShaderType::Fragment;
ShaderLanguage language = ShaderLanguage::GLSL; ShaderLanguage language = ShaderLanguage::GLSL;
@@ -61,6 +96,7 @@ struct ShaderStageVariant {
struct ShaderPass { struct ShaderPass {
Containers::String name; Containers::String name;
Containers::Array<ShaderPassTagEntry> tags; Containers::Array<ShaderPassTagEntry> tags;
Containers::Array<ShaderResourceBindingDesc> resources;
Containers::Array<ShaderStageVariant> variants; Containers::Array<ShaderStageVariant> variants;
}; };
@@ -95,6 +131,11 @@ public:
void AddAttribute(const ShaderAttribute& attribute); void AddAttribute(const ShaderAttribute& attribute);
const Containers::Array<ShaderAttribute>& GetAttributes() const { return m_attributes; } const Containers::Array<ShaderAttribute>& GetAttributes() const { return m_attributes; }
void AddProperty(const ShaderPropertyDesc& property);
void ClearProperties();
const Containers::Array<ShaderPropertyDesc>& GetProperties() const { return m_properties; }
const ShaderPropertyDesc* FindProperty(const Containers::String& propertyName) const;
void AddPass(const ShaderPass& pass); void AddPass(const ShaderPass& pass);
void ClearPasses(); void ClearPasses();
Core::uint32 GetPassCount() const { return static_cast<Core::uint32>(m_passes.Size()); } Core::uint32 GetPassCount() const { return static_cast<Core::uint32>(m_passes.Size()); }
@@ -105,9 +146,15 @@ public:
const Containers::String& passName, const Containers::String& passName,
const Containers::String& tagName, const Containers::String& tagName,
const Containers::String& tagValue); const Containers::String& tagValue);
void AddPassResourceBinding(
const Containers::String& passName,
const ShaderResourceBindingDesc& binding);
bool HasPass(const Containers::String& passName) const; bool HasPass(const Containers::String& passName) const;
const ShaderPass* FindPass(const Containers::String& passName) const; const ShaderPass* FindPass(const Containers::String& passName) const;
ShaderPass* FindPass(const Containers::String& passName); ShaderPass* FindPass(const Containers::String& passName);
const ShaderResourceBindingDesc* FindPassResourceBinding(
const Containers::String& passName,
const Containers::String& resourceName) const;
const ShaderStageVariant* FindVariant( const ShaderStageVariant* FindVariant(
const Containers::String& passName, const Containers::String& passName,
ShaderType stage, ShaderType stage,
@@ -129,6 +176,7 @@ private:
Containers::Array<ShaderUniform> m_uniforms; Containers::Array<ShaderUniform> m_uniforms;
Containers::Array<ShaderAttribute> m_attributes; Containers::Array<ShaderAttribute> m_attributes;
Containers::Array<ShaderPropertyDesc> m_properties;
Containers::Array<ShaderPass> m_passes; Containers::Array<ShaderPass> m_passes;
class IRHIShader* m_rhiResource = nullptr; class IRHIShader* m_rhiResource = nullptr;

View File

@@ -617,12 +617,22 @@ Mesh* BuildMeshResource(
size_t CalculateBuiltinShaderMemorySize(const Shader& shader) { size_t CalculateBuiltinShaderMemorySize(const Shader& shader) {
size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length(); size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length();
for (const ShaderPropertyDesc& property : shader.GetProperties()) {
memorySize += property.name.Length();
memorySize += property.displayName.Length();
memorySize += property.defaultValue.Length();
memorySize += property.semantic.Length();
}
for (const ShaderPass& pass : shader.GetPasses()) { for (const ShaderPass& pass : shader.GetPasses()) {
memorySize += pass.name.Length(); memorySize += pass.name.Length();
for (const ShaderPassTagEntry& tag : pass.tags) { for (const ShaderPassTagEntry& tag : pass.tags) {
memorySize += tag.name.Length(); memorySize += tag.name.Length();
memorySize += tag.value.Length(); memorySize += tag.value.Length();
} }
for (const ShaderResourceBindingDesc& binding : pass.resources) {
memorySize += binding.name.Length();
memorySize += binding.semantic.Length();
}
for (const ShaderStageVariant& variant : pass.variants) { for (const ShaderStageVariant& variant : pass.variants) {
memorySize += variant.entryPoint.Length(); memorySize += variant.entryPoint.Length();
memorySize += variant.profile.Length(); memorySize += variant.profile.Length();

View File

@@ -20,6 +20,7 @@ void Shader::Release() {
m_compiledBinary.Clear(); m_compiledBinary.Clear();
m_uniforms.Clear(); m_uniforms.Clear();
m_attributes.Clear(); m_attributes.Clear();
m_properties.Clear();
m_passes.Clear(); m_passes.Clear();
m_rhiResource = nullptr; m_rhiResource = nullptr;
m_isValid = false; m_isValid = false;
@@ -53,6 +54,31 @@ void Shader::AddAttribute(const ShaderAttribute& attribute) {
m_attributes.PushBack(attribute); m_attributes.PushBack(attribute);
} }
void Shader::AddProperty(const ShaderPropertyDesc& property) {
for (ShaderPropertyDesc& existingProperty : m_properties) {
if (existingProperty.name == property.name) {
existingProperty = property;
return;
}
}
m_properties.PushBack(property);
}
void Shader::ClearProperties() {
m_properties.Clear();
}
const ShaderPropertyDesc* Shader::FindProperty(const Containers::String& propertyName) const {
for (const ShaderPropertyDesc& property : m_properties) {
if (property.name == propertyName) {
return &property;
}
}
return nullptr;
}
void Shader::AddPass(const ShaderPass& pass) { void Shader::AddPass(const ShaderPass& pass) {
m_passes.PushBack(pass); m_passes.PushBack(pass);
} }
@@ -85,6 +111,20 @@ void Shader::SetPassTag(
tag.value = tagValue; tag.value = tagValue;
} }
void Shader::AddPassResourceBinding(
const Containers::String& passName,
const ShaderResourceBindingDesc& binding) {
ShaderPass& pass = GetOrCreatePass(passName);
for (ShaderResourceBindingDesc& existingBinding : pass.resources) {
if (existingBinding.name == binding.name) {
existingBinding = binding;
return;
}
}
pass.resources.PushBack(binding);
}
bool Shader::HasPass(const Containers::String& passName) const { bool Shader::HasPass(const Containers::String& passName) const {
return FindPass(passName) != nullptr; return FindPass(passName) != nullptr;
} }
@@ -109,6 +149,23 @@ ShaderPass* Shader::FindPass(const Containers::String& passName) {
return nullptr; return nullptr;
} }
const ShaderResourceBindingDesc* Shader::FindPassResourceBinding(
const Containers::String& passName,
const Containers::String& resourceName) const {
const ShaderPass* pass = FindPass(passName);
if (pass == nullptr) {
return nullptr;
}
for (const ShaderResourceBindingDesc& binding : pass->resources) {
if (binding.name == resourceName) {
return &binding;
}
}
return nullptr;
}
const ShaderStageVariant* Shader::FindVariant( const ShaderStageVariant* Shader::FindVariant(
const Containers::String& passName, const Containers::String& passName,
ShaderType stage, ShaderType stage,

View File

@@ -431,6 +431,62 @@ bool TryParseShaderBackend(const Containers::String& value, ShaderBackend& outBa
return false; return false;
} }
bool TryParseShaderPropertyType(const Containers::String& value, ShaderPropertyType& outType) {
const Containers::String normalized = value.Trim().ToLower();
if (normalized == "float") {
outType = ShaderPropertyType::Float;
return true;
}
if (normalized == "range") {
outType = ShaderPropertyType::Range;
return true;
}
if (normalized == "int" || normalized == "integer") {
outType = ShaderPropertyType::Int;
return true;
}
if (normalized == "vector" || normalized == "float4") {
outType = ShaderPropertyType::Vector;
return true;
}
if (normalized == "color") {
outType = ShaderPropertyType::Color;
return true;
}
if (normalized == "2d" || normalized == "texture2d" || normalized == "texture") {
outType = ShaderPropertyType::Texture2D;
return true;
}
if (normalized == "cube" || normalized == "cubemap" || normalized == "texturecube") {
outType = ShaderPropertyType::TextureCube;
return true;
}
return false;
}
bool TryParseShaderResourceType(const Containers::String& value, ShaderResourceType& outType) {
const Containers::String normalized = value.Trim().ToLower();
if (normalized == "constantbuffer" || normalized == "cbuffer" || normalized == "cbv") {
outType = ShaderResourceType::ConstantBuffer;
return true;
}
if (normalized == "texture2d" || normalized == "texture" || normalized == "srvtexture2d") {
outType = ShaderResourceType::Texture2D;
return true;
}
if (normalized == "texturecube" || normalized == "cubemap") {
outType = ShaderResourceType::TextureCube;
return true;
}
if (normalized == "sampler" || normalized == "samplerstate") {
outType = ShaderResourceType::Sampler;
return true;
}
return false;
}
Containers::String GetDefaultEntryPoint(ShaderLanguage language, ShaderType stage) { Containers::String GetDefaultEntryPoint(ShaderLanguage language, ShaderType stage) {
if (language != ShaderLanguage::HLSL) { if (language != ShaderLanguage::HLSL) {
return Containers::String("main"); return Containers::String("main");
@@ -520,14 +576,47 @@ bool ReadTextFile(const Containers::String& path, Containers::String& outText) {
return true; return true;
} }
bool TryParseUnsignedValue(const std::string& json, const char* key, Core::uint32& outValue) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos)) {
return false;
}
size_t endPos = valuePos;
while (endPos < json.size() && std::isdigit(static_cast<unsigned char>(json[endPos])) != 0) {
++endPos;
}
if (endPos == valuePos) {
return false;
}
try {
outValue = static_cast<Core::uint32>(std::stoul(json.substr(valuePos, endPos - valuePos)));
return true;
} catch (...) {
return false;
}
}
size_t CalculateShaderMemorySize(const Shader& shader) { size_t CalculateShaderMemorySize(const Shader& shader) {
size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length(); size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length();
for (const ShaderPropertyDesc& property : shader.GetProperties()) {
memorySize += property.name.Length();
memorySize += property.displayName.Length();
memorySize += property.defaultValue.Length();
memorySize += property.semantic.Length();
}
for (const ShaderPass& pass : shader.GetPasses()) { for (const ShaderPass& pass : shader.GetPasses()) {
memorySize += pass.name.Length(); memorySize += pass.name.Length();
for (const ShaderPassTagEntry& tag : pass.tags) { for (const ShaderPassTagEntry& tag : pass.tags) {
memorySize += tag.name.Length(); memorySize += tag.name.Length();
memorySize += tag.value.Length(); memorySize += tag.value.Length();
} }
for (const ShaderResourceBindingDesc& binding : pass.resources) {
memorySize += binding.name.Length();
memorySize += binding.semantic.Length();
}
for (const ShaderStageVariant& variant : pass.variants) { for (const ShaderStageVariant& variant : pass.variants) {
memorySize += variant.entryPoint.Length(); memorySize += variant.entryPoint.Length();
memorySize += variant.profile.Length(); memorySize += variant.profile.Length();
@@ -583,6 +672,38 @@ LoadResult LoadShaderManifest(const Containers::String& path, const std::string&
shader->Initialize(params); shader->Initialize(params);
std::string propertiesArray;
if (TryExtractArray(jsonText, "properties", propertiesArray)) {
std::vector<std::string> propertyObjects;
if (!SplitTopLevelArrayElements(propertiesArray, propertyObjects)) {
return LoadResult("Shader manifest properties array could not be parsed: " + path);
}
for (const std::string& propertyObject : propertyObjects) {
ShaderPropertyDesc property = {};
if (!TryParseStringValue(propertyObject, "name", property.name) || property.name.Empty()) {
return LoadResult("Shader manifest property is missing a valid name: " + path);
}
Containers::String propertyTypeName;
if (!TryParseStringValue(propertyObject, "type", propertyTypeName) ||
!TryParseShaderPropertyType(propertyTypeName, property.type)) {
return LoadResult("Shader manifest property has an invalid type: " + path);
}
if (!TryParseStringValue(propertyObject, "displayName", property.displayName)) {
property.displayName = property.name;
}
if (!TryParseStringValue(propertyObject, "defaultValue", property.defaultValue)) {
TryParseStringValue(propertyObject, "default", property.defaultValue);
}
TryParseStringValue(propertyObject, "semantic", property.semantic);
shader->AddProperty(property);
}
}
for (const std::string& passObject : passObjects) { for (const std::string& passObject : passObjects) {
Containers::String passName; Containers::String passName;
if (!TryParseStringValue(passObject, "name", passName) || passName.Empty()) { if (!TryParseStringValue(passObject, "name", passName) || passName.Empty()) {
@@ -600,6 +721,38 @@ LoadResult LoadShaderManifest(const Containers::String& path, const std::string&
} }
} }
std::string resourcesArray;
if (TryExtractArray(passObject, "resources", resourcesArray)) {
std::vector<std::string> resourceObjects;
if (!SplitTopLevelArrayElements(resourcesArray, resourceObjects)) {
return LoadResult("Shader manifest pass resources could not be parsed: " + path);
}
for (const std::string& resourceObject : resourceObjects) {
ShaderResourceBindingDesc resourceBinding = {};
if (!TryParseStringValue(resourceObject, "name", resourceBinding.name) ||
resourceBinding.name.Empty()) {
return LoadResult("Shader manifest pass resource is missing a valid name: " + path);
}
Containers::String resourceTypeName;
if (!TryParseStringValue(resourceObject, "type", resourceTypeName) ||
!TryParseShaderResourceType(resourceTypeName, resourceBinding.type)) {
return LoadResult("Shader manifest pass resource has an invalid type: " + path);
}
if (!TryParseUnsignedValue(resourceObject, "set", resourceBinding.set)) {
return LoadResult("Shader manifest pass resource is missing a valid set: " + path);
}
if (!TryParseUnsignedValue(resourceObject, "binding", resourceBinding.binding)) {
return LoadResult("Shader manifest pass resource is missing a valid binding: " + path);
}
TryParseStringValue(resourceObject, "semantic", resourceBinding.semantic);
shader->AddPassResourceBinding(passName, resourceBinding);
}
}
std::string variantsArray; std::string variantsArray;
if (!TryExtractArray(passObject, "variants", variantsArray)) { if (!TryExtractArray(passObject, "variants", variantsArray)) {
return LoadResult("Shader manifest pass is missing variants: " + path); return LoadResult("Shader manifest pass is missing variants: " + path);

View File

@@ -446,6 +446,39 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanon
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture); EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture);
} }
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticMetadata) {
auto* shader = new Shader();
ShaderPropertyDesc colorProperty = {};
colorProperty.name = "TintColor";
colorProperty.displayName = "Tint";
colorProperty.type = ShaderPropertyType::Color;
colorProperty.semantic = "BaseColor";
shader->AddProperty(colorProperty);
ShaderPropertyDesc textureProperty = {};
textureProperty.name = "AlbedoMap";
textureProperty.displayName = "Albedo";
textureProperty.type = ShaderPropertyType::Texture2D;
textureProperty.semantic = "BaseColorTexture";
shader->AddProperty(textureProperty);
Material material;
material.SetShader(ResourceHandle<Shader>(shader));
material.SetFloat4("TintColor", Vector4(0.3f, 0.5f, 0.7f, 0.9f));
Texture* texture = new Texture();
IResource::ConstructParams textureParams = {};
textureParams.name = "SemanticTexture";
textureParams.path = "Textures/semantic_base_color.texture";
textureParams.guid = ResourceGUID::Generate(textureParams.path);
texture->Initialize(textureParams);
material.SetTexture("AlbedoMap", ResourceHandle<Texture>(texture));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.3f, 0.5f, 0.7f, 0.9f));
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), texture);
}
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) { TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
Material material; Material material;
material.SetFloat("opacity", 0.35f); material.SetFloat("opacity", 0.35f);

View File

@@ -158,9 +158,48 @@ TEST(Shader, StoresPerPassTags) {
EXPECT_EQ(pass->tags[1].value, "Geometry"); EXPECT_EQ(pass->tags[1].value, "Geometry");
} }
TEST(Shader, StoresShaderPropertiesAndPassResources) {
Shader shader;
ShaderPropertyDesc baseColor = {};
baseColor.name = "_BaseColor";
baseColor.displayName = "Base Color";
baseColor.type = ShaderPropertyType::Color;
baseColor.defaultValue = "(1,1,1,1)";
baseColor.semantic = "BaseColor";
shader.AddProperty(baseColor);
ShaderResourceBindingDesc perObject = {};
perObject.name = "PerObjectConstants";
perObject.type = ShaderResourceType::ConstantBuffer;
perObject.set = 1;
perObject.binding = 0;
perObject.semantic = "PerObject";
shader.AddPassResourceBinding("ForwardLit", perObject);
ASSERT_EQ(shader.GetProperties().Size(), 1u);
const ShaderPropertyDesc* storedProperty = shader.FindProperty("_BaseColor");
ASSERT_NE(storedProperty, nullptr);
EXPECT_EQ(storedProperty->displayName, "Base Color");
EXPECT_EQ(storedProperty->type, ShaderPropertyType::Color);
EXPECT_EQ(storedProperty->semantic, "BaseColor");
const ShaderResourceBindingDesc* storedBinding =
shader.FindPassResourceBinding("ForwardLit", "PerObjectConstants");
ASSERT_NE(storedBinding, nullptr);
EXPECT_EQ(storedBinding->type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(storedBinding->set, 1u);
EXPECT_EQ(storedBinding->binding, 0u);
EXPECT_EQ(storedBinding->semantic, "PerObject");
}
TEST(Shader, ReleaseClearsPassRuntimeData) { TEST(Shader, ReleaseClearsPassRuntimeData) {
Shader shader; Shader shader;
shader.SetSourceCode("void main() {}"); shader.SetSourceCode("void main() {}");
ShaderPropertyDesc property = {};
property.name = "_BaseColor";
property.type = ShaderPropertyType::Color;
shader.AddProperty(property);
ShaderStageVariant variant = {}; ShaderStageVariant variant = {};
variant.stage = ShaderType::Fragment; variant.stage = ShaderType::Fragment;
variant.sourceCode = "fragment"; variant.sourceCode = "fragment";
@@ -168,6 +207,7 @@ TEST(Shader, ReleaseClearsPassRuntimeData) {
shader.Release(); shader.Release();
EXPECT_EQ(shader.GetProperties().Size(), 0u);
EXPECT_EQ(shader.GetPassCount(), 0u); EXPECT_EQ(shader.GetPassCount(), 0u);
EXPECT_EQ(shader.GetSourceCode(), ""); EXPECT_EQ(shader.GetSourceCode(), "");
EXPECT_EQ(shader.GetCompiledBinary().Size(), 0u); EXPECT_EQ(shader.GetCompiledBinary().Size(), 0u);

View File

@@ -107,6 +107,22 @@ TEST(ShaderLoader, LoadShaderManifestBuildsMultiPassBackendVariants) {
ASSERT_TRUE(manifest.is_open()); ASSERT_TRUE(manifest.is_open());
manifest << "{\n"; manifest << "{\n";
manifest << " \"name\": \"TestLitShader\",\n"; manifest << " \"name\": \"TestLitShader\",\n";
manifest << " \"properties\": [\n";
manifest << " {\n";
manifest << " \"name\": \"_BaseColor\",\n";
manifest << " \"displayName\": \"Base Color\",\n";
manifest << " \"type\": \"Color\",\n";
manifest << " \"defaultValue\": \"(1,1,1,1)\",\n";
manifest << " \"semantic\": \"BaseColor\"\n";
manifest << " },\n";
manifest << " {\n";
manifest << " \"name\": \"_MainTex\",\n";
manifest << " \"displayName\": \"Base Map\",\n";
manifest << " \"type\": \"2D\",\n";
manifest << " \"defaultValue\": \"white\",\n";
manifest << " \"semantic\": \"BaseColorTexture\"\n";
manifest << " }\n";
manifest << " ],\n";
manifest << " \"passes\": [\n"; manifest << " \"passes\": [\n";
manifest << " {\n"; manifest << " {\n";
manifest << " \"name\": \"ForwardLit\",\n"; manifest << " \"name\": \"ForwardLit\",\n";
@@ -114,6 +130,12 @@ TEST(ShaderLoader, LoadShaderManifestBuildsMultiPassBackendVariants) {
manifest << " \"LightMode\": \"ForwardBase\",\n"; manifest << " \"LightMode\": \"ForwardBase\",\n";
manifest << " \"Queue\": \"Geometry\"\n"; manifest << " \"Queue\": \"Geometry\"\n";
manifest << " },\n"; manifest << " },\n";
manifest << " \"resources\": [\n";
manifest << " { \"name\": \"PerObjectConstants\", \"type\": \"ConstantBuffer\", \"set\": 1, \"binding\": 0, \"semantic\": \"PerObject\" },\n";
manifest << " { \"name\": \"MaterialConstants\", \"type\": \"ConstantBuffer\", \"set\": 2, \"binding\": 0, \"semantic\": \"Material\" },\n";
manifest << " { \"name\": \"BaseColorTexture\", \"type\": \"Texture2D\", \"set\": 3, \"binding\": 0, \"semantic\": \"BaseColorTexture\" },\n";
manifest << " { \"name\": \"LinearClampSampler\", \"type\": \"Sampler\", \"set\": 4, \"binding\": 0, \"semantic\": \"LinearClampSampler\" }\n";
manifest << " ],\n";
manifest << " \"variants\": [\n"; manifest << " \"variants\": [\n";
manifest << " { \"stage\": \"Vertex\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.vs.hlsl\", \"entryPoint\": \"MainVS\", \"profile\": \"vs_5_0\" },\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.vs.hlsl\", \"entryPoint\": \"MainVS\", \"profile\": \"vs_5_0\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.ps.hlsl\", \"entryPoint\": \"MainPS\", \"profile\": \"ps_5_0\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.ps.hlsl\", \"entryPoint\": \"MainPS\", \"profile\": \"ps_5_0\" },\n";
@@ -146,16 +168,39 @@ TEST(ShaderLoader, LoadShaderManifestBuildsMultiPassBackendVariants) {
ASSERT_NE(shader, nullptr); ASSERT_NE(shader, nullptr);
ASSERT_TRUE(shader->IsValid()); ASSERT_TRUE(shader->IsValid());
EXPECT_EQ(shader->GetName(), "TestLitShader"); EXPECT_EQ(shader->GetName(), "TestLitShader");
ASSERT_EQ(shader->GetProperties().Size(), 2u);
ASSERT_EQ(shader->GetPassCount(), 2u); ASSERT_EQ(shader->GetPassCount(), 2u);
const ShaderPropertyDesc* baseColorProperty = shader->FindProperty("_BaseColor");
ASSERT_NE(baseColorProperty, nullptr);
EXPECT_EQ(baseColorProperty->displayName, "Base Color");
EXPECT_EQ(baseColorProperty->type, ShaderPropertyType::Color);
EXPECT_EQ(baseColorProperty->defaultValue, "(1,1,1,1)");
EXPECT_EQ(baseColorProperty->semantic, "BaseColor");
const ShaderPropertyDesc* baseMapProperty = shader->FindProperty("_MainTex");
ASSERT_NE(baseMapProperty, nullptr);
EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D);
EXPECT_EQ(baseMapProperty->defaultValue, "white");
EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture");
const ShaderPass* forwardLitPass = shader->FindPass("ForwardLit"); const ShaderPass* forwardLitPass = shader->FindPass("ForwardLit");
ASSERT_NE(forwardLitPass, nullptr); ASSERT_NE(forwardLitPass, nullptr);
ASSERT_EQ(forwardLitPass->tags.Size(), 2u); ASSERT_EQ(forwardLitPass->tags.Size(), 2u);
ASSERT_EQ(forwardLitPass->resources.Size(), 4u);
EXPECT_EQ(forwardLitPass->tags[0].name, "LightMode"); EXPECT_EQ(forwardLitPass->tags[0].name, "LightMode");
EXPECT_EQ(forwardLitPass->tags[0].value, "ForwardBase"); EXPECT_EQ(forwardLitPass->tags[0].value, "ForwardBase");
EXPECT_EQ(forwardLitPass->tags[1].name, "Queue"); EXPECT_EQ(forwardLitPass->tags[1].name, "Queue");
EXPECT_EQ(forwardLitPass->tags[1].value, "Geometry"); EXPECT_EQ(forwardLitPass->tags[1].value, "Geometry");
const ShaderResourceBindingDesc* baseTextureBinding =
shader->FindPassResourceBinding("ForwardLit", "BaseColorTexture");
ASSERT_NE(baseTextureBinding, nullptr);
EXPECT_EQ(baseTextureBinding->type, ShaderResourceType::Texture2D);
EXPECT_EQ(baseTextureBinding->set, 3u);
EXPECT_EQ(baseTextureBinding->binding, 0u);
EXPECT_EQ(baseTextureBinding->semantic, "BaseColorTexture");
const ShaderStageVariant* d3d12Vertex = shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12); const ShaderStageVariant* d3d12Vertex = shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12);
ASSERT_NE(d3d12Vertex, nullptr); ASSERT_NE(d3d12Vertex, nullptr);
EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS");
@@ -252,11 +297,31 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) {
const ShaderPass* pass = shader->FindPass("ForwardLit"); const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr); ASSERT_NE(pass, nullptr);
ASSERT_EQ(shader->GetProperties().Size(), 2u);
ASSERT_EQ(pass->variants.Size(), 6u); ASSERT_EQ(pass->variants.Size(), 6u);
ASSERT_EQ(pass->tags.Size(), 1u); ASSERT_EQ(pass->tags.Size(), 1u);
ASSERT_EQ(pass->resources.Size(), 4u);
EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].name, "LightMode");
EXPECT_EQ(pass->tags[0].value, "ForwardBase"); EXPECT_EQ(pass->tags[0].value, "ForwardBase");
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("ForwardLit", "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("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr);
EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr);
EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr);