aboutsummaryrefslogtreecommitdiff
path: root/source/Material.cpp
diff options
context:
space:
mode:
authorhnOsmium0001 <[email protected]>2022-04-15 20:30:39 -0700
committerhnOsmium0001 <[email protected]>2022-04-15 20:30:39 -0700
commit509201784d6525fc26345e55a56ab81e4a7616b3 (patch)
treebcd68f247937324d06480b58a284b47e1c6bb2b8 /source/Material.cpp
parent989f90ebe2c37e8a517691a35d7e0d827fbe7006 (diff)
Work on Material system
Diffstat (limited to 'source/Material.cpp')
-rw-r--r--source/Material.cpp362
1 files changed, 352 insertions, 10 deletions
diff --git a/source/Material.cpp b/source/Material.cpp
index 138434c..db76b21 100644
--- a/source/Material.cpp
+++ b/source/Material.cpp
@@ -1,15 +1,43 @@
#include "Material.hpp"
+#include "AppConfig.hpp"
+#include "RapidJsonHelper.hpp"
+#include "ScopeGuard.hpp"
+#include "Utils.hpp"
+
+#include <rapidjson/document.h>
+#include <rapidjson/filereadstream.h>
+#include <rapidjson/filewritestream.h>
+#include <rapidjson/writer.h>
#include <cstdlib>
#include <cstring>
+#include <utility>
+
+namespace fs = std::filesystem;
+using namespace std::literals;
-Material::Material(Shader* shader)
- : mShader(shader) {
+Material::Material(Shader* shader, std::string name)
+ : mShader(shader)
+ , mName(std::move(name)) {
}
namespace ProjectBrussel_UNITY_ID {
+bool TryFindShaderId(Shader* shader, std::string_view name, int& out) {
+ auto info = shader->GetInfo();
+ if (!info) return false;
+
+ auto iter = info->things.find(name);
+ if (iter == info->things.end()) return false;
+ auto& id = iter->second;
+
+ if (id.kind != ShaderThingId::KD_Uniform) return false;
+
+ out = id.index;
+ return true;
+}
+
template <class TUniform>
-TUniform& ObtainUniform(std::vector<TUniform>& uniforms, GLint location) {
+TUniform& ObtainUniform(Shader* shader, const char* name, std::vector<TUniform>& uniforms, GLint location) {
for (auto& uniform : uniforms) {
if (uniform.location == location) {
return uniform;
@@ -18,37 +46,114 @@ TUniform& ObtainUniform(std::vector<TUniform>& uniforms, GLint location) {
auto& uniform = uniforms.emplace_back();
uniform.location = location;
+ if (!TryFindShaderId(shader, name, uniform.infoUniformIndex)) {
+ uniform.infoUniformIndex = -1;
+ }
+
return uniform;
}
+
+rapidjson::Value MakeVectorJson(const Material::VectorUniform& vector, rapidjson::Document& root) {
+ int len = vector.actualLength;
+
+ rapidjson::Value result(rapidjson::kArrayType);
+ result.Reserve(len, root.GetAllocator());
+
+ for (int i = 0; i < len; ++i) {
+ result.PushBack(vector.value[i], root.GetAllocator());
+ }
+
+ return result;
+}
+
+Material::VectorUniform ReadVectorFromJson(const rapidjson::Value& rv) {
+ assert(rv.IsArray());
+ Material::VectorUniform result;
+ int len = result.actualLength = rv.Size();
+ for (int i = 0; i < len; ++i) {
+ result.value[i] = rv[i].GetFloat();
+ }
+ return result;
+}
+
+rapidjson::Value MakeMatrixJson(const Material::MatrixUniform& matrix, rapidjson::Document& root) {
+ int w = matrix.actualWidth;
+ int h = matrix.actualHeight;
+
+ rapidjson::Value result(rapidjson::kArrayType);
+ result.Reserve(h, root.GetAllocator());
+
+ for (int y = 0; y < h; ++y) {
+ rapidjson::Value row(rapidjson::kArrayType);
+ row.Reserve(w, root.GetAllocator());
+
+ for (int x = 0; x < w; ++x) {
+ // Each item in a column is consecutive in memory in glm::mat<> structs
+ row.PushBack(matrix.value[x * h + y], root.GetAllocator());
+ }
+
+ result.PushBack(row, root.GetAllocator());
+ }
+
+ return result;
+}
+
+Material::MatrixUniform ReadMatrixFromjson(const rapidjson::Value& rv) {
+ assert(rv.IsArray());
+ assert(rv.Size() > 0);
+ assert(rv[0].IsArray());
+ Material::MatrixUniform result;
+ int w = result.actualWidth = rv[0].Size();
+ int h = result.actualHeight = rv.Size();
+ for (int y = 0; y < h; ++y) {
+ auto& row = rv[y];
+ assert(row.IsArray());
+ assert(row.Size() == w);
+ for (int x = 0; x < w; ++x) {
+ auto& val = row[x];
+ assert(val.IsFloat());
+ result.value[x * h + y] = val.GetFloat();
+ }
+ }
+ return result;
+}
} // namespace ProjectBrussel_UNITY_ID
void Material::SetFloat(const char* name, float value) {
+ assert(IsValid());
+
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
- auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location);
uniform.floatValue = value;
uniform.actualType = GL_FLOAT;
}
void Material::SetInt(const char* name, int32_t value) {
+ assert(IsValid());
+
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
- auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location);
uniform.intValue = value;
uniform.actualType = GL_INT;
}
void Material::SetUInt(const char* name, uint32_t value) {
+ assert(IsValid());
+
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
- auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundScalars, location);
uniform.uintValue = value;
uniform.actualType = GL_UNSIGNED_INT;
}
template <int length>
void Material::SetVector(const char* name, const glm::vec<length, float>& vec) {
+ assert(IsValid());
+
static_assert(length >= 1 && length <= 4);
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
- auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundVectors, location);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundVectors, location);
uniform.actualLength = length;
std::memset(uniform.value, 0, sizeof(uniform.value));
std::memcpy(uniform.value, &vec[0], length * sizeof(float));
@@ -65,7 +170,7 @@ void Material::SetMatrix(const char* name, const glm::mat<width, height, float>&
static_assert(height >= 1 && height <= 4);
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
- auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundMatrices, location);
+ auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mShader.Get(), name, mBoundMatrices, location);
uniform.actualWidth = width;
uniform.actualHeight = height;
std::memset(uniform.value, 0, sizeof(uniform.value));
@@ -86,6 +191,8 @@ template void Material::SetMatrix<3, 4>(const char*, const glm::mat<3, 4, float>
template void Material::SetMatrix<4, 3>(const char*, const glm::mat<4, 3, float>&);
void Material::SetTexture(const char* name, Texture* texture) {
+ assert(IsValid());
+
GLint location = glGetUniformLocation(mShader->GetProgram(), name);
for (auto& uniform : mBoundTextures) {
@@ -112,8 +219,25 @@ std::span<const Material::TextureUniform> Material::GetTextures() const {
return mBoundTextures;
}
-const Shader& Material::GetShader() const {
- return *mShader;
+Shader* Material::GetShader() const {
+ return mShader.Get();
+}
+
+void Material::SetShader(Shader* shader) {
+ // TODO validate uniforms?
+ mShader.Attach(shader);
+}
+
+const std::string& Material::GetName() const {
+ return mName;
+}
+
+bool Material::IsAnnoymous() const {
+ return mName.empty();
+}
+
+bool Material::IsValid() const {
+ return mShader != nullptr;
}
static constexpr int IdentifyMatrixSize(int width, int height) {
@@ -167,3 +291,221 @@ void Material::UseUniforms() const {
++i;
}
}
+
+void Material::GetDesignatedPath(char* buffer, int bufferSize) {
+ snprintf(buffer, bufferSize, "%s/Materials/%s.json", AppConfig::assetDir.c_str(), mName.c_str());
+}
+
+fs::path Material::GetDesignatedPath() {
+ return AppConfig::assetDirPath / "Materials" / fs::path(mName).replace_extension(".json");
+}
+
+bool Material::SaveToFile(const fs::path& filePath) const {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ if (IsAnnoymous()) return false;
+ if (!IsValid()) return false;
+
+ auto info = mShader->GetInfo();
+
+ rapidjson::Document root(rapidjson::kObjectType);
+
+ root.AddMember("Name", mName, root.GetAllocator());
+ root.AddMember("ShaderName", mShader->GetName(), root.GetAllocator());
+
+ rapidjson::Value fields(rapidjson::kArrayType);
+ for (auto& scalar : mBoundScalars) {
+ rapidjson::Value rvField(rapidjson::kObjectType);
+ if (info) rvField.AddMember("Name", info->uniforms[scalar.infoUniformIndex]->name, root.GetAllocator());
+ rvField.AddMember("Type", "Scalar", root.GetAllocator());
+ switch (scalar.actualType) {
+ case GL_FLOAT: rvField.AddMember("Value", scalar.floatValue, root.GetAllocator()); break;
+ case GL_INT: rvField.AddMember("Value", scalar.intValue, root.GetAllocator()); break;
+ case GL_UNSIGNED_INT: rvField.AddMember("Value", scalar.uintValue, root.GetAllocator()); break;
+ }
+ fields.PushBack(rvField, root.GetAllocator());
+ }
+ for (auto& vector : mBoundVectors) {
+ rapidjson::Value rvField(rapidjson::kObjectType);
+ if (info) rvField.AddMember("Name", info->uniforms[vector.infoUniformIndex]->name, root.GetAllocator());
+ rvField.AddMember("Type", "Vector", root.GetAllocator());
+ rvField.AddMember("Value", MakeVectorJson(vector, root).Move(), root.GetAllocator());
+ fields.PushBack(rvField, root.GetAllocator());
+ }
+ for (auto& matrix : mBoundMatrices) {
+ rapidjson::Value rvField(rapidjson::kObjectType);
+ if (info) rvField.AddMember("Name", info->uniforms[matrix.infoUniformIndex]->name, root.GetAllocator());
+ rvField.AddMember("Type", "Matrix", root.GetAllocator());
+ rvField.AddMember("Value", MakeMatrixJson(matrix, root).Move(), root.GetAllocator());
+ fields.PushBack(rvField, root.GetAllocator());
+ }
+ for (auto& texture : mBoundTextures) {
+ // TODO
+ }
+ root.AddMember("Fields", fields, root.GetAllocator());
+
+ auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate);
+ if (!file) return false;
+ DEFER { fclose(file); };
+
+ char writerBuffer[65536];
+ rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer));
+ rapidjson::Writer<rapidjson::FileWriteStream> writer(stream);
+ root.Accept(writer);
+
+ return true;
+}
+
+bool Material::LoadFromFile(const fs::path& filePath) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ auto file = Utils::OpenCstdioFile(filePath, Utils::Read);
+ if (!file) return false;
+ DEFER { fclose(file); };
+
+ char readerBuffer[65536];
+ rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer));
+
+ rapidjson::Document root;
+ root.ParseStream(stream);
+
+ // TODO update reference in MaterialManager
+ {
+ auto rvName = rapidjson::GetProperty(root, rapidjson::kStringType, "Name"sv);
+ if (rvName) {
+ mName = rapidjson::AsString(*rvName);
+ } else {
+ mName = "";
+ }
+ }
+
+ {
+ auto rvShaderName = rapidjson::GetProperty(root, rapidjson::kStringType, "ShaderName"sv);
+ if (!rvShaderName) return false;
+
+ auto shader = ShaderManager::instance->FindShader(rapidjson::AsStringView(*rvShaderName));
+ if (!shader) return false;
+
+ mShader.Attach(shader);
+ }
+
+ auto fields = rapidjson::GetProperty(root, rapidjson::kArrayType, "Fields"sv);
+ if (!fields) return false;
+
+ for (auto& rvField : fields->GetArray()) {
+ if (!rvField.IsObject()) continue;
+
+ auto rvName = rapidjson::GetProperty(rvField, rapidjson::kStringType, "Name"sv);
+
+ auto rvType = rapidjson::GetProperty(rvField, rapidjson::kStringType, "Type"sv);
+ if (!rvType) continue;
+ auto type = rapidjson::AsStringView(*rvType);
+
+ auto rvValue = rapidjson::GetProperty(rvField, "Value"sv);
+
+ if (type == "Scalar"sv) {
+ ScalarUniform uniform;
+ if (rvName) {
+ TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex);
+ }
+ if (rvValue->IsFloat()) {
+ uniform.actualType = GL_FLOAT;
+ uniform.floatValue = rvValue->GetFloat();
+ } else if (rvValue->IsInt()) {
+ uniform.actualType = GL_INT;
+ uniform.intValue = rvValue->GetInt();
+ } else if (rvValue->IsUint()) {
+ uniform.actualType = GL_UNSIGNED_INT;
+ uniform.uintValue = rvValue->GetUint();
+ }
+ mBoundScalars.push_back(std::move(uniform));
+ } else if (type == "Vector"sv) {
+ auto uniform = ReadVectorFromJson(*rvValue);
+ if (rvName) {
+ TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex);
+ }
+ mBoundVectors.push_back(std::move(uniform));
+ } else if (type == "Matrix"sv) {
+ auto uniform = ReadMatrixFromjson(*rvValue);
+ if (rvName) {
+ TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex);
+ }
+ mBoundMatrices.push_back(uniform);
+ } else if (type == "Texture"sv) {
+ // TODO
+ }
+ }
+
+ return true;
+}
+
+void MaterialManager::DiscoverMaterials() {
+ mMaterials.clear();
+
+ auto path = AppConfig::assetDirPath / "Materials";
+ if (!fs::exists(path)) {
+ return;
+ }
+
+ for (auto& item : fs::directory_iterator(path)) {
+ if (item.is_regular_file()) {
+ RcPtr mat(new Material());
+ if (!mat->LoadFromFile(item.path())) {
+ continue;
+ }
+
+ auto& matName = mat->GetName();
+ mMaterials.try_emplace(matName, std::move(mat));
+ }
+ }
+}
+
+std::pair<Material*, bool> MaterialManager::SaveMaterial(Material* mat) {
+ // NOTE: we explicitly allow invalid materials (i.e. without a shader)
+ if (mat->IsAnnoymous()) return { nullptr, false };
+
+ auto [iter, inserted] = mMaterials.try_emplace(mat->GetName(), mat);
+ if (inserted) {
+ mat->SaveToFile(mat->GetDesignatedPath());
+ return { mat, true };
+ } else {
+ return { iter->second.Get(), false };
+ }
+}
+
+void MaterialManager::DeleteMaterial(Material* mat, bool onDisk) {
+ // TODO
+ assert(false && "unimplemented");
+}
+
+Material* MaterialManager::LoadMaterial(std::string_view name) {
+ // TODO
+ assert(false && "unimplemented");
+}
+
+bool MaterialManager::RenameMaterial(Material* mat, std::string newName) {
+ if (mMaterials.contains(newName)) {
+ return false;
+ }
+
+ // Keep the material from being deleted, in case the old entry in map is the only one existing
+ RcPtr rc(mat);
+
+ // Remove old entry (must do before replacing Material::mName, because the std::string_view in the map is a reference to it)
+ mMaterials.erase(mat->GetName());
+
+ // Add new entry
+ mat->mName = std::move(newName);
+ mMaterials.try_emplace(mat->GetName(), mat);
+
+ return true;
+}
+
+Material* MaterialManager::FindMaterial(std::string_view name) {
+ auto iter = mMaterials.find(name);
+ if (iter != mMaterials.end()) {
+ return iter->second.Get();
+ } else {
+ return nullptr;
+ }
+}