diff options
-rw-r--r-- | 3rdparty/glfw/source/src/x11_window.c | 127 | ||||
-rw-r--r-- | assets/Materials/M_Default.json | 1 | ||||
-rw-r--r-- | source/EditorAttachmentImpl.hpp | 7 | ||||
-rw-r--r-- | source/EditorCore.cpp | 141 | ||||
-rw-r--r-- | source/EditorCore.hpp | 2 | ||||
-rw-r--r-- | source/EditorResources.cpp | 83 | ||||
-rw-r--r-- | source/EditorResources.hpp | 3 | ||||
-rw-r--r-- | source/EditorUtils.cpp | 33 | ||||
-rw-r--r-- | source/EditorUtils.hpp | 7 | ||||
-rw-r--r-- | source/Material.cpp | 362 | ||||
-rw-r--r-- | source/Material.hpp | 61 | ||||
-rw-r--r-- | source/RapidJsonHelper.hpp | 11 | ||||
-rw-r--r-- | source/ScopeGuard.hpp | 7 | ||||
-rw-r--r-- | source/Shader.cpp | 50 | ||||
-rw-r--r-- | source/Shader.hpp | 39 | ||||
-rw-r--r-- | source/Texture.cpp | 13 | ||||
-rw-r--r-- | source/Texture.hpp | 15 | ||||
-rw-r--r-- | source/main.cpp | 75 |
18 files changed, 879 insertions, 158 deletions
diff --git a/3rdparty/glfw/source/src/x11_window.c b/3rdparty/glfw/source/src/x11_window.c index 490abcc..5f1441f 100644 --- a/3rdparty/glfw/source/src/x11_window.c +++ b/3rdparty/glfw/source/src/x11_window.c @@ -1218,6 +1218,12 @@ static void processEvent(XEvent *event) case XI_KeyPress: { XIDeviceEvent* de = event->xcookie.data; + if (de->deviceid != de->sourceid) + { + // This event came from a master device + break; + } + Bool repeat = de->flags & XIKeyRepeat; _GLFWwindow* window = NULL; @@ -1229,13 +1235,89 @@ static void processEvent(XEvent *event) break; } + keycode = de->detail; const int key = translateKey(de->detail); const int mods = translateXI2Mods(&de->mods); + const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); // XIDeviceEvent::deviceid gives the final device in the redirection chain (which is usually the master device), // whereas XIDeviceEvent::sourceid gives the actual slave device that generated this event _GLFWkeyboard* keyboard = _glfwGetKeyboardFromDeviceIdX11(de->sourceid);// TODO(hnosm) do we need to handle XIM? - _glfwInputKey(window, keyboard, key, de->detail, repeat ? GLFW_REPEAT : GLFW_PRESS, mods); + + XKeyEvent fakeEvent; + fakeEvent.type = KeyPress; + fakeEvent.serial = de->serial; + fakeEvent.send_event = de->send_event; + fakeEvent.display = de->display; + fakeEvent.window = de->event; + fakeEvent.root = de->root; + fakeEvent.subwindow = de->child; + fakeEvent.time = de->time; + fakeEvent.x = de->event_x; + fakeEvent.y = de->event_y; + fakeEvent.x_root = de->root_x; + fakeEvent.y_root = de->root_y; + // NOTE: we assume XI2 mods and X event modifier state are thet same; this appears to be true but isn't documented anywhere + fakeEvent.state = de->mods.effective; + fakeEvent.keycode = de->detail; + fakeEvent.same_screen = 1; // TODO + + if (window->x11.ic) + { + // HACK: See processing of regular KeyPressed for details + Time diff = de->time - window->x11.keyPressTimes[keycode]; + if (diff == de->time || (diff > 0 && diff < ((Time)1 << 31))) + { + if (keycode) + _glfwInputKey(window, keyboard, key, keycode, repeat ? GLFW_REPEAT : GLFW_PRESS, mods); + + window->x11.keyPressTimes[keycode] = de->time; + } + + if (!filtered) + { + int count; + Status status; + char buffer[100]; + char* chars = buffer; + + count = Xutf8LookupString(window->x11.ic, + &fakeEvent, + buffer, sizeof(buffer) - 1, + NULL, &status); + + if (status == XBufferOverflow) + { + chars = _glfw_calloc(count + 1, 1); + count = Xutf8LookupString(window->x11.ic, + &fakeEvent, + chars, count, + NULL, &status); + } + + if (status == XLookupChars || status == XLookupBoth) + { + const char* c = chars; + chars[count] = '\0'; + while (c - chars < count) + _glfwInputChar(window, decodeUTF8(&c), mods, plain); + } + + if (chars != buffer) + _glfw_free(chars); + } + } + else + { + KeySym keysym; + XLookupString(&fakeEvent, NULL, 0, &keysym, NULL); + + _glfwInputKey(window, keyboard, key, keycode, repeat ? GLFW_REPEAT : GLFW_PRESS, mods); + + const uint32_t codepoint = _glfwKeySym2Unicode(keysym); + if (codepoint != GLFW_INVALID_CODEPOINT) + _glfwInputChar(window, codepoint, mods, plain); + } break; } @@ -1243,6 +1325,11 @@ static void processEvent(XEvent *event) case XI_KeyRelease: { XIDeviceEvent* de = event->xcookie.data; + if (de->deviceid != de->sourceid) + { + // This event came from a master device + break; + } _GLFWwindow* window = NULL; if (XFindContext(_glfw.x11.display, @@ -1384,30 +1471,30 @@ static void processEvent(XEvent *event) case KeyPress: { + // Non-XInput2 codepath, see above for handling of those + if (!_glfwKeyboardsSupportedX11()) + break; + const int key = translateKey(keycode); const int mods = translateState(event->xkey.state); const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); if (window->x11.ic) { - // Non-XInput2 codepath, see above for handling of those - if (!_glfwKeyboardsSupportedX11()) + // HACK: Do not report the key press events duplicated by XIM + // Duplicate key releases are filtered out implicitly by + // the GLFW key repeat logic in _glfwInputKey + // A timestamp per key is used to handle simultaneous keys + // NOTE: Always allow the first event for each key through + // (the server never sends a timestamp of zero) + // NOTE: Timestamp difference is compared to handle wrap-around + Time diff = event->xkey.time - window->x11.keyPressTimes[keycode]; + if (diff == event->xkey.time || (diff > 0 && diff < ((Time)1 << 31))) { - // HACK: Do not report the key press events duplicated by XIM - // Duplicate key releases are filtered out implicitly by - // the GLFW key repeat logic in _glfwInputKey - // A timestamp per key is used to handle simultaneous keys - // NOTE: Always allow the first event for each key through - // (the server never sends a timestamp of zero) - // NOTE: Timestamp difference is compared to handle wrap-around - Time diff = event->xkey.time - window->x11.keyPressTimes[keycode]; - if (diff == event->xkey.time || (diff > 0 && diff < ((Time)1 << 31))) - { - if (keycode) - _glfwInputKey(window, NULL, key, keycode, GLFW_PRESS, mods); + if (keycode) + _glfwInputKey(window, NULL, key, keycode, GLFW_PRESS, mods); - window->x11.keyPressTimes[keycode] = event->xkey.time; - } + window->x11.keyPressTimes[keycode] = event->xkey.time; } if (!filtered) @@ -1448,11 +1535,7 @@ static void processEvent(XEvent *event) KeySym keysym; XLookupString(&event->xkey, NULL, 0, &keysym, NULL); - // Non-XInput2 codepath, see above for handling of those - if (!_glfwKeyboardsSupportedX11()) - { - _glfwInputKey(window, NULL, key, keycode, GLFW_PRESS, mods); - } + _glfwInputKey(window, NULL, key, keycode, GLFW_PRESS, mods); const uint32_t codepoint = _glfwKeySym2Unicode(keysym); if (codepoint != GLFW_INVALID_CODEPOINT) diff --git a/assets/Materials/M_Default.json b/assets/Materials/M_Default.json new file mode 100644 index 0000000..87a467f --- /dev/null +++ b/assets/Materials/M_Default.json @@ -0,0 +1 @@ +{"Name":"M_Default","ShaderName":"Default","Fields":[]}
\ No newline at end of file diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp index 32618b1..cb96348 100644 --- a/source/EditorAttachmentImpl.hpp +++ b/source/EditorAttachmentImpl.hpp @@ -22,5 +22,10 @@ public: class EaShader : public EditorAttachment { public: Shader* shader; - std::string name; +}; + +class EaMaterial : public EditorAttachment { +public: + std::string editingScratch; + bool isEditingName = false; }; diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp index 7a77b26..1846962 100644 --- a/source/EditorCore.cpp +++ b/source/EditorCore.cpp @@ -16,6 +16,8 @@ #include <GLFW/glfw3.h> #include <ImGuizmo.h> +#include <imgui.h> +#include <misc/cpp/imgui_stdlib.h> #include <functional> #include <memory> #include <utility> @@ -36,6 +38,34 @@ void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { return false; }); } + +void ShowShaderName(const Shader* shader) { + if (shader) { + auto& name = shader->GetName(); + bool isAnnoymous = name.empty(); + if (isAnnoymous) { + ImGui::Text("Shader <annoymous at %p>", (void*)shader); + } else { + ImGui::Text("Shader: %s", name.c_str()); + } + } else { + ImGui::TextUnformatted("Shader: <null>"); + } +} + +void ShowMaterialName(const Material* material) { + if (material) { + auto& name = material->GetName(); + bool isAnnoymous = name.empty(); + if (isAnnoymous) { + ImGui::Text("Material: <annoymous at %p>", (void*)material); + } else { + ImGui::Text("Material: %s", name.c_str()); + } + } else { + ImGui::TextUnformatted("Material: <null>"); + } +} } // namespace ProjectBrussel_UNITY_ID EditorInstance::EditorInstance(App* app, GameWorld* world) @@ -66,6 +96,7 @@ void EditorInstance::Show() { switch (mSelectedItt) { case ITT_GameObject: ShowInspector(static_cast<GameObject*>(mSelectedItPtr)); break; case ITT_Shader: ShowInspector(static_cast<Shader*>(mSelectedItPtr)); break; + case ITT_Material: ShowInspector(static_cast<Material*>(mSelectedItPtr)); break; case ITT_None: break; } ImGui::End(); @@ -89,6 +120,15 @@ void EditorInstance::ShowInspector(Shader* shader) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; + EaShader* attachment; + if (auto ea = shader->GetEditorAttachment()) { + attachment = static_cast<EaShader*>(ea); + } else { + attachment = new EaShader(); + attachment->shader = shader; + shader->SetEditorAttachment(attachment); + } + auto info = shader->GetInfo(); if (!info) { ImGui::TextUnformatted("No info present for this shader."); @@ -103,26 +143,14 @@ void EditorInstance::ShowInspector(Shader* shader) { auto& name = shader->GetName(); bool isAnnoymous = name.empty(); - if (isAnnoymous) { - ImGui::Text("<Annoymous Shader at %p>", (void*)shader); - } else { - ImGui::Text("Name: %s", shader->GetName().c_str()); - } + ShowShaderName(shader); - // TODO use std::filesystem::path - auto GetMetadataPath = [&](char* path, int pathLength) { - snprintf(path, pathLength, "%s/Shaders/%s.json", AppConfig::assetDir.c_str(), shader->GetName().c_str()); - }; if (ImGui::Button("Reimport metadata", isAnnoymous)) { - char path[512]; - GetMetadataPath(path, sizeof(path)); - info->LoadFromFile(path); + info->LoadFromFile(shader->GetDesignatedMetadataPath()); } ImGui::SameLine(); if (ImGui::Button("Export metadata", isAnnoymous)) { - char path[512]; - GetMetadataPath(path, sizeof(path)); - info->SaveToFile(path); + info->SaveToFile(shader->GetDesignatedMetadataPath()); } auto ShowThing = [&](const std::vector<ShaderInfo::InputOutputThing>& things) { @@ -148,6 +176,89 @@ void EditorInstance::ShowInspector(Shader* shader) { } } +void EditorInstance::ShowInspector(Material* material) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + EaMaterial* attachment; + if (auto ea = material->GetEditorAttachment()) { + attachment = static_cast<EaMaterial*>(ea); + } else { + attachment = new EaMaterial(); + material->SetEditorAttachment(attachment); + } + + auto& name = material->GetName(); + bool isAnnoymous = name.empty(); + auto shader = material->GetShader(); + + if (isAnnoymous) { + ImGui::Text("<Annoymous Material at %p>", (void*)(&material)); + } else { + if (attachment->isEditingName) { + bool save = false; + save |= ImGui::InputText("##", &attachment->editingScratch, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::SameLine(); + save |= ImGui::Button("Save"); + if (save) { + bool success = MaterialManager::instance->RenameMaterial(material, attachment->editingScratch); + if (success) { + attachment->isEditingName = false; + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + attachment->isEditingName = false; + } + + ImGui::Text("%s", attachment->editingScratch.c_str()); + } else { + // NOTE: ReadOnly shouldn't write any data into the buffer + ImGui::InputText("##", material->mName.data(), name.size() + 1, ImGuiInputTextFlags_ReadOnly); + ImGui::SameLine(); + if (ImGui::Button("Edit")) { + attachment->editingScratch = name; // Copy + attachment->isEditingName = true; + } + } + } + + ShowShaderName(shader); + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_DRAG_DROP_SHADER)) { + auto shader = *static_cast<Shader* const*>(payload->Data); + material->SetShader(shader); + } + ImGui::EndDragDropTarget(); + } + ImGui::SameLine(); + if (ImGui::Button("GoTo", shader == nullptr)) { + mSelectedItt = ITT_Shader; + mSelectedItPtr = shader; + } + + if (ImGui::Button("Reload", isAnnoymous)) { + material->LoadFromFile(material->GetDesignatedPath()); + } + ImGui::SameLine(); + if (ImGui::Button("Save", isAnnoymous)) { + material->SaveToFile(material->GetDesignatedPath()); + } + + for (auto& field : material->mBoundScalars) { + // TODO + } + for (auto& field : material->mBoundVectors) { + // TODO + } + for (auto& field : material->mBoundMatrices) { + // TODO + } + for (auto& field : material->mBoundTextures) { + // TODO + } +} + void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index 28ad849..3d91fa6 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -14,6 +14,7 @@ public: enum InspectorTargetType { ITT_GameObject, ITT_Shader, + ITT_Material, ITT_None, }; @@ -39,6 +40,7 @@ private: void ShowWorldProperties(); void ShowInspector(Shader* shader); + void ShowInspector(Material* material); void ShowInspector(GameObject* object); void ShowGameObjecetFields(GameObject* object); diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp index c6d4d09..be7cefb 100644 --- a/source/EditorResources.cpp +++ b/source/EditorResources.cpp @@ -3,9 +3,18 @@ #include "EditorCore.hpp" #include "EditorNotification.hpp" #include "EditorUtils.hpp" +#include "Macros.hpp" +#include "ScopeGuard.hpp" #include "Shader.hpp" #include <imgui.h> +#include <misc/cpp/imgui_stdlib.h> +#include <cstdlib> +#include <limits> +#include <string> +#include <string_view> + +using namespace std::literals; EditorContentBrowser::EditorContentBrowser(EditorInstance* editor) : mEditor{ editor } { @@ -37,6 +46,9 @@ void EditorContentBrowser::Show(bool* open) { if (ImGui::Selectable("Shaders", mPane == P_Shader)) { mPane = P_Shader; } + if (ImGui::Selectable("Materials", mPane == P_Material)) { + mPane = P_Material; + } } ImGui::EndChild(); @@ -49,23 +61,82 @@ void EditorContentBrowser::Show(bool* open) { } break; case P_Shader: { - // TODO reload shaders while keeping existing references working - // if (ImGui::Button("Reload from disk")) { - // ShaderManager::instance->DiscoverShaders(); - // } + if (ImGui::Button("Refresh")) { + // TODO reload shaders while keeping existing references working + } + ImGui::SameLine(); + if (ImGui::Button("Save all")) { + auto& shaders = ShaderManager::instance->GetShaders(); + for (auto&& [DISCARD, shader] : shaders) { + auto info = shader->GetInfo(); + if (info) { + info->SaveToFile(shader->GetDesignatedMetadataPath()); + } + } + } auto& shaders = ShaderManager::instance->GetShaders(); for (auto it = shaders.begin(); it != shaders.end(); ++it) { - auto name = it->first; auto shader = it->second.Get(); + auto& name = shader->GetName(); shader->GatherInfoIfAbsent(); auto details = shader->GetInfo(); bool selected = mEditor->GetSelectedItPtr() == shader; - if (ImGui::Selectable(name.data(), selected)) { + if (ImGui::Selectable(name.c_str(), selected)) { mEditor->SelectIt(shader, EditorInstance::ITT_Shader); } + + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as Fpayload + ImGui::SetDragDropPayload(BRUSSEL_DRAG_DROP_SHADER, &shader, sizeof(shader)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("Shader '%s'", name.c_str()); + ImGui::EndDragDropSource(); + } + } + } break; + + case P_Material: { + if (ImGui::Button("New")) { + int n = std::rand(); + auto mat = new Material(nullptr, "Unnamed Material " + std::to_string(n)); + auto guard = GuardDeletion(mat); + auto [DISCARD, inserted] = MaterialManager::instance->SaveMaterial(mat); + if (inserted) { + guard.Dismiss(); + } else { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Error, "Failed to create material.")); + } + } + ImGui::SameLine(); + if (ImGui::Button("Refresh")) { + // TODO + } + ImGui::SameLine(); + if (ImGui::Button("Save all")) { + auto& mats = MaterialManager::instance->GetMaterials(); + for (auto&& [DISCARD, mat] : mats) { + mat->SaveToFile(mat->GetDesignatedPath()); + } + } + + auto& mats = MaterialManager::instance->GetMaterials(); + for (auto it = mats.begin(); it != mats.end(); ++it) { + auto mat = it->second.Get(); + auto& name = mat->GetName(); + + bool selected = mEditor->GetSelectedItPtr() == mat; + if (ImGui::Selectable(name.c_str(), selected)) { + mEditor->SelectIt(mat, EditorInstance::ITT_Material); + } + + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(BRUSSEL_DRAG_DROP_MATERIAL, &mat, sizeof(mat)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("Material '%s'", name.c_str()); + ImGui::EndDragDropSource(); + } } } break; } diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp index db6a277..81145a3 100644 --- a/source/EditorResources.hpp +++ b/source/EditorResources.hpp @@ -2,12 +2,15 @@ #include "Shader.hpp" +#include <string> + class EditorInstance; class EditorContentBrowser { private: enum Pane { P_Settings, P_Shader, + P_Material, }; static constexpr float kSplitterThickness = 3.0f; diff --git a/source/EditorUtils.cpp b/source/EditorUtils.cpp index ab6ffad..a0d2fc7 100644 --- a/source/EditorUtils.cpp +++ b/source/EditorUtils.cpp @@ -98,39 +98,6 @@ static int InputTextCallback(ImGuiInputTextCallbackData* data) { return 0; } -bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackUserData cbUserData; - cbUserData.str = str; - cbUserData.chainCallback = callback; - cbUserData.chainCallbackUserData = user_data; - return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cbUserData); -} - -bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* userData) { - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackUserData cbUserData; - cbUserData.str = str; - cbUserData.chainCallback = callback; - cbUserData.chainCallbackUserData = userData; - return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cbUserData); -} - -bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* userData) { - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackUserData cbUserData; - cbUserData.str = str; - cbUserData.chainCallback = callback; - cbUserData.chainCallbackUserData = userData; - return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cbUserData); -} - bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize) { // Adapted from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/blueprints-example.cpp // ::Splitter diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp index 27510fe..090f7f6 100644 --- a/source/EditorUtils.hpp +++ b/source/EditorUtils.hpp @@ -5,6 +5,9 @@ #include <imgui.h> #include <string> +#define BRUSSEL_DRAG_DROP_SHADER "Shader" +#define BRUSSEL_DRAG_DROP_MATERIAL "Mat" + namespace ImGui { const char* GetKeyNameGlfw(int key); @@ -23,10 +26,6 @@ bool ColorEdit4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = bool ColorPicker3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); bool ColorPicker4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags = 0); -bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); -bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* userData = nullptr); -bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* userData = nullptr); - bool Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize = -1.0f); } // namespace ImGui 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; + } +} diff --git a/source/Material.hpp b/source/Material.hpp index 6290a25..6b431be 100644 --- a/source/Material.hpp +++ b/source/Material.hpp @@ -1,5 +1,6 @@ #pragma once +#include "EditorAttachment.hpp" #include "RcPtr.hpp" #include "Shader.hpp" #include "Texture.hpp" @@ -7,15 +8,18 @@ #include <glad/glad.h> #include <cstddef> #include <cstdint> +#include <filesystem> #include <glm/glm.hpp> #include <memory> #include <span> #include <string_view> #include <vector> -// TODO support multiple sizes of vectors and matrices +class MaterialManager; class Material : public RefCounted { -public: +public: // NOTE: public for internal helpers and editor + // NOTE: specialize between scalar vs matrix vs vector to save memory + struct ScalarUniform { union { float floatValue; @@ -23,27 +27,33 @@ public: uint32_t uintValue; }; GLenum actualType; - GLint location; + /* Saves 'name' */ int infoUniformIndex; + /* Transient */ GLint location; }; struct VectorUniform { float value[4]; int actualLength; - GLint location; + /* Saves 'name' */ int infoUniformIndex; + /* Transient */ GLint location; }; struct MatrixUniform { float value[16]; int actualWidth; int actualHeight; - GLint location; + /* Saves 'name' */ int infoUniformIndex; + /* Transient */ GLint location; }; struct TextureUniform { RcPtr<Texture> value; - GLint location; + /* Saves 'name' */ int infoUniformIndex; + /* Transient */ GLint location; }; + std::string mName; + std::unique_ptr<EditorAttachment> mEditorAttachment; RcPtr<Shader> mShader; std::vector<ScalarUniform> mBoundScalars; std::vector<VectorUniform> mBoundVectors; @@ -51,7 +61,9 @@ public: std::vector<TextureUniform> mBoundTextures; public: - Material(Shader* shader); + // NOTE: constructs invalid object + Material() = default; + Material(Shader* shader, std::string name = ""); void SetFloat(const char* name, float value); void SetInt(const char* name, int32_t value); @@ -70,7 +82,40 @@ public: std::span<const VectorUniform> GetVectors() const; std::span<const MatrixUniform> GetMatrices() const; std::span<const TextureUniform> GetTextures() const; - const Shader& GetShader() const; + Shader* GetShader() const; + void SetShader(Shader* shader); + const std::string& GetName() const; + + bool IsAnnoymous() const; + bool IsValid() const; void UseUniforms() const; + + EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } + void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } + + void GetDesignatedPath(char* buffer, int bufferSize); + std::filesystem::path GetDesignatedPath(); + + bool SaveToFile(const std::filesystem::path& filePath) const; + bool LoadFromFile(const std::filesystem::path& filePath); +}; + +class MaterialManager { +public: + static inline MaterialManager* instance = nullptr; + +private: + absl::flat_hash_map<std::string_view, RcPtr<Material>> mMaterials; + +public: + void DiscoverMaterials(); + + std::pair<Material*, bool> SaveMaterial(Material* mat); + void DeleteMaterial(Material* mat, bool onDisk = false); + Material* LoadMaterial(std::string_view name); + bool RenameMaterial(Material* mat, std::string newName); + + const auto& GetMaterials() const { return mMaterials; } + Material* FindMaterial(std::string_view name); }; diff --git a/source/RapidJsonHelper.hpp b/source/RapidJsonHelper.hpp index ac1f664..9dc0701 100644 --- a/source/RapidJsonHelper.hpp +++ b/source/RapidJsonHelper.hpp @@ -31,9 +31,20 @@ namespace rapidjson { +inline const Value* GetProperty(const Value& value, std::string_view name) { + for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { + if (it->name.GetStringLength() != name.size()) continue; + if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; + + return &it->value; + } + return nullptr; +} + inline const Value* GetProperty(const Value& value, Type type, std::string_view name) { for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { if (it->name.GetStringLength() != name.size()) continue; + if (it->value.GetType() != type) continue; if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue; return &it->value; diff --git a/source/ScopeGuard.hpp b/source/ScopeGuard.hpp index b4a1749..28f3385 100644 --- a/source/ScopeGuard.hpp +++ b/source/ScopeGuard.hpp @@ -49,5 +49,12 @@ public: } }; +template <class T> +auto GuardDeletion(T* ptr) { + return ScopeGuard([ptr]() { + delete ptr; + }); +} + #define SCOPE_GUARD(name) ScopeGuard name = [&]() #define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]() diff --git a/source/Shader.cpp b/source/Shader.cpp index 3ff0d10..4a576d0 100644 --- a/source/Shader.cpp +++ b/source/Shader.cpp @@ -10,6 +10,7 @@ #include <rapidjson/filereadstream.h> #include <rapidjson/filewritestream.h> #include <rapidjson/writer.h> +#include <cassert> #include <cstddef> #include <cstdlib> #include <utility> @@ -17,6 +18,20 @@ namespace fs = std::filesystem; using namespace std::literals; +bool ShaderThingId::IsValid() const { + return kind == KD_Invalid; +} + +ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) { + switch (thing.kind) { + case ShaderThingId::KD_Input: return &inputs[thing.index].variable; + case ShaderThingId::KD_Output: return &outputs[thing.index].variable; + case ShaderThingId::KD_Uniform: return uniforms[thing.index].get(); + case ShaderThingId::KD_Invalid: break; + } + return nullptr; +} + bool ShaderInfo::SaveToFile(const fs::path& filePath) const { rapidjson::Document root(rapidjson::kObjectType); @@ -103,6 +118,16 @@ bool ShaderInfo::LoadFromFile(const fs::path& filePath) { return true; } +void ShaderInfo::LoadLocations(const Shader& ownerShader) { + GLuint program = ownerShader.GetProgram(); + + // TODO inputs + // TODO outputs + for (auto& uniform : uniforms) { + uniform->location = glGetUniformLocation(ownerShader.GetProgram(), uniform->name.c_str()); + } +} + Shader::Shader(std::string name) : mName{ name } { } @@ -337,6 +362,14 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) { #undef CATCH_ERROR +void Shader::GetDesignatedMetadataPath(char* buffer, int bufferSize) { + snprintf(buffer, bufferSize, "%s/Shaders/%s.json", AppConfig::assetDir.c_str(), mName.c_str()); +} + +fs::path Shader::GetDesignatedMetadataPath() { + return AppConfig::assetDirPath / "Shaders" / fs::path(mName).replace_extension(".json"); +} + namespace ProjectBrussel_UNITY_ID { bool QueryMathInfo(GLenum type, GLenum& scalarType, int& width, int& height) { auto DoOutput = [&](GLenum scalarTypeIn, int widthIn, int heightIn) { @@ -476,7 +509,6 @@ bool Shader::CreateEmptyInfoIfAbsent() { bool Shader::GatherInfoIfAbsent() { using namespace ProjectBrussel_UNITY_ID; - using ThingId = ShaderInfo::ThingId; if (mInfo || !IsValid()) { return false; @@ -487,7 +519,8 @@ bool Shader::GatherInfoIfAbsent() { // TODO handle differnt types of variables with the same name // TODO work with OpenGL < 4.3, possibly with glslang -#if 0 + return true; + int inputCount; glGetProgramInterfaceiv(mHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount); int outputCount; @@ -498,7 +531,7 @@ bool Shader::GatherInfoIfAbsent() { glGetProgramInterfaceiv(mHandle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount); // Gather inputs - auto GatherMathVars = [&](int count, GLenum resourceType, ShaderInfo::ThingKind resourceKind, std::vector<ShaderInfo::InputOutputThing>& list) { + auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderInfo::InputOutputThing>& list) { for (int i = 0; i < count; ++i) { const GLenum query[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE }; GLint props[std::size(query)]; @@ -511,7 +544,7 @@ bool Shader::GatherInfoIfAbsent() { std::string fieldName(nameLength - 1, '\0'); glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - mInfo->things.insert(fieldName, ThingId{ resourceKind, i }); + mInfo->things.try_emplace(fieldName, ShaderThingId{ resourceKind, i }); auto& thing = list.emplace_back(ShaderInfo::InputOutputThing{}); auto& var = thing.variable; @@ -520,8 +553,8 @@ bool Shader::GatherInfoIfAbsent() { QueryMathInfo(type, var.scalarType, var.width, var.height); } }; - GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderInfo::TKD_Input, mInfo->inputs); - GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderInfo::TKD_Output, mInfo->outputs); + GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo->inputs); + GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_Output, mInfo->outputs); // Gather uniform variables for (int i = 0; i < uniformCount; ++i) { @@ -540,13 +573,10 @@ bool Shader::GatherInfoIfAbsent() { std::string fieldName(nameLength - 1, '\0'); glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - mInfo->things.insert(fieldName, ThingId{ ShaderInfo::TKD_Uniform, i }); + mInfo->things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i }); mInfo->uniforms.push_back(CreateVariable(type, loc)); } - mInfo->things.shrink_to_fit(); -#endif - return true; } diff --git a/source/Shader.hpp b/source/Shader.hpp index cda5531..79262a6 100644 --- a/source/Shader.hpp +++ b/source/Shader.hpp @@ -13,13 +13,18 @@ // TODO move to variable after pattern matching is in the language +// Forward declarations +class Shader; + struct ShaderVariable { enum Kind { KD_Math, KD_Sampler, }; + std::string name; Kind kind; + GLuint location; protected: ShaderVariable(Kind kind) @@ -27,8 +32,6 @@ protected: }; struct ShaderMathVariable : public ShaderVariable { - std::string name; - GLuint location; GLenum scalarType; int arrayLength; int width; @@ -39,8 +42,6 @@ struct ShaderMathVariable : public ShaderVariable { }; struct ShaderSamplerVariable : public ShaderVariable { - std::string name; - GLuint location; GLenum type; int arrayLength; @@ -48,31 +49,36 @@ struct ShaderSamplerVariable : public ShaderVariable { : ShaderVariable(KD_Sampler) {} }; -struct ShaderInfo { - enum ThingKind { - TKD_Input, - TKD_Output, - TKD_Uniform, - TKD_UniformBlock, +struct ShaderThingId { + enum Kind { + KD_Input, + KD_Output, + KD_Uniform, + KD_Invalid, }; - struct ThingId { - ThingKind kind; - int index; - }; + Kind kind = KD_Invalid; + int index = 0; + + bool IsValid() const; +}; +struct ShaderInfo { struct InputOutputThing { ShaderMathVariable variable; Tags::VertexElementSemantic semantic = Tags::VES_Generic; }; - absl::flat_hash_map<std::string, ThingId> things; + absl::flat_hash_map<std::string, ShaderThingId> things; std::vector<InputOutputThing> inputs; std::vector<InputOutputThing> outputs; std::vector<std::unique_ptr<ShaderVariable>> uniforms; + ShaderVariable* FindVariable(const ShaderThingId& thing); + bool SaveToFile(const std::filesystem::path& filePath) const; bool LoadFromFile(const std::filesystem::path& filePath); + void LoadLocations(const Shader& ownerShader); }; class Shader : public RefCounted { @@ -129,6 +135,9 @@ public: EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } + void GetDesignatedMetadataPath(char* buffer, int bufferSize); + std::filesystem::path GetDesignatedMetadataPath(); + bool CreateEmptyInfoIfAbsent(); bool GatherInfoIfAbsent(); ShaderInfo* GetInfo() const; diff --git a/source/Texture.cpp b/source/Texture.cpp index 968c4bc..08641e1 100644 --- a/source/Texture.cpp +++ b/source/Texture.cpp @@ -97,3 +97,16 @@ GLuint Texture::GetHandle() const { bool Texture::IsValid() const { return mHandle != 0; } + +void TextureManager::DiscoverTextures() { + // TODO +} + +Texture* TextureManager::FindTexture(std::string_view name) { + auto iter = mTextures.find(name); + if (iter != mTextures.end()) { + return iter->second.Get(); + } else { + return nullptr; + } +} diff --git a/source/Texture.hpp b/source/Texture.hpp index c372998..c330bb3 100644 --- a/source/Texture.hpp +++ b/source/Texture.hpp @@ -2,6 +2,7 @@ #include "RcPtr.hpp" +#include <absl/container/flat_hash_map.h> #include <glad/glad.h> #include <glm/glm.hpp> #include <memory> @@ -64,3 +65,17 @@ struct Subregion { struct TextureSubregion : public Subregion { RcPtr<Texture> atlasTexture; }; + +class TextureManager { +public: + static inline TextureManager* instance = nullptr; + +private: + absl::flat_hash_map<std::string_view, RcPtr<Texture>> mTextures; + +public: + void DiscoverTextures(); + + const auto& GetTextures() const { return mTextures; } + Texture* FindTexture(std::string_view name); +}; diff --git a/source/main.cpp b/source/main.cpp index f7cde70..8f16403 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -2,18 +2,20 @@ #include "AppConfig.hpp" #include "EditorNotification.hpp" +#include "Material.hpp" #include "Shader.hpp" +#include "Texture.hpp" #define GLFW_INCLUDE_NONE #include <GLFW/glfw3.h> -#include <filesystem> #include <backends/imgui_impl_glfw.h> #include <backends/imgui_impl_opengl3.h> #include <glad/glad.h> #include <imgui.h> #include <cstdlib> #include <cxxopts.hpp> +#include <filesystem> #include <string> namespace fs = std::filesystem; @@ -26,7 +28,7 @@ static void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mo if (ImGui::GetIO().WantCaptureMouse) { return; } - + App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); app->HandleMouse(button, action); } @@ -35,7 +37,7 @@ static void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff if (ImGui::GetIO().WantCaptureMouse) { return; } - + App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); app->HandleMouseMotion(xOff, yOff); } @@ -44,7 +46,7 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio if (ImGui::GetIO().WantCaptureKeyboard) { return; } - + GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard(); if (keyboard) { App* app = static_cast<App*>(glfwGetWindowUserPointer(window)); @@ -55,7 +57,7 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio int main(int argc, char* argv[]) { constexpr auto kGameDataDir = "game-data-directory"; constexpr auto kGameAssetDir = "game-asset-directory"; - + cxxopts::Options options(std::string(AppConfig::kAppName), ""); // clang-format off options.add_options() @@ -64,26 +66,26 @@ int main(int argc, char* argv[]) { ; // clang-format on auto args = options.parse(argc, argv); - + { auto assetDir = args[kGameAssetDir].as<std::string>(); - + fs::path assetDirPath(assetDir); if (!fs::exists(assetDirPath)) { fprintf(stderr, "Invalid asset directory.\n"); return -4; } - + AppConfig::assetDir = std::move(assetDir); AppConfig::assetDirPath = std::move(assetDirPath); } - + if (args.count(kGameDataDir) > 0) { auto dataDir = args[kGameDataDir].as<std::string>(); - + fs::path dataDirPath(dataDir); fs::create_directories(dataDir); - + AppConfig::dataDir = std::move(dataDir); AppConfig::dataDirPath = std::move(dataDirPath); } else { @@ -91,13 +93,13 @@ int main(int argc, char* argv[]) { AppConfig::dataDir = "."; AppConfig::dataDirPath = fs::path("."); } - + if (!glfwInit()) { return -1; } - + glfwSetErrorCallback(&GlfwErrorCallback); - + // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 @@ -118,48 +120,53 @@ int main(int argc, char* argv[]) { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); #endif - + App app; - + GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr); if (window == nullptr) { return -2; } - + glfwSetWindowUserPointer(window, &app); - + // Window callbacks are retained by ImGui GLFW backend glfwSetKeyCallback(window, &GlfwKeyCallback); glfwSetMouseButtonCallback(window, &GlfwMouseCallback); glfwSetCursorPosCallback(window, &GlfwMouseMotionCallback); - + glfwMakeContextCurrent(window); glfwSwapInterval(1); - + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { return -3; } - + IMGUI_CHECKVERSION(); ImGui::CreateContext(); - + ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); - - ShaderManager::instance = new ShaderManager(); - ShaderManager::instance->DiscoverShaders(); - + + ShaderManager::instance = new ShaderManager(); + TextureManager::instance = new TextureManager(); + MaterialManager::instance = new MaterialManager(); + + ShaderManager::instance->DiscoverShaders(); + TextureManager::instance->DiscoverTextures(); + MaterialManager::instance->DiscoverMaterials(); + app.Init(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - + app.Show(); ImGui::ShowNotifications(); - + ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); @@ -168,18 +175,18 @@ int main(int argc, char* argv[]) { glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - + glfwSwapBuffers(window); } app.Shutdown(); - + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); - + ImGui::DestroyContext(); - + glfwDestroyWindow(window); glfwTerminate(); - + return 0; } |