diff options
author | rtk0c <[email protected]> | 2023-10-19 22:50:07 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2025-08-16 11:31:16 -0700 |
commit | 297232d21594b138bb368a42b5b0d085ff9ed6aa (patch) | |
tree | 075d5407e1e12a9d35cbee6e4c20ad34e0765c42 /src/brussel.engine/Material.cpp | |
parent | d5cd34ff69f7fd134d5450696f298af1a864afbc (diff) |
The great renaming: switch to "module style"
Diffstat (limited to 'src/brussel.engine/Material.cpp')
-rw-r--r-- | src/brussel.engine/Material.cpp | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/src/brussel.engine/Material.cpp b/src/brussel.engine/Material.cpp new file mode 100644 index 0000000..4443ae5 --- /dev/null +++ b/src/brussel.engine/Material.cpp @@ -0,0 +1,526 @@ +#include "Material.hpp" + +#include "AppConfig.hpp" +#include "EditorCore.hpp" +#include "EditorUtils.hpp" + +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <imgui.h> +#include <rapidjson/document.h> +#include <cstdlib> +#include <cstring> +#include <utility> + +using namespace std::literals; + +Material::Material() { +} + +namespace ProjectBrussel_UNITY_ID { +bool TryFindShaderId(Shader* shader, std::string_view name, int& out) { + auto& info = shader->GetInfo(); + 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 <typename TUniform> +TUniform& ObtainUniform(Shader* shader, const char* name, std::vector<TUniform>& uniforms, GLint location) { + for (auto& uniform : uniforms) { + if (uniform.location == location) { + return uniform; + } + } + + 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.IsNumber()); + 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(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(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(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(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)); +} + +template void Material::SetVector<1>(const char*, const glm::vec<1, float>&); +template void Material::SetVector<2>(const char*, const glm::vec<2, float>&); +template void Material::SetVector<3>(const char*, const glm::vec<3, float>&); +template void Material::SetVector<4>(const char*, const glm::vec<4, float>&); + +template <int width, int height> +void Material::SetMatrix(const char* name, const glm::mat<width, height, float>& mat) { + static_assert(width >= 1 && width <= 4); + static_assert(height >= 1 && height <= 4); + + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + 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)); + std::memcpy(uniform.value, &mat[0][0], width * height * sizeof(float)); +} + +template void Material::SetMatrix<2, 2>(const char*, const glm::mat<2, 2, float>&); +template void Material::SetMatrix<3, 3>(const char*, const glm::mat<3, 3, float>&); +template void Material::SetMatrix<4, 4>(const char*, const glm::mat<4, 4, float>&); + +template void Material::SetMatrix<2, 3>(const char*, const glm::mat<2, 3, float>&); +template void Material::SetMatrix<3, 2>(const char*, const glm::mat<3, 2, float>&); + +template void Material::SetMatrix<2, 4>(const char*, const glm::mat<2, 4, float>&); +template void Material::SetMatrix<4, 2>(const char*, const glm::mat<4, 2, float>&); + +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) { + if (uniform.location == location) { + uniform.value.Attach(texture); + return; + } + } + + auto& uniform = mBoundTextures.emplace_back(); + uniform.value.Attach(texture); + uniform.location = location; +} + +std::span<const Material::VectorUniform> Material::GetVectors() const { + return mBoundVectors; +} + +std::span<const Material::MatrixUniform> Material::GetMatrices() const { + return mBoundMatrices; +} + +std::span<const Material::TextureUniform> Material::GetTextures() const { + return mBoundTextures; +} + +Shader* Material::GetShader() const { + return mShader.Get(); +} + +void Material::SetShader(Shader* shader) { + mShader.Attach(shader); + auto& info = shader->GetInfo(); + + mBoundScalars.clear(); + mBoundVectors.clear(); + mBoundMatrices.clear(); + mBoundTextures.clear(); + for (int i = 0; i < info.uniforms.size(); ++i) { + auto& decl = info.uniforms[i]; + switch (decl->kind) { + case ShaderVariable::KD_Math: { + auto& mathDecl = static_cast<ShaderMathVariable&>(*decl); + if (mathDecl.width == 1) { + if (mathDecl.height == 1) { + // Scalar + auto& scalar = mBoundScalars.emplace_back(); + scalar.location = decl->location; + scalar.infoUniformIndex = i; + } else { + // Vector + auto& vec = mBoundVectors.emplace_back(); + vec.location = decl->location; + vec.infoUniformIndex = i; + vec.actualLength = mathDecl.height; + } + } else { + // Matrix + auto& mat = mBoundMatrices.emplace_back(); + mat.location = decl->location; + mat.infoUniformIndex = i; + mat.actualWidth = mathDecl.width; + mat.actualHeight = mathDecl.height; + } + } break; + + case ShaderVariable::KD_Sampler: { + auto& uniform = mBoundTextures.emplace_back(); + uniform.location = decl->location; + uniform.infoUniformIndex = i; + } break; + } + } +} + +bool Material::IsValid() const { + return mShader != nullptr; +} + +static constexpr int IdentifyMatrixSize(int width, int height) { + return width * 10 + height; +} + +void Material::UseUniforms() const { + for (auto& uniform : mBoundScalars) { + switch (uniform.actualType) { + case GL_FLOAT: glUniform1f(uniform.location, uniform.intValue); break; + case GL_INT: glUniform1i(uniform.location, uniform.intValue); break; + case GL_UNSIGNED_INT: glUniform1ui(uniform.location, uniform.intValue); break; + default: break; + } + } + + for (auto& uniform : mBoundVectors) { + switch (uniform.actualLength) { + case 1: glUniform1fv(uniform.location, 1, &uniform.value[0]); break; + case 2: glUniform2fv(uniform.location, 1, &uniform.value[0]); break; + case 3: glUniform3fv(uniform.location, 1, &uniform.value[0]); break; + case 4: glUniform4fv(uniform.location, 1, &uniform.value[0]); break; + default: break; + } + } + + for (auto& uniform : mBoundMatrices) { + switch (IdentifyMatrixSize(uniform.actualWidth, uniform.actualHeight)) { + case IdentifyMatrixSize(2, 2): glUniformMatrix2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(3, 3): glUniformMatrix3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 4): glUniformMatrix4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(2, 3): glUniformMatrix2x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(3, 2): glUniformMatrix3x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(2, 4): glUniformMatrix2x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 2): glUniformMatrix4x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(3, 4): glUniformMatrix3x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 3): glUniformMatrix4x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + default: break; + } + } + + int i = 0; + for (auto& uniform : mBoundTextures) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, uniform.value->GetHandle()); + glUniform1i(uniform.location, i); + ++i; + } +} + +IresMaterial::IresMaterial() + : IresObject(KD_Material) + , mInstance(new Material()) { + mInstance->mIres = this; +} + +Material* IresMaterial::GetInstance() const { + return mInstance.Get(); +} + +void IresMaterial::InvalidateInstance() { + if (mInstance != nullptr) { + mInstance->mIres = nullptr; + } + mInstance.Attach(new Material()); + mInstance->mIres = this; +} + +void IresMaterial::ShowEditor(IEditor& editor) { + using namespace Tags; + + IresObject::ShowEditor(editor); + + auto shader = mInstance->GetShader(); + if (shader) { + shader->GetIres()->ShowReference(editor); + } else { + IresObject::ShowReferenceNull(editor); + } + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(KD_Shader).data())) { + auto shader = *static_cast<IresShader* const*>(payload->Data); + mInstance->SetShader(shader->GetInstance()); + } + ImGui::EndDragDropTarget(); + } + + if (!shader) return; + auto& shaderInfo = shader->GetInfo(); + auto shaderIres = shader->GetIres(); + + for (auto& field : mInstance->mBoundScalars) { + auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + ImGui::Indent(); + switch (decl.scalarType) { + case GL_FLOAT: ImGui::InputFloat("##", &field.floatValue); break; + case GL_INT: ImGui::InputInt("##", &field.intValue); break; + // TODO proper uint edit? + case GL_UNSIGNED_INT: ImGui::InputInt("##", (int32_t*)(&field.uintValue), 0, std::numeric_limits<int32_t>::max()); break; + default: ImGui::TextUnformatted("Unsupported scalar type"); break; + } + ImGui::Unindent(); + } + for (auto& field : mInstance->mBoundVectors) { + auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + ImGui::Indent(); + switch (decl.semantic) { + case VES_Color1: + case VES_Color2: { + ImGui::ColorEdit4("##", field.value); + } break; + + default: { + ImGui::InputFloat4("##", field.value); + } break; + } + ImGui::Unindent(); + } + for (auto& field : mInstance->mBoundMatrices) { + auto& decl = static_cast<ShaderMathVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + // TODO + } + for (auto& field : mInstance->mBoundTextures) { + auto& decl = static_cast<ShaderSamplerVariable&>(*shaderInfo.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + // TODO + } +} + +void IresMaterial::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { + using namespace ProjectBrussel_UNITY_ID; + + IresObject::Write(ctx, value, root); + + if (!mInstance->IsValid()) { + return; + } + + auto& shaderInfo = mInstance->mShader->GetInfo(); + auto shaderUid = mInstance->mShader->GetIres()->GetUid(); + value.AddMember("Shader", shaderUid.Write(root), root.GetAllocator()); + + rapidjson::Value fields(rapidjson::kArrayType); + for (auto& scalar : mInstance->mBoundScalars) { + rapidjson::Value rvField(rapidjson::kObjectType); + rvField.AddMember("Name", shaderInfo.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 : mInstance->mBoundVectors) { + rapidjson::Value rvField(rapidjson::kObjectType); + rvField.AddMember("Name", shaderInfo.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 : mInstance->mBoundMatrices) { + rapidjson::Value rvField(rapidjson::kObjectType); + rvField.AddMember("Name", shaderInfo.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 : mInstance->mBoundTextures) { + // TODO + } + value.AddMember("Fields", fields, root.GetAllocator()); +} + +void IresMaterial::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { + using namespace ProjectBrussel_UNITY_ID; + + IresObject::Read(ctx, value); + + { + auto rvShader = rapidjson::GetProperty(value, "Shader"sv); + if (!rvShader) return; + + Uid uid; + uid.Read(*rvShader); + + auto ires = ctx.FindIres(uid); + if (!ires) return; + if (ires->GetKind() != KD_Shader) return; + auto shader = static_cast<IresShader*>(ires); + + mInstance->mShader.Attach(shader->GetInstance()); + } + auto shader = mInstance->mShader.Get(); + auto& shaderInfo = shader->GetInfo(); + + auto fields = rapidjson::GetProperty(value, rapidjson::kArrayType, "Fields"sv); + if (!fields) return; + + 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) { + Material::ScalarUniform uniform; + if (rvName) { + TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; + } + 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(); + } + mInstance->mBoundScalars.push_back(std::move(uniform)); + } else if (type == "Vector"sv) { + auto uniform = ReadVectorFromJson(*rvValue); + if (rvName) { + TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; + } + mInstance->mBoundVectors.push_back(std::move(uniform)); + } else if (type == "Matrix"sv) { + auto uniform = ReadMatrixFromjson(*rvValue); + if (rvName) { + TryFindShaderId(shader, rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; + } + mInstance->mBoundMatrices.push_back(uniform); + } else if (type == "Texture"sv) { + // TODO + } + } +} |