Add material render metadata and loader parsing
This commit is contained in:
@@ -14,6 +14,14 @@
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
enum class MaterialRenderQueue : Core::int32 {
|
||||
Background = 1000,
|
||||
Geometry = 2000,
|
||||
AlphaTest = 2450,
|
||||
Transparent = 3000,
|
||||
Overlay = 4000
|
||||
};
|
||||
|
||||
enum class MaterialPropertyType {
|
||||
Float, Float2, Float3, Float4,
|
||||
Int, Int2, Int3, Int4,
|
||||
@@ -52,6 +60,20 @@ public:
|
||||
|
||||
void SetShader(const ResourceHandle<class Shader>& shader);
|
||||
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 SetFloat2(const Containers::String& name, const Math::Vector2& value);
|
||||
@@ -72,7 +94,8 @@ public:
|
||||
|
||||
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
|
||||
void UpdateConstantBuffer();
|
||||
|
||||
void RecalculateMemorySize();
|
||||
|
||||
bool HasProperty(const Containers::String& name) const;
|
||||
void RemoveProperty(const Containers::String& name);
|
||||
void ClearAllProperties();
|
||||
@@ -81,6 +104,13 @@ private:
|
||||
void UpdateMemorySize();
|
||||
|
||||
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::Array<Core::uint8> m_constantBufferData;
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
namespace XCEngine {
|
||||
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() {
|
||||
static ResourceManager instance;
|
||||
return instance;
|
||||
@@ -13,6 +29,9 @@ ResourceManager& ResourceManager::Get() {
|
||||
void ResourceManager::Initialize() {
|
||||
m_asyncLoader = Core::MakeUnique<AsyncLoader>();
|
||||
m_asyncLoader->Initialize(2);
|
||||
|
||||
RegisterBuiltinLoader(*this, g_materialLoader);
|
||||
RegisterBuiltinLoader(*this, g_shaderLoader);
|
||||
}
|
||||
|
||||
void ResourceManager::Shutdown() {
|
||||
|
||||
@@ -11,6 +11,9 @@ Material::~Material() = default;
|
||||
|
||||
void Material::Release() {
|
||||
m_shader.Reset();
|
||||
m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||
m_shaderPass.Clear();
|
||||
m_tags.Clear();
|
||||
m_properties.Clear();
|
||||
m_textureBindings.Clear();
|
||||
m_constantBufferData.Clear();
|
||||
@@ -23,6 +26,73 @@ void Material::SetShader(const ResourceHandle<Shader>& shader) {
|
||||
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) {
|
||||
MaterialProperty prop;
|
||||
prop.name = name;
|
||||
@@ -175,6 +245,10 @@ void Material::UpdateConstantBuffer() {
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::RecalculateMemorySize() {
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
bool Material::HasProperty(const Containers::String& name) const {
|
||||
return m_properties.Contains(name);
|
||||
}
|
||||
@@ -193,11 +267,18 @@ void Material::ClearAllProperties() {
|
||||
|
||||
void Material::UpdateMemorySize() {
|
||||
m_memorySize = m_constantBufferData.Size() +
|
||||
m_shaderPass.Length() +
|
||||
m_tags.Size() * sizeof(TagEntry) +
|
||||
m_textureBindings.Size() * sizeof(TextureBinding) +
|
||||
m_properties.Size() * sizeof(MaterialProperty) +
|
||||
m_name.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) {
|
||||
m_memorySize += binding.name.Length();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,256 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
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;
|
||||
@@ -23,41 +269,25 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
}
|
||||
|
||||
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
(void)settings;
|
||||
|
||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||
if (data.Empty()) {
|
||||
return LoadResult("Failed to read material file: " + path);
|
||||
}
|
||||
|
||||
|
||||
Material* material = new Material();
|
||||
material->m_path = path;
|
||||
material->m_name = path;
|
||||
material->m_guid = ResourceGUID::Generate(path);
|
||||
|
||||
Containers::String jsonStr;
|
||||
jsonStr.Reserve(data.Size());
|
||||
for (size_t i = 0; i < data.Size(); ++i) {
|
||||
jsonStr += static_cast<char>(data[i]);
|
||||
|
||||
if (!ParseMaterialData(data, material)) {
|
||||
delete material;
|
||||
return LoadResult("Failed to parse material file: " + path);
|
||||
}
|
||||
|
||||
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_memorySize = sizeof(Material) + material->m_name.Length() + material->m_path.Length();
|
||||
|
||||
material->RecalculateMemorySize();
|
||||
return LoadResult(material);
|
||||
}
|
||||
|
||||
@@ -66,6 +296,59 @@ ImportSettings* MaterialLoader::GetDefaultSettings() const {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user