Add material render metadata and loader parsing
This commit is contained in:
@@ -14,6 +14,14 @@
|
|||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Resources {
|
namespace Resources {
|
||||||
|
|
||||||
|
enum class MaterialRenderQueue : Core::int32 {
|
||||||
|
Background = 1000,
|
||||||
|
Geometry = 2000,
|
||||||
|
AlphaTest = 2450,
|
||||||
|
Transparent = 3000,
|
||||||
|
Overlay = 4000
|
||||||
|
};
|
||||||
|
|
||||||
enum class MaterialPropertyType {
|
enum class MaterialPropertyType {
|
||||||
Float, Float2, Float3, Float4,
|
Float, Float2, Float3, Float4,
|
||||||
Int, Int2, Int3, Int4,
|
Int, Int2, Int3, Int4,
|
||||||
@@ -52,6 +60,20 @@ public:
|
|||||||
|
|
||||||
void SetShader(const ResourceHandle<class Shader>& shader);
|
void SetShader(const ResourceHandle<class Shader>& shader);
|
||||||
class Shader* GetShader() const { return m_shader.Get(); }
|
class Shader* GetShader() const { return m_shader.Get(); }
|
||||||
|
|
||||||
|
void SetRenderQueue(Core::int32 renderQueue);
|
||||||
|
void SetRenderQueue(MaterialRenderQueue renderQueue);
|
||||||
|
Core::int32 GetRenderQueue() const { return m_renderQueue; }
|
||||||
|
|
||||||
|
void SetShaderPass(const Containers::String& shaderPass);
|
||||||
|
const Containers::String& GetShaderPass() const { return m_shaderPass; }
|
||||||
|
|
||||||
|
void SetTag(const Containers::String& name, const Containers::String& value);
|
||||||
|
Containers::String GetTag(const Containers::String& name) const;
|
||||||
|
bool HasTag(const Containers::String& name) const;
|
||||||
|
void RemoveTag(const Containers::String& name);
|
||||||
|
void ClearTags();
|
||||||
|
Core::uint32 GetTagCount() const { return static_cast<Core::uint32>(m_tags.Size()); }
|
||||||
|
|
||||||
void SetFloat(const Containers::String& name, float value);
|
void SetFloat(const Containers::String& name, float value);
|
||||||
void SetFloat2(const Containers::String& name, const Math::Vector2& value);
|
void SetFloat2(const Containers::String& name, const Math::Vector2& value);
|
||||||
@@ -72,7 +94,8 @@ public:
|
|||||||
|
|
||||||
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
|
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
|
||||||
void UpdateConstantBuffer();
|
void UpdateConstantBuffer();
|
||||||
|
void RecalculateMemorySize();
|
||||||
|
|
||||||
bool HasProperty(const Containers::String& name) const;
|
bool HasProperty(const Containers::String& name) const;
|
||||||
void RemoveProperty(const Containers::String& name);
|
void RemoveProperty(const Containers::String& name);
|
||||||
void ClearAllProperties();
|
void ClearAllProperties();
|
||||||
@@ -81,6 +104,13 @@ private:
|
|||||||
void UpdateMemorySize();
|
void UpdateMemorySize();
|
||||||
|
|
||||||
ResourceHandle<class Shader> m_shader;
|
ResourceHandle<class Shader> m_shader;
|
||||||
|
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||||
|
Containers::String m_shaderPass;
|
||||||
|
struct TagEntry {
|
||||||
|
Containers::String name;
|
||||||
|
Containers::String value;
|
||||||
|
};
|
||||||
|
Containers::Array<TagEntry> m_tags;
|
||||||
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
|
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
|
||||||
Containers::Array<Core::uint8> m_constantBufferData;
|
Containers::Array<Core::uint8> m_constantBufferData;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||||
|
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||||
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Resources {
|
namespace Resources {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename TLoader>
|
||||||
|
void RegisterBuiltinLoader(ResourceManager& manager, TLoader& loader) {
|
||||||
|
if (manager.GetLoader(loader.GetResourceType()) == nullptr) {
|
||||||
|
manager.RegisterLoader(&loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialLoader g_materialLoader;
|
||||||
|
ShaderLoader g_shaderLoader;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
ResourceManager& ResourceManager::Get() {
|
ResourceManager& ResourceManager::Get() {
|
||||||
static ResourceManager instance;
|
static ResourceManager instance;
|
||||||
return instance;
|
return instance;
|
||||||
@@ -13,6 +29,9 @@ ResourceManager& ResourceManager::Get() {
|
|||||||
void ResourceManager::Initialize() {
|
void ResourceManager::Initialize() {
|
||||||
m_asyncLoader = Core::MakeUnique<AsyncLoader>();
|
m_asyncLoader = Core::MakeUnique<AsyncLoader>();
|
||||||
m_asyncLoader->Initialize(2);
|
m_asyncLoader->Initialize(2);
|
||||||
|
|
||||||
|
RegisterBuiltinLoader(*this, g_materialLoader);
|
||||||
|
RegisterBuiltinLoader(*this, g_shaderLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceManager::Shutdown() {
|
void ResourceManager::Shutdown() {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ Material::~Material() = default;
|
|||||||
|
|
||||||
void Material::Release() {
|
void Material::Release() {
|
||||||
m_shader.Reset();
|
m_shader.Reset();
|
||||||
|
m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||||
|
m_shaderPass.Clear();
|
||||||
|
m_tags.Clear();
|
||||||
m_properties.Clear();
|
m_properties.Clear();
|
||||||
m_textureBindings.Clear();
|
m_textureBindings.Clear();
|
||||||
m_constantBufferData.Clear();
|
m_constantBufferData.Clear();
|
||||||
@@ -23,6 +26,73 @@ void Material::SetShader(const ResourceHandle<Shader>& shader) {
|
|||||||
UpdateMemorySize();
|
UpdateMemorySize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Material::SetRenderQueue(Core::int32 renderQueue) {
|
||||||
|
m_renderQueue = renderQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Material::SetRenderQueue(MaterialRenderQueue renderQueue) {
|
||||||
|
SetRenderQueue(static_cast<Core::int32>(renderQueue));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Material::SetShaderPass(const Containers::String& shaderPass) {
|
||||||
|
m_shaderPass = shaderPass;
|
||||||
|
UpdateMemorySize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Material::SetTag(const Containers::String& name, const Containers::String& value) {
|
||||||
|
for (TagEntry& tag : m_tags) {
|
||||||
|
if (tag.name == name) {
|
||||||
|
tag.value = value;
|
||||||
|
UpdateMemorySize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TagEntry tag;
|
||||||
|
tag.name = name;
|
||||||
|
tag.value = value;
|
||||||
|
m_tags.PushBack(tag);
|
||||||
|
UpdateMemorySize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Containers::String Material::GetTag(const Containers::String& name) const {
|
||||||
|
for (const TagEntry& tag : m_tags) {
|
||||||
|
if (tag.name == name) {
|
||||||
|
return tag.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Containers::String();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Material::HasTag(const Containers::String& name) const {
|
||||||
|
for (const TagEntry& tag : m_tags) {
|
||||||
|
if (tag.name == name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Material::RemoveTag(const Containers::String& name) {
|
||||||
|
for (size_t tagIndex = 0; tagIndex < m_tags.Size(); ++tagIndex) {
|
||||||
|
if (m_tags[tagIndex].name == name) {
|
||||||
|
if (tagIndex != m_tags.Size() - 1) {
|
||||||
|
m_tags[tagIndex] = std::move(m_tags.Back());
|
||||||
|
}
|
||||||
|
m_tags.PopBack();
|
||||||
|
UpdateMemorySize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Material::ClearTags() {
|
||||||
|
m_tags.Clear();
|
||||||
|
UpdateMemorySize();
|
||||||
|
}
|
||||||
|
|
||||||
void Material::SetFloat(const Containers::String& name, float value) {
|
void Material::SetFloat(const Containers::String& name, float value) {
|
||||||
MaterialProperty prop;
|
MaterialProperty prop;
|
||||||
prop.name = name;
|
prop.name = name;
|
||||||
@@ -175,6 +245,10 @@ void Material::UpdateConstantBuffer() {
|
|||||||
UpdateMemorySize();
|
UpdateMemorySize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Material::RecalculateMemorySize() {
|
||||||
|
UpdateMemorySize();
|
||||||
|
}
|
||||||
|
|
||||||
bool Material::HasProperty(const Containers::String& name) const {
|
bool Material::HasProperty(const Containers::String& name) const {
|
||||||
return m_properties.Contains(name);
|
return m_properties.Contains(name);
|
||||||
}
|
}
|
||||||
@@ -193,11 +267,18 @@ void Material::ClearAllProperties() {
|
|||||||
|
|
||||||
void Material::UpdateMemorySize() {
|
void Material::UpdateMemorySize() {
|
||||||
m_memorySize = m_constantBufferData.Size() +
|
m_memorySize = m_constantBufferData.Size() +
|
||||||
|
m_shaderPass.Length() +
|
||||||
|
m_tags.Size() * sizeof(TagEntry) +
|
||||||
m_textureBindings.Size() * sizeof(TextureBinding) +
|
m_textureBindings.Size() * sizeof(TextureBinding) +
|
||||||
m_properties.Size() * sizeof(MaterialProperty) +
|
m_properties.Size() * sizeof(MaterialProperty) +
|
||||||
m_name.Length() +
|
m_name.Length() +
|
||||||
m_path.Length();
|
m_path.Length();
|
||||||
|
|
||||||
|
for (const TagEntry& tag : m_tags) {
|
||||||
|
m_memorySize += tag.name.Length();
|
||||||
|
m_memorySize += tag.value.Length();
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& binding : m_textureBindings) {
|
for (const auto& binding : m_textureBindings) {
|
||||||
m_memorySize += binding.name.Length();
|
m_memorySize += binding.name.Length();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,256 @@
|
|||||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||||
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Resources {
|
namespace Resources {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ToStdString(const Containers::Array<Core::uint8>& data) {
|
||||||
|
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SkipWhitespace(const std::string& text, size_t pos) {
|
||||||
|
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos])) != 0) {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasKey(const std::string& json, const char* key) {
|
||||||
|
const std::string token = std::string("\"") + key + "\"";
|
||||||
|
return json.find(token) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindValueStart(const std::string& json, const char* key, size_t& valuePos) {
|
||||||
|
const std::string token = std::string("\"") + key + "\"";
|
||||||
|
const size_t keyPos = json.find(token);
|
||||||
|
if (keyPos == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t colonPos = json.find(':', keyPos + token.length());
|
||||||
|
if (colonPos == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
valuePos = SkipWhitespace(json, colonPos + 1);
|
||||||
|
return valuePos < json.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseQuotedString(const std::string& text, size_t quotePos, Containers::String& outValue, size_t* nextPos = nullptr) {
|
||||||
|
if (quotePos >= text.size() || text[quotePos] != '"') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string parsed;
|
||||||
|
++quotePos;
|
||||||
|
|
||||||
|
while (quotePos < text.size()) {
|
||||||
|
const char ch = text[quotePos];
|
||||||
|
if (ch == '\\') {
|
||||||
|
if (quotePos + 1 >= text.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push_back(text[quotePos + 1]);
|
||||||
|
quotePos += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '"') {
|
||||||
|
outValue = parsed.c_str();
|
||||||
|
if (nextPos != nullptr) {
|
||||||
|
*nextPos = quotePos + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push_back(ch);
|
||||||
|
++quotePos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryParseStringValue(const std::string& json, const char* key, Containers::String& outValue) {
|
||||||
|
size_t valuePos = 0;
|
||||||
|
if (!FindValueStart(json, key, valuePos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseQuotedString(json, valuePos, outValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryParseIntValue(const std::string& json, const char* key, Core::int32& outValue) {
|
||||||
|
size_t valuePos = 0;
|
||||||
|
if (!FindValueStart(json, key, valuePos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valuePos >= json.size() ||
|
||||||
|
(json[valuePos] != '-' && std::isdigit(static_cast<unsigned char>(json[valuePos])) == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* endPtr = nullptr;
|
||||||
|
const long parsed = std::strtol(json.c_str() + valuePos, &endPtr, 10);
|
||||||
|
if (endPtr == json.c_str() + valuePos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outValue = static_cast<Core::int32>(parsed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) {
|
||||||
|
size_t valuePos = 0;
|
||||||
|
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '{') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inString = false;
|
||||||
|
bool escaped = false;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
for (size_t pos = valuePos; pos < json.size(); ++pos) {
|
||||||
|
const char ch = json[pos];
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '"') {
|
||||||
|
inString = !inString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inString) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '{') {
|
||||||
|
++depth;
|
||||||
|
} else if (ch == '}') {
|
||||||
|
--depth;
|
||||||
|
if (depth == 0) {
|
||||||
|
outObject = json.substr(valuePos, pos - valuePos + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryParseRenderQueueName(const Containers::String& queueName, Core::int32& outQueue) {
|
||||||
|
const Containers::String normalized = queueName.ToLower();
|
||||||
|
if (normalized == "background") {
|
||||||
|
outQueue = static_cast<Core::int32>(MaterialRenderQueue::Background);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == "geometry" || normalized == "opaque") {
|
||||||
|
outQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == "alphatest" || normalized == "alpha_test") {
|
||||||
|
outQueue = static_cast<Core::int32>(MaterialRenderQueue::AlphaTest);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == "transparent") {
|
||||||
|
outQueue = static_cast<Core::int32>(MaterialRenderQueue::Transparent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == "overlay") {
|
||||||
|
outQueue = static_cast<Core::int32>(MaterialRenderQueue::Overlay);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryParseTagMap(const std::string& objectText, Material* material) {
|
||||||
|
if (material == nullptr || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 1;
|
||||||
|
while (pos < objectText.size()) {
|
||||||
|
pos = SkipWhitespace(objectText, pos);
|
||||||
|
if (pos >= objectText.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectText[pos] == '}') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Containers::String key;
|
||||||
|
if (!ParseQuotedString(objectText, pos, key, &pos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = SkipWhitespace(objectText, pos);
|
||||||
|
if (pos >= objectText.size() || objectText[pos] != ':') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = SkipWhitespace(objectText, pos + 1);
|
||||||
|
Containers::String value;
|
||||||
|
if (!ParseQuotedString(objectText, pos, value, &pos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
material->SetTag(key, value);
|
||||||
|
|
||||||
|
pos = SkipWhitespace(objectText, pos);
|
||||||
|
if (pos >= objectText.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectText[pos] == ',') {
|
||||||
|
++pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectText[pos] == '}') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath) {
|
||||||
|
ResourceHandle<Shader> shader = ResourceManager::Get().Load<Shader>(shaderPath);
|
||||||
|
if (shader.IsValid()) {
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderLoader shaderLoader;
|
||||||
|
LoadResult shaderResult = shaderLoader.Load(shaderPath);
|
||||||
|
if (!shaderResult || shaderResult.resource == nullptr) {
|
||||||
|
return ResourceHandle<Shader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResourceHandle<Shader>(static_cast<Shader*>(shaderResult.resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
MaterialLoader::MaterialLoader() = default;
|
MaterialLoader::MaterialLoader() = default;
|
||||||
|
|
||||||
MaterialLoader::~MaterialLoader() = default;
|
MaterialLoader::~MaterialLoader() = default;
|
||||||
@@ -23,41 +269,25 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||||
|
(void)settings;
|
||||||
|
|
||||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||||
if (data.Empty()) {
|
if (data.Empty()) {
|
||||||
return LoadResult("Failed to read material file: " + path);
|
return LoadResult("Failed to read material file: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Material* material = new Material();
|
Material* material = new Material();
|
||||||
material->m_path = path;
|
material->m_path = path;
|
||||||
material->m_name = path;
|
material->m_name = path;
|
||||||
material->m_guid = ResourceGUID::Generate(path);
|
material->m_guid = ResourceGUID::Generate(path);
|
||||||
|
|
||||||
Containers::String jsonStr;
|
if (!ParseMaterialData(data, material)) {
|
||||||
jsonStr.Reserve(data.Size());
|
delete material;
|
||||||
for (size_t i = 0; i < data.Size(); ++i) {
|
return LoadResult("Failed to parse material file: " + path);
|
||||||
jsonStr += static_cast<char>(data[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t shaderPos = jsonStr.Find("\"shader\"");
|
|
||||||
if (shaderPos != Containers::String::npos) {
|
|
||||||
size_t colonPos = jsonStr.Find(":", shaderPos);
|
|
||||||
if (colonPos != Containers::String::npos) {
|
|
||||||
size_t quoteStart = jsonStr.Find("\"", colonPos);
|
|
||||||
size_t quoteEnd = jsonStr.Find("\"", quoteStart + 1);
|
|
||||||
if (quoteStart != Containers::String::npos && quoteEnd != Containers::String::npos) {
|
|
||||||
Containers::String shaderPath = jsonStr.Substring(quoteStart + 1, quoteEnd - quoteStart - 1);
|
|
||||||
auto shaderHandle = ResourceManager::Get().Load<Shader>(shaderPath);
|
|
||||||
if (shaderHandle.IsValid()) {
|
|
||||||
material->SetShader(shaderHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
material->m_isValid = true;
|
material->m_isValid = true;
|
||||||
material->m_memorySize = sizeof(Material) + material->m_name.Length() + material->m_path.Length();
|
material->RecalculateMemorySize();
|
||||||
|
|
||||||
return LoadResult(material);
|
return LoadResult(material);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +296,59 @@ ImportSettings* MaterialLoader::GetDefaultSettings() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& data, Material* material) {
|
bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& data, Material* material) {
|
||||||
|
if (material == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string jsonText = ToStdString(data);
|
||||||
|
|
||||||
|
Containers::String shaderPath;
|
||||||
|
if (HasKey(jsonText, "shader")) {
|
||||||
|
if (!TryParseStringValue(jsonText, "shader", shaderPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourceHandle<Shader> shaderHandle = LoadShaderHandle(shaderPath);
|
||||||
|
if (shaderHandle.IsValid()) {
|
||||||
|
material->SetShader(shaderHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Containers::String shaderPass;
|
||||||
|
if (HasKey(jsonText, "shaderPass")) {
|
||||||
|
if (!TryParseStringValue(jsonText, "shaderPass", shaderPass)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
material->SetShaderPass(shaderPass);
|
||||||
|
} else if (HasKey(jsonText, "pass")) {
|
||||||
|
if (!TryParseStringValue(jsonText, "pass", shaderPass)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
material->SetShaderPass(shaderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasKey(jsonText, "renderQueue")) {
|
||||||
|
Core::int32 renderQueue = 0;
|
||||||
|
if (TryParseIntValue(jsonText, "renderQueue", renderQueue)) {
|
||||||
|
material->SetRenderQueue(renderQueue);
|
||||||
|
} else {
|
||||||
|
Containers::String renderQueueName;
|
||||||
|
if (!TryParseStringValue(jsonText, "renderQueue", renderQueueName) ||
|
||||||
|
!TryParseRenderQueueName(renderQueueName, renderQueue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
material->SetRenderQueue(renderQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasKey(jsonText, "tags")) {
|
||||||
|
std::string tagObject;
|
||||||
|
if (!TryExtractObject(jsonText, "tags", tagObject) || !TryParseTagMap(tagObject, material)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,76 @@ TEST(Material, SetGetShader) {
|
|||||||
EXPECT_EQ(material.GetShader(), shader);
|
EXPECT_EQ(material.GetShader(), shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Material, DefaultRenderMetadata) {
|
||||||
|
Material material;
|
||||||
|
EXPECT_EQ(material.GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Geometry));
|
||||||
|
EXPECT_TRUE(material.GetShaderPass().Empty());
|
||||||
|
EXPECT_EQ(material.GetTagCount(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, SetGetRenderQueue) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetRenderQueue(MaterialRenderQueue::Transparent);
|
||||||
|
EXPECT_EQ(material.GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
|
||||||
|
|
||||||
|
material.SetRenderQueue(2600);
|
||||||
|
EXPECT_EQ(material.GetRenderQueue(), 2600);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, SetGetShaderPass) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetShaderPass("ForwardLit");
|
||||||
|
EXPECT_EQ(material.GetShaderPass(), "ForwardLit");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, SetGetTags) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetTag("LightMode", "ForwardBase");
|
||||||
|
material.SetTag("RenderType", "Opaque");
|
||||||
|
|
||||||
|
EXPECT_TRUE(material.HasTag("LightMode"));
|
||||||
|
EXPECT_EQ(material.GetTag("LightMode"), "ForwardBase");
|
||||||
|
EXPECT_EQ(material.GetTag("RenderType"), "Opaque");
|
||||||
|
EXPECT_EQ(material.GetTagCount(), 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, SetTagReplacesExistingValue) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetTag("LightMode", "ForwardBase");
|
||||||
|
material.SetTag("LightMode", "ShadowCaster");
|
||||||
|
|
||||||
|
EXPECT_EQ(material.GetTagCount(), 1u);
|
||||||
|
EXPECT_EQ(material.GetTag("LightMode"), "ShadowCaster");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, RemoveTag) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetTag("LightMode", "ForwardBase");
|
||||||
|
EXPECT_TRUE(material.HasTag("LightMode"));
|
||||||
|
|
||||||
|
material.RemoveTag("LightMode");
|
||||||
|
EXPECT_FALSE(material.HasTag("LightMode"));
|
||||||
|
EXPECT_TRUE(material.GetTag("LightMode").Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Material, ClearTags) {
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
material.SetTag("LightMode", "ForwardBase");
|
||||||
|
material.SetTag("RenderType", "Opaque");
|
||||||
|
ASSERT_EQ(material.GetTagCount(), 2u);
|
||||||
|
|
||||||
|
material.ClearTags();
|
||||||
|
EXPECT_EQ(material.GetTagCount(), 0u);
|
||||||
|
EXPECT_FALSE(material.HasTag("LightMode"));
|
||||||
|
EXPECT_FALSE(material.HasTag("RenderType"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Material, SetGetFloat) {
|
TEST(Material, SetGetFloat) {
|
||||||
Material material;
|
Material material;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||||
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||||
#include <XCEngine/Core/Containers/Array.h>
|
#include <XCEngine/Core/Containers/Array.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
using namespace XCEngine::Resources;
|
using namespace XCEngine::Resources;
|
||||||
using namespace XCEngine::Containers;
|
using namespace XCEngine::Containers;
|
||||||
|
|
||||||
@@ -33,4 +38,76 @@ TEST(MaterialLoader, LoadInvalidPath) {
|
|||||||
EXPECT_FALSE(result);
|
EXPECT_FALSE(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MaterialLoader, ResourceManagerRegistersMaterialAndShaderLoaders) {
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
manager.Initialize();
|
||||||
|
|
||||||
|
EXPECT_NE(manager.GetLoader(ResourceType::Material), nullptr);
|
||||||
|
EXPECT_NE(manager.GetLoader(ResourceType::Shader), nullptr);
|
||||||
|
|
||||||
|
manager.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
|
||||||
|
const std::filesystem::path shaderPath =
|
||||||
|
std::filesystem::current_path() / "material_loader_valid_shader.hlsl";
|
||||||
|
const std::filesystem::path materialPath =
|
||||||
|
std::filesystem::current_path() / "material_loader_valid.material";
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream shaderFile(shaderPath);
|
||||||
|
ASSERT_TRUE(shaderFile.is_open());
|
||||||
|
shaderFile << "float4 MainPS() : SV_TARGET { return float4(1, 1, 1, 1); }";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream materialFile(materialPath);
|
||||||
|
ASSERT_TRUE(materialFile.is_open());
|
||||||
|
materialFile << "{\n";
|
||||||
|
materialFile << " \"shader\": \"" << shaderPath.generic_string() << "\",\n";
|
||||||
|
materialFile << " \"renderQueue\": \"Transparent\",\n";
|
||||||
|
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
|
||||||
|
materialFile << " \"tags\": {\n";
|
||||||
|
materialFile << " \"LightMode\": \"ForwardBase\",\n";
|
||||||
|
materialFile << " \"RenderType\": \"Transparent\"\n";
|
||||||
|
materialFile << " }\n";
|
||||||
|
materialFile << "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialLoader loader;
|
||||||
|
LoadResult result = loader.Load(materialPath.string().c_str());
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
ASSERT_NE(result.resource, nullptr);
|
||||||
|
|
||||||
|
Material* material = static_cast<Material*>(result.resource);
|
||||||
|
ASSERT_NE(material, nullptr);
|
||||||
|
EXPECT_TRUE(material->IsValid());
|
||||||
|
EXPECT_NE(material->GetShader(), nullptr);
|
||||||
|
EXPECT_EQ(material->GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
|
||||||
|
EXPECT_EQ(material->GetShaderPass(), "ForwardLit");
|
||||||
|
EXPECT_EQ(material->GetTag("LightMode"), "ForwardBase");
|
||||||
|
EXPECT_EQ(material->GetTag("RenderType"), "Transparent");
|
||||||
|
|
||||||
|
delete material;
|
||||||
|
std::remove(materialPath.string().c_str());
|
||||||
|
std::remove(shaderPath.string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MaterialLoader, RejectsUnknownRenderQueueName) {
|
||||||
|
const std::filesystem::path materialPath =
|
||||||
|
std::filesystem::current_path() / "material_loader_invalid_queue.material";
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream materialFile(materialPath);
|
||||||
|
ASSERT_TRUE(materialFile.is_open());
|
||||||
|
materialFile << "{ \"renderQueue\": \"NotAQueue\" }";
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialLoader loader;
|
||||||
|
LoadResult result = loader.Load(materialPath.string().c_str());
|
||||||
|
EXPECT_FALSE(result);
|
||||||
|
|
||||||
|
std::remove(materialPath.string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user