diff options
Diffstat (limited to 'source/Material.cpp')
-rw-r--r-- | source/Material.cpp | 362 |
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; + } +} |