aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-04-10 23:00:36 -0700
committerrtk0c <[email protected]>2022-04-10 23:00:36 -0700
commit17d5b091c9b2901ac96f5eee0da6af07228ae690 (patch)
treec210229c4bee1d3f56a64eacf1f976dfd8f971a7
parenta849c199d970e153580c312a24cfdfa099bc7b69 (diff)
Changeset: 5 Add shader and corresponding editor components
-rw-r--r--CMakeLists.txt53
-rw-r--r--assets/Shaders/Default.glsl30
-rw-r--r--assets/Shaders/Default.json32
-rw-r--r--conanfile.txt6
-rw-r--r--source/CMakeLists.txt2
-rw-r--r--source/EditorAttachmentImpl.cpp7
-rw-r--r--source/EditorAttachmentImpl.hpp7
-rw-r--r--source/EditorCore.cpp95
-rw-r--r--source/EditorCore.hpp18
-rw-r--r--source/EditorInspector.cpp15
-rw-r--r--source/EditorInspector.hpp17
-rw-r--r--source/EditorResources.cpp74
-rw-r--r--source/EditorResources.hpp12
-rw-r--r--source/GameObject.cpp2
-rw-r--r--source/GameObject.hpp2
-rw-r--r--source/GameObjectTags.hpp (renamed from source/GameObjectTypeTag.hpp)0
-rw-r--r--source/GraphicsTags.cpp215
-rw-r--r--source/GraphicsTags.hpp85
-rw-r--r--source/Mesh.cpp61
-rw-r--r--source/Mesh.hpp74
-rw-r--r--source/Player.cpp5
-rw-r--r--source/Player.hpp2
-rw-r--r--source/RapidJsonHelper.hpp67
-rw-r--r--source/Shader.cpp411
-rw-r--r--source/Shader.hpp101
25 files changed, 1097 insertions, 296 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6096166..aafd9e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,39 +11,30 @@ add_subdirectory(3rdparty/imgui)
add_subdirectory(3rdparty/imguizmo)
add_subdirectory(3rdparty/imguicolortextedit)
-add_executable(${PROJECT_NAME}
- # add_executable requires at least one source file
- dummy.c
-)
+# add_executable requires at least one source file
+add_executable(${PROJECT_NAME} dummy.c)
add_subdirectory(source)
-option(BRUSSEL_ENABLE_ASAN "Enable AddressSanitizer or not." OFF)
-if(BRUSSEL_ENABLE_ASAN)
- target_compile_options(${PROJECT_NAME}
- PRIVATE
- -fsanitize=address
- -fno-omit-frame-pointer
- )
- target_link_options(${PROJECT_NAME}
- PRIVATE
- -fsanitize=address
- -fno-omit-frame-pointer
- )
-endif()
+set_target_properties(${PROJECT_NAME} PROPERTIES
+ UNITY_BUILD_MODE BATCH
+ UNITY_BUILD_UNIQUE_ID "${PROJECT_NAME}_UNITY_ID"
+)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
-set_target_properties(${PROJECT_NAME}
-PROPERTIES
+set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
+)
- UNITY_BUILD_MODE BATCH
- UNITY_BUILD_UNIQUE_ID "${PROJECT_NAME}_UNITY_ID"
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+ RAPIDJSON_HAS_STDSTRING=1
)
-target_include_directories(${PROJECT_NAME} PRIVATE sources/)
-target_link_libraries(${PROJECT_NAME}
-PRIVATE
+target_include_directories(${PROJECT_NAME} PRIVATE
+ sources
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
${CONAN_LIBS}
OpenGL::GL
glfw
@@ -52,3 +43,17 @@ PRIVATE
ImGuizmo
ImGuiColorTextEdit
)
+
+option(BRUSSEL_ENABLE_ASAN "Enable AddressSanitizer or not." OFF)
+if(BRUSSEL_ENABLE_ASAN)
+ target_compile_options(${PROJECT_NAME}
+ PRIVATE
+ -fsanitize=address
+ -fno-omit-frame-pointer
+ )
+ target_link_options(${PROJECT_NAME}
+ PRIVATE
+ -fsanitize=address
+ -fno-omit-frame-pointer
+ )
+endif()
diff --git a/assets/Shaders/Default.glsl b/assets/Shaders/Default.glsl
new file mode 100644
index 0000000..d103102
--- /dev/null
+++ b/assets/Shaders/Default.glsl
@@ -0,0 +1,30 @@
+#type vertex
+#version 330 core
+
+layout(location = 0) in vec3 pos;
+layout(location = 1) in vec4 color;
+
+out Vertex2Fragmnet {
+ vec4 color;
+} v2f;
+
+// Standard PainterHost uniform
+uniform mat4 transformation;
+
+void main() {
+ gl_Position = transformation * vec4(pos, 1.0);
+ v2f.color = color;
+}
+
+#type fragment
+#version 330 core
+
+in Vertex2Fragmnet {
+ vec4 color;
+} v2f;
+
+out vec4 fragColor;
+
+void main() {
+ fragColor = v2f.color;
+}
diff --git a/assets/Shaders/Default.json b/assets/Shaders/Default.json
new file mode 100644
index 0000000..d294372
--- /dev/null
+++ b/assets/Shaders/Default.json
@@ -0,0 +1,32 @@
+{
+ "Inputs": [
+ {
+ "Semantic": "Position",
+ "Name": "pos",
+ "ScalarType": "float",
+ "Width": 1,
+ "Height": 3,
+ "ArrayLength": 1,
+ "OpenGLLocation": 0
+ },
+ {
+ "Semantic": "Color1",
+ "Name": "color",
+ "ScalarType": "float",
+ "Width": 1,
+ "Height": 4,
+ "ArrayLength": 1,
+ "OpenGLLocation": 1
+ }
+ ],
+ "Outputs": [
+ {
+ "Name": "fragColor",
+ "ScalarType": "float",
+ "Width": 1,
+ "Height": 4,
+ "ArrayLength": 1,
+ "OpenGLLocation": 0
+ }
+ ]
+} \ No newline at end of file
diff --git a/conanfile.txt b/conanfile.txt
index 9043fcf..4c6b0f7 100644
--- a/conanfile.txt
+++ b/conanfile.txt
@@ -2,11 +2,9 @@
cxxopts/3.0.0
glad/0.1.34
assimp/5.2.2
+rapidjson/cci.20211112
stb/cci.20210910
-tsl-ordered-map/1.0.0
-tsl-robin-map/0.6.3
-tsl-array-hash/0.7.1
-tsl-sparse-map/0.6.2
+abseil/20211102.0
[generators]
cmake
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 0749b62..f851bcb 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -3,11 +3,13 @@ PRIVATE
App.cpp
CpuMesh.cpp
EditorAccessories.cpp
+ EditorAttachmentImpl.cpp
EditorCore.cpp
EditorNotification.cpp
EditorResources.cpp
EditorUtils.cpp
GameObject.cpp
+ GraphicsTags.cpp
Level.cpp
Material.cpp
Mesh.cpp
diff --git a/source/EditorAttachmentImpl.cpp b/source/EditorAttachmentImpl.cpp
index 79c72ff..75d93cb 100644
--- a/source/EditorAttachmentImpl.cpp
+++ b/source/EditorAttachmentImpl.cpp
@@ -1,4 +1,8 @@
#include "EditorAttachmentImpl.hpp"
+#include "EditorAttachment.hpp"
+
+EditorAttachment::EditorAttachment() {
+}
std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) {
EditorAttachment* result;
@@ -14,6 +18,3 @@ std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) {
result->name = NameOf(object->GetTypeTag());
return std::unique_ptr<EditorAttachment>(result);
}
-
-void EaShader::ShowInspector() {
-}
diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp
index 7763a8f..32618b1 100644
--- a/source/EditorAttachmentImpl.hpp
+++ b/source/EditorAttachmentImpl.hpp
@@ -1,7 +1,6 @@
#pragma once
#include "EditorAttachment.hpp"
-#include "EditorInspector.hpp"
#include "GameObject.hpp"
#include "Player.hpp"
@@ -20,10 +19,8 @@ class EaLevelWrapper : public EditorAttachment {
public:
};
-class EaShader : public EditorAttachment, public IEditorInspectorTarget {
+class EaShader : public EditorAttachment {
public:
Shader* shader;
-
-public:
- void ShowInspector() override;
+ std::string name;
};
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
index 7744793..7a77b26 100644
--- a/source/EditorCore.cpp
+++ b/source/EditorCore.cpp
@@ -7,7 +7,7 @@
#include "EditorAttachmentImpl.hpp"
#include "EditorNotification.hpp"
#include "EditorUtils.hpp"
-#include "GameObjectTypeTag.hpp"
+#include "GameObjectTags.hpp"
#include "Level.hpp"
#include "Mesh.hpp"
#include "Player.hpp"
@@ -41,8 +41,7 @@ void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) {
EditorInstance::EditorInstance(App* app, GameWorld* world)
: mApp{ app }
, mWorld{ world }
- , mEdInspector()
- , mEdContentBrowser(&mEdInspector) {}
+ , mEdContentBrowser(this) {}
EditorInstance::~EditorInstance() {
}
@@ -50,6 +49,11 @@ EditorInstance::~EditorInstance() {
void EditorInstance::Show() {
if (!mWorld) return;
+ auto& io = ImGui::GetIO();
+ if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) {
+ mEdContentBrowserVisible = !mEdContentBrowserVisible;
+ }
+
ImGui::Begin("World properties");
ShowWorldProperties();
ImGui::End();
@@ -59,15 +63,91 @@ void EditorInstance::Show() {
ImGui::End();
ImGui::Begin("Inspector");
- if (mSelectedGameObject) {
- ShowInspector(mSelectedGameObject);
+ switch (mSelectedItt) {
+ case ITT_GameObject: ShowInspector(static_cast<GameObject*>(mSelectedItPtr)); break;
+ case ITT_Shader: ShowInspector(static_cast<Shader*>(mSelectedItPtr)); break;
+ case ITT_None: break;
}
ImGui::End();
+
+ if (mEdContentBrowserVisible) {
+ mEdContentBrowser.Show(&mEdContentBrowserVisible);
+ }
+}
+
+void EditorInstance::SelectIt(void* ptr, InspectorTargetType itt) {
+ mSelectedItPtr = ptr;
+ mSelectedItt = itt;
}
void EditorInstance::ShowWorldProperties() {
}
+// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism
+
+void EditorInstance::ShowInspector(Shader* shader) {
+ using namespace Tags;
+ using namespace ProjectBrussel_UNITY_ID;
+
+ auto info = shader->GetInfo();
+ if (!info) {
+ ImGui::TextUnformatted("No info present for this shader.");
+ if (ImGui::Button("Create empty info")) {
+ shader->CreateEmptyInfoIfAbsent();
+ }
+ if (ImGui::Button("Gather info")) {
+ shader->GatherInfoIfAbsent();
+ }
+ return;
+ }
+
+ 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());
+ }
+
+ // 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);
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Export metadata", isAnnoymous)) {
+ char path[512];
+ GetMetadataPath(path, sizeof(path));
+ info->SaveToFile(path);
+ }
+
+ auto ShowThing = [&](const std::vector<ShaderInfo::InputOutputThing>& things) {
+ for (auto& thing : things) {
+ ImGui::BulletText("Location %d\nName: %s\nSemantic: %s\nType: %s %dx%d",
+ thing.variable.location,
+ thing.variable.name.c_str(),
+ Tags::NameOf(thing.semantic).data(),
+ Tags::NameOfGLType(thing.variable.scalarType).data(),
+ thing.variable.width,
+ thing.variable.height);
+ }
+ };
+ if (ImGui::CollapsingHeader("Inputs")) {
+ ShowThing(info->inputs);
+ }
+ if (ImGui::CollapsingHeader("Outputs")) {
+ ShowThing(info->outputs);
+ }
+ if (ImGui::CollapsingHeader("Uniforms")) {
+ }
+ if (ImGui::CollapsingHeader("Uniform blocks")) {
+ }
+}
+
void EditorInstance::ShowInspector(GameObject* object) {
using namespace Tags;
using namespace ProjectBrussel_UNITY_ID;
@@ -160,13 +240,14 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) {
flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick;
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
- if (mSelectedGameObject == object) {
+ if (mSelectedItPtr == object) {
flags |= ImGuiTreeNodeFlags_Selected;
}
if (ImGui::TreeNodeEx(attachment->name.c_str(), flags)) {
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
- mSelectedGameObject = object;
+ mSelectedItPtr = object;
+ mSelectedItt = ITT_GameObject;
}
for (auto& child : object->GetChildren()) {
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
index a9ef93a..28ad849 100644
--- a/source/EditorCore.hpp
+++ b/source/EditorCore.hpp
@@ -1,7 +1,6 @@
#pragma once
#include "EditorAttachment.hpp"
-#include "EditorInspector.hpp"
#include "EditorResources.hpp"
#include "GameObject.hpp"
#include "World.hpp"
@@ -11,12 +10,20 @@
class App;
class EditorInstance {
+public:
+ enum InspectorTargetType {
+ ITT_GameObject,
+ ITT_Shader,
+ ITT_None,
+ };
+
private:
App* mApp;
GameWorld* mWorld;
- GameObject* mSelectedGameObject = nullptr;
- EditorInspector mEdInspector;
+ void* mSelectedItPtr = nullptr;
EditorContentBrowser mEdContentBrowser;
+ InspectorTargetType mSelectedItt = ITT_None;
+ bool mEdContentBrowserVisible=false;
public:
EditorInstance(App* app, GameWorld* world);
@@ -24,9 +31,14 @@ public:
void Show();
+ void* GetSelectedItPtr() const { return mSelectedItPtr; }
+ InspectorTargetType GetSelectedItt() const { return mSelectedItt; }
+ void SelectIt(void* ptr, InspectorTargetType itt);
+
private:
void ShowWorldProperties();
+ void ShowInspector(Shader* shader);
void ShowInspector(GameObject* object);
void ShowGameObjecetFields(GameObject* object);
diff --git a/source/EditorInspector.cpp b/source/EditorInspector.cpp
deleted file mode 100644
index 2f1d65c..0000000
--- a/source/EditorInspector.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "EditorInspector.hpp"
-
-void EditorInspector::Show() {
- if (mCurrentTarget) {
- mCurrentTarget->ShowInspector();
- }
-}
-
-IEditorInspectorTarget* EditorInspector::GetCurrentTarget() const {
- return mCurrentTarget;
-}
-
-void EditorInspector::SetCurrentTarget(IEditorInspectorTarget* target) {
- mCurrentTarget = target;
-}
diff --git a/source/EditorInspector.hpp b/source/EditorInspector.hpp
deleted file mode 100644
index d74b7a8..0000000
--- a/source/EditorInspector.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-class IEditorInspectorTarget {
-public:
- virtual void ShowInspector() = 0;
-};
-
-class EditorInspector {
-private:
- IEditorInspectorTarget* mCurrentTarget = nullptr;
-
-public:
- void Show();
-
- IEditorInspectorTarget* GetCurrentTarget() const;
- void SetCurrentTarget(IEditorInspectorTarget* target);
-};
diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp
index 722c1f1..c6d4d09 100644
--- a/source/EditorResources.cpp
+++ b/source/EditorResources.cpp
@@ -1,72 +1,76 @@
#include "EditorResources.hpp"
-#include "EditorAttachmentImpl.hpp"
#include "EditorCore.hpp"
-#include "EditorInspector.hpp"
#include "EditorNotification.hpp"
#include "EditorUtils.hpp"
#include "Shader.hpp"
#include <imgui.h>
-EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector)
- : mInspector{ inspector } {
+EditorContentBrowser::EditorContentBrowser(EditorInstance* editor)
+ : mEditor{ editor } {
}
-void EditorContentBrowser::Show() {
- if (visible) {
- ImGuiWindowFlags windowFlags;
- if (docked) {
- // Center window horizontally, align bottom vertically
- auto& viewportSize = ImGui::GetMainViewport()->Size;
- ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f));
- ImGui::SetNextWindowSizeRelScreen(0.8f, 0.5f);
- windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
- } else {
- windowFlags = 0;
- }
- ImGui::Begin("Content Browser", &visible, windowFlags);
+EditorContentBrowser::~EditorContentBrowser() {
+}
- ImGui::Splitter(true, kSplitterThickness, &splitterLeft, &splitterRight, kLeftPaneMinWidth, kRightPaneMinWidth);
+void EditorContentBrowser::Show(bool* open) {
+ ImGuiWindowFlags windowFlags;
+ if (docked) {
+ // Center window horizontally, align bottom vertically
+ auto& viewportSize = ImGui::GetMainViewport()->Size;
+ ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f));
+ ImGui::SetNextWindowSizeRelScreen(0.8f, 0.5f);
+ windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
+ } else {
+ windowFlags = 0;
+ }
+ ImGui::Begin("Content Browser", open, windowFlags);
- ImGui::BeginChild("LeftPane", ImVec2(splitterLeft - kPadding, 0.0f));
+ ImGui::Splitter(true, kSplitterThickness, &splitterLeft, &splitterRight, kLeftPaneMinWidth, kRightPaneMinWidth);
+ ImGui::BeginChild("LeftPane", ImVec2(splitterLeft - kPadding, 0.0f));
+ {
if (ImGui::Selectable("Settings", mPane == P_Settings)) {
mPane = P_Settings;
}
if (ImGui::Selectable("Shaders", mPane == P_Shader)) {
mPane = P_Shader;
}
+ }
+ ImGui::EndChild();
- ImGui::EndChild(); // Window "LeftPane"
-
- ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding);
- ImGui::BeginChild("RightPane"); // Fill remaining space
-
+ ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding);
+ ImGui::BeginChild("RightPane"); // Fill remaining space
+ {
switch (mPane) {
case P_Settings: {
ImGui::Checkbox("Docked", &docked);
} break;
case P_Shader: {
+ // TODO reload shaders while keeping existing references working
+ // if (ImGui::Button("Reload from disk")) {
+ // ShaderManager::instance->DiscoverShaders();
+ // }
+
auto& shaders = ShaderManager::instance->GetShaders();
for (auto it = shaders.begin(); it != shaders.end(); ++it) {
- auto name = it.key();
- auto shader = it.value().Get();
+ auto name = it->first;
+ auto shader = it->second.Get();
- shader->GatherDetailsIfAbsent();
- auto details = shader->GetDetails();
- auto ea = static_cast<EaShader*>(shader->GetEditorAttachment());
+ shader->GatherInfoIfAbsent();
+ auto details = shader->GetInfo();
- if (ImGui::Selectable(name, mInspector->GetCurrentTarget() == ea)) {
- mInspector->SetCurrentTarget(ea);
+ bool selected = mEditor->GetSelectedItPtr() == shader;
+ if (ImGui::Selectable(name.data(), selected)) {
+ mEditor->SelectIt(shader, EditorInstance::ITT_Shader);
}
}
} break;
}
-
- ImGui::EndChild(); // Window "RightPane"
-
- ImGui::End();
}
+ ImGui::EndChild();
+
+ ImGui::End();
}
diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp
index f4e1ffe..db6a277 100644
--- a/source/EditorResources.hpp
+++ b/source/EditorResources.hpp
@@ -2,7 +2,7 @@
#include "Shader.hpp"
-class EditorInspector;
+class EditorInstance;
class EditorContentBrowser {
private:
enum Pane {
@@ -17,19 +17,15 @@ private:
static constexpr float kLeftPaneMinWidth = 200.0f;
static constexpr float kRightPaneMinWidth = 200.0f;
- EditorInspector* mInspector;
+ EditorInstance* mEditor;
Pane mPane = P_Settings;
float splitterLeft = kLeftPaneMinWidth;
float splitterRight = 0.0f;
bool docked = true;
- bool visible = false;
public:
- EditorContentBrowser(EditorInspector* inspector);
+ EditorContentBrowser(EditorInstance* editor);
~EditorContentBrowser();
- bool IsVisible() const;
- void SetVisible(bool visible);
-
- void Show();
+ void Show(bool* open = nullptr);
};
diff --git a/source/GameObject.cpp b/source/GameObject.cpp
index 2aaedeb..0a063e8 100644
--- a/source/GameObject.cpp
+++ b/source/GameObject.cpp
@@ -1,6 +1,6 @@
#include "GameObject.hpp"
-#include "GameObjectTypeTag.hpp"
+#include "GameObjectTags.hpp"
#include "World.hpp"
#include <string_view>
diff --git a/source/GameObject.hpp b/source/GameObject.hpp
index cb83da3..e1e6dd1 100644
--- a/source/GameObject.hpp
+++ b/source/GameObject.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "EditorAttachment.hpp"
-#include "GameObjectTypeTag.hpp"
+#include "GameObjectTags.hpp"
#include "Material.hpp"
#include "Mesh.hpp"
#include "PodVector.hpp"
diff --git a/source/GameObjectTypeTag.hpp b/source/GameObjectTags.hpp
index 01a0ca4..01a0ca4 100644
--- a/source/GameObjectTypeTag.hpp
+++ b/source/GameObjectTags.hpp
diff --git a/source/GraphicsTags.cpp b/source/GraphicsTags.cpp
new file mode 100644
index 0000000..d2da091
--- /dev/null
+++ b/source/GraphicsTags.cpp
@@ -0,0 +1,215 @@
+#include "GraphicsTags.hpp"
+
+#include <absl/container/flat_hash_map.h>
+#include <cstddef>
+#include <cstdint>
+
+using namespace std::literals;
+
+std::string_view Tags::NameOf(VertexElementSemantic semantic) {
+ switch (semantic) {
+ case VES_Position: return "Position"sv;
+ case VES_BlendWeights: return "BlendWeights"sv;
+ case VES_BlendIndices: return "BlendIndices"sv;
+ case VES_Normal: return "Normal"sv;
+ case VES_Color1: return "Color1"sv;
+ case VES_Color2: return "Color2"sv;
+ case VES_Texture_coordinates: return "Texture_coordinates"sv;
+ case VES_Binormal: return "Binormal"sv;
+ case VES_Tangent: return "Tangent"sv;
+ case VES_Generic: return "Generic"sv;
+ case VES_COUNT: break;
+ }
+ return std::string_view();
+}
+
+Tags::VertexElementSemantic Tags::FindVertexElementSemantic(std::string_view name) {
+ if (name == "Position"sv) return VES_Position;
+ if (name == "BlendWeights"sv) return VES_BlendWeights;
+ if (name == "BlendIndices"sv) return VES_BlendIndices;
+ if (name == "Normal"sv) return VES_Normal;
+ if (name == "Color1"sv) return VES_Color1;
+ if (name == "Color2"sv) return VES_Color2;
+ if (name == "Texture_coordinates"sv) return VES_Texture_coordinates;
+ if (name == "Binormal"sv) return VES_Binormal;
+ if (name == "Tangent"sv) return VES_Tangent;
+ if (name == "Generic"sv) return VES_Generic;
+ return VES_COUNT;
+}
+
+int Tags::SizeOf(VertexElementType type) {
+ switch (type) {
+ case VET_Float1:
+ return sizeof(float);
+ case VET_Float2:
+ return sizeof(float) * 2;
+ case VET_Float3:
+ return sizeof(float) * 3;
+ case VET_Float4:
+ return sizeof(float) * 4;
+ case VET_Double1:
+ return sizeof(double);
+ case VET_Double2:
+ return sizeof(double) * 2;
+ case VET_Double3:
+ return sizeof(double) * 3;
+ case VET_Double4:
+ return sizeof(double) * 4;
+ case VET_Short2:
+ case VET_Short2Norm:
+ case VET_Ushort2:
+ case VET_Ushort2Norm:
+ return sizeof(short) * 2;
+ case VET_Short4:
+ case VET_Short4Norm:
+ case VET_Ushort4:
+ case VET_Ushort4Norm:
+ return sizeof(short) * 4;
+ case VET_Int1:
+ case VET_Uint1:
+ return sizeof(int);
+ case VET_Int2:
+ case VET_Uint2:
+ return sizeof(int) * 2;
+ case VET_Int3:
+ case VET_Uint3:
+ return sizeof(int) * 3;
+ case VET_Int4:
+ case VET_Uint4:
+ return sizeof(int) * 4;
+ case VET_Byte4:
+ case VET_Byte4Norm:
+ case VET_Ubyte4:
+ case VET_Ubyte4Norm:
+ return sizeof(char) * 4;
+ }
+ return 0;
+}
+
+bool Tags::IsNormalized(VertexElementType type) {
+ return type >= VET_NORM_BEGIN && type <= VET_NORM_END;
+}
+
+int Tags::SizeOf(IndexType type) {
+ switch (type) {
+ case IT_16Bit: return sizeof(uint16_t);
+ case IT_32Bit: return sizeof(uint32_t);
+ }
+ return 0;
+}
+
+namespace ProjectBrussel_UNITY_ID {
+
+struct GLTypeInfo {
+ absl::flat_hash_map<GLenum, std::string_view> enum2Name;
+ absl::flat_hash_map<std::string_view, GLenum> name2Enum;
+
+ GLTypeInfo() {
+ InsertEntry("float"sv, GL_FLOAT);
+ InsertEntry("double"sv, GL_DOUBLE);
+ InsertEntry("int"sv, GL_INT);
+ InsertEntry("uint"sv, GL_UNSIGNED_INT);
+ InsertEntry("bool"sv, GL_BOOL);
+
+ InsertEntry("vec2"sv, GL_FLOAT_VEC2);
+ InsertEntry("vec3"sv, GL_FLOAT_VEC3);
+ InsertEntry("vec4"sv, GL_FLOAT_VEC4);
+ InsertEntry("dvec2"sv, GL_DOUBLE_VEC2);
+ InsertEntry("dvec3"sv, GL_DOUBLE_VEC3);
+ InsertEntry("dvec4"sv, GL_DOUBLE_VEC4);
+ InsertEntry("ivec2"sv, GL_INT_VEC2);
+ InsertEntry("ivec3"sv, GL_INT_VEC3);
+ InsertEntry("ivec4"sv, GL_INT_VEC4);
+ InsertEntry("uvec2"sv, GL_UNSIGNED_INT_VEC2);
+ InsertEntry("uvec3"sv, GL_UNSIGNED_INT_VEC3);
+ InsertEntry("uvec4"sv, GL_UNSIGNED_INT_VEC4);
+ InsertEntry("bvec2"sv, GL_BOOL_VEC2);
+ InsertEntry("bvec3"sv, GL_BOOL_VEC3);
+ InsertEntry("bvec4"sv, GL_BOOL_VEC4);
+
+ InsertEntry("mat2"sv, GL_FLOAT_MAT2);
+ InsertEntry("mat3"sv, GL_FLOAT_MAT3);
+ InsertEntry("mat4"sv, GL_FLOAT_MAT4);
+ InsertEntry("mat2x3"sv, GL_FLOAT_MAT2x3);
+ InsertEntry("mat2x4"sv, GL_FLOAT_MAT2x4);
+ InsertEntry("mat3x2"sv, GL_FLOAT_MAT3x2);
+ InsertEntry("mat3x4"sv, GL_FLOAT_MAT3x4);
+ InsertEntry("mat4x2"sv, GL_FLOAT_MAT4x2);
+ InsertEntry("mat4x3"sv, GL_FLOAT_MAT4x3);
+
+ InsertEntry("dmat2"sv, GL_DOUBLE_MAT2);
+ InsertEntry("dmat3"sv, GL_DOUBLE_MAT3);
+ InsertEntry("dmat4"sv, GL_DOUBLE_MAT4);
+ InsertEntry("dmat2x3"sv, GL_DOUBLE_MAT2x3);
+ InsertEntry("dmat2x4"sv, GL_DOUBLE_MAT2x4);
+ InsertEntry("dmat3x2"sv, GL_DOUBLE_MAT3x2);
+ InsertEntry("dmat3x4"sv, GL_DOUBLE_MAT3x4);
+ InsertEntry("dmat4x2"sv, GL_DOUBLE_MAT4x2);
+ InsertEntry("dmat4x3"sv, GL_DOUBLE_MAT4x3);
+
+ InsertEntry("sampler1D"sv, GL_SAMPLER_1D);
+ InsertEntry("sampler2D"sv, GL_SAMPLER_2D);
+ InsertEntry("sampler3D"sv, GL_SAMPLER_3D);
+ InsertEntry("samplerCube"sv, GL_SAMPLER_CUBE);
+ InsertEntry("sampler1DShadow"sv, GL_SAMPLER_1D_SHADOW);
+ InsertEntry("sampler2DShadow"sv, GL_SAMPLER_2D_SHADOW);
+ InsertEntry("sampler1DArray"sv, GL_SAMPLER_1D_ARRAY);
+ InsertEntry("sampler2DArray"sv, GL_SAMPLER_2D_ARRAY);
+ InsertEntry("sampler1DArrayShadow"sv, GL_SAMPLER_1D_ARRAY_SHADOW);
+ InsertEntry("sampler2DArrayShadow"sv, GL_SAMPLER_2D_ARRAY_SHADOW);
+ InsertEntry("sampler2DMultisample"sv, GL_SAMPLER_2D_MULTISAMPLE);
+ InsertEntry("sampler2DMultisampleArray"sv, GL_SAMPLER_2D_MULTISAMPLE_ARRAY);
+ InsertEntry("samplerCubeShadow"sv, GL_SAMPLER_CUBE_SHADOW);
+ InsertEntry("samplerBuffer"sv, GL_SAMPLER_BUFFER);
+ InsertEntry("sampler2DRect"sv, GL_SAMPLER_2D_RECT);
+ InsertEntry("sampler2DRectShadow"sv, GL_SAMPLER_2D_RECT_SHADOW);
+
+ InsertEntry("isampler1D"sv, GL_INT_SAMPLER_1D);
+ InsertEntry("isampler2D"sv, GL_INT_SAMPLER_2D);
+ InsertEntry("isampler3D"sv, GL_INT_SAMPLER_3D);
+ InsertEntry("isamplerCube"sv, GL_INT_SAMPLER_CUBE);
+ InsertEntry("isampler1DArray"sv, GL_INT_SAMPLER_1D_ARRAY);
+ InsertEntry("isampler2DArray"sv, GL_INT_SAMPLER_2D_ARRAY);
+ InsertEntry("isampler2DMultisample"sv, GL_INT_SAMPLER_2D_MULTISAMPLE);
+ InsertEntry("isampler2DMultisampleArray"sv, GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY);
+ InsertEntry("isamplerBuffer"sv, GL_INT_SAMPLER_BUFFER);
+ InsertEntry("isampler2DRect"sv, GL_INT_SAMPLER_2D_RECT);
+
+ InsertEntry("usampler1D"sv, GL_UNSIGNED_INT_SAMPLER_1D);
+ InsertEntry("usampler2D"sv, GL_UNSIGNED_INT_SAMPLER_2D);
+ InsertEntry("usampler3D"sv, GL_UNSIGNED_INT_SAMPLER_3D);
+ InsertEntry("usamplerCube"sv, GL_UNSIGNED_INT_SAMPLER_CUBE);
+ InsertEntry("usampler1DArray"sv, GL_UNSIGNED_INT_SAMPLER_1D_ARRAY);
+ InsertEntry("usampler2DArray"sv, GL_UNSIGNED_INT_SAMPLER_2D_ARRAY);
+ InsertEntry("usampler2DMultisample"sv, GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE);
+ InsertEntry("usampler2DMultisampleArray"sv, GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY);
+ InsertEntry("usamplerBuffer"sv, GL_UNSIGNED_INT_SAMPLER_BUFFER);
+ InsertEntry("usampler2DRect"sv, GL_UNSIGNED_INT_SAMPLER_2D_RECT);
+ }
+
+ void InsertEntry(std::string_view name, GLenum value) {
+ enum2Name.try_emplace(value, name);
+ name2Enum.try_emplace(name, value);
+ }
+} const kGLTypeInfo;
+} // namespace ProjectBrussel_UNITY_ID
+
+std::string_view Tags::NameOfGLType(GLenum value) {
+ using namespace ProjectBrussel_UNITY_ID;
+ auto iter = kGLTypeInfo.enum2Name.find(value);
+ if (iter != kGLTypeInfo.enum2Name.end()) {
+ return iter->second;
+ } else {
+ return std::string_view();
+ }
+}
+
+GLenum Tags::FindGLType(std::string_view name) {
+ using namespace ProjectBrussel_UNITY_ID;
+ auto iter = kGLTypeInfo.name2Enum.find(name);
+ if (iter != kGLTypeInfo.name2Enum.end()) {
+ return iter->second;
+ } else {
+ return GL_NONE;
+ }
+}
diff --git a/source/GraphicsTags.hpp b/source/GraphicsTags.hpp
new file mode 100644
index 0000000..465c57d
--- /dev/null
+++ b/source/GraphicsTags.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <glad/glad.h>
+#include <string>
+#include <string_view>
+
+namespace Tags {
+/// Vertex element semantics, used to identify the meaning of vertex buffer contents
+enum VertexElementSemantic {
+ /// Position, typically VET_Float3
+ VES_Position,
+ /// Blending weights
+ VES_BlendWeights,
+ /// Blending indices
+ VES_BlendIndices,
+ /// Normal, typically VET_Float3
+ VES_Normal,
+ /// Colour, typically VET_Ubyte4
+ VES_Color1,
+ /// Secondary colour. Generally free for custom data. Means specular with OpenGL FFP.
+ VES_Color2,
+ /// Texture coordinates, typically VET_Float2
+ VES_Texture_coordinates,
+ /// Binormal (Y axis if normal is Z)
+ VES_Binormal,
+ /// Tangent (X axis if normal is Z)
+ VES_Tangent,
+ /// Default semantic
+ VES_Generic,
+ VES_COUNT,
+};
+
+std::string_view NameOf(VertexElementSemantic semantic);
+VertexElementSemantic FindVertexElementSemantic(std::string_view name);
+
+enum VertexElementType {
+ VET_Float1,
+ VET_Float2,
+ VET_Float3,
+ VET_Float4,
+
+ VET_Short2,
+ VET_Short4,
+ VET_Ubyte4,
+
+ // the following are not universally supported on all hardware:
+ VET_Double1,
+ VET_Double2,
+ VET_Double3,
+ VET_Double4,
+ VET_Ushort2,
+ VET_Ushort4,
+ VET_Int1,
+ VET_Int2,
+ VET_Int3,
+ VET_Int4,
+ VET_Uint1,
+ VET_Uint2,
+ VET_Uint3,
+ VET_Uint4,
+ VET_Byte4, /// signed bytes
+
+ VET_NORM_BEGIN,
+ VET_Byte4Norm = VET_NORM_BEGIN, /// signed bytes (normalized to -1..1)
+ VET_Ubyte4Norm, /// unsigned bytes (normalized to 0..1)
+ VET_Short2Norm, /// signed shorts (normalized to -1..1)
+ VET_Short4Norm,
+ VET_Ushort2Norm, /// unsigned shorts (normalized to 0..1)
+ VET_Ushort4Norm,
+ VET_NORM_END = VET_Ushort4Norm,
+};
+
+int SizeOf(VertexElementType type);
+bool IsNormalized(VertexElementType type);
+
+enum IndexType {
+ IT_16Bit,
+ IT_32Bit,
+};
+
+int SizeOf(IndexType type);
+
+std::string_view NameOfGLType(GLenum);
+GLenum FindGLType(std::string_view name);
+} // namespace Tags
diff --git a/source/Mesh.cpp b/source/Mesh.cpp
index 385ef55..3622d42 100644
--- a/source/Mesh.cpp
+++ b/source/Mesh.cpp
@@ -2,67 +2,6 @@
#include <algorithm>
-int Tags::SizeOf(VertexElementType type) {
- switch (type) {
- case VET_Float1:
- return sizeof(float);
- case VET_Float2:
- return sizeof(float) * 2;
- case VET_Float3:
- return sizeof(float) * 3;
- case VET_Float4:
- return sizeof(float) * 4;
- case VET_Double1:
- return sizeof(double);
- case VET_Double2:
- return sizeof(double) * 2;
- case VET_Double3:
- return sizeof(double) * 3;
- case VET_Double4:
- return sizeof(double) * 4;
- case VET_Short2:
- case VET_Short2Norm:
- case VET_Ushort2:
- case VET_Ushort2Norm:
- return sizeof(short) * 2;
- case VET_Short4:
- case VET_Short4Norm:
- case VET_Ushort4:
- case VET_Ushort4Norm:
- return sizeof(short) * 4;
- case VET_Int1:
- case VET_Uint1:
- return sizeof(int);
- case VET_Int2:
- case VET_Uint2:
- return sizeof(int) * 2;
- case VET_Int3:
- case VET_Uint3:
- return sizeof(int) * 3;
- case VET_Int4:
- case VET_Uint4:
- return sizeof(int) * 4;
- case VET_Byte4:
- case VET_Byte4Norm:
- case VET_Ubyte4:
- case VET_Ubyte4Norm:
- return sizeof(char) * 4;
- }
- return 0;
-}
-
-bool Tags::IsNormalized(VertexElementType type) {
- return type >= VET_NORM_BEGIN && type <= VET_NORM_END;
-}
-
-int Tags::SizeOf(IndexType type) {
- switch (type) {
- case IT_16Bit: return sizeof(uint16_t);
- case IT_32Bit: return sizeof(uint32_t);
- }
- return 0;
-}
-
GpuVertexBuffer::GpuVertexBuffer() {
glGenBuffers(1, &handle);
}
diff --git a/source/Mesh.hpp b/source/Mesh.hpp
index a208eda..a1c0984 100644
--- a/source/Mesh.hpp
+++ b/source/Mesh.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "GraphicsTags.hpp"
#include "RcPtr.hpp"
#include "SmallVector.hpp"
@@ -8,79 +9,6 @@
#include <cstdint>
#include <vector>
-namespace Tags {
-/// Vertex element semantics, used to identify the meaning of vertex buffer contents
-enum VertexElementSemantic {
- /// Position, typically VET_Float3
- VES_Position = 1,
- /// Blending weights
- VES_BlendWeights = 2,
- /// Blending indices
- VES_BlendIndices = 3,
- /// Normal, typically VET_Float3
- VES_Normal = 4,
- /// Colour, typically VET_Ubyte4
- VES_Colour = 5,
- /// Secondary colour. Generally free for custom data. Means specular with OpenGL FFP.
- VES_Colour2 = 6,
- /// Texture coordinates, typically VET_Float2
- VES_Texture_coordinates = 7,
- /// Binormal (Y axis if normal is Z)
- VES_Binormal = 8,
- /// Tangent (X axis if normal is Z)
- VES_Tangent = 9,
- /// The number of VertexElementSemantic elements (note - the first value VES_Position is 1)
- VES_COUNT = 9,
-};
-
-enum VertexElementType {
- VET_Float1,
- VET_Float2,
- VET_Float3,
- VET_Float4,
-
- VET_Short2,
- VET_Short4,
- VET_Ubyte4,
-
- // the following are not universally supported on all hardware:
- VET_Double1,
- VET_Double2,
- VET_Double3,
- VET_Double4,
- VET_Ushort2,
- VET_Ushort4,
- VET_Int1,
- VET_Int2,
- VET_Int3,
- VET_Int4,
- VET_Uint1,
- VET_Uint2,
- VET_Uint3,
- VET_Uint4,
- VET_Byte4, /// signed bytes
-
- VET_NORM_BEGIN,
- VET_Byte4Norm = VET_NORM_BEGIN, /// signed bytes (normalized to -1..1)
- VET_Ubyte4Norm, /// unsigned bytes (normalized to 0..1)
- VET_Short2Norm, /// signed shorts (normalized to -1..1)
- VET_Short4Norm,
- VET_Ushort2Norm, /// unsigned shorts (normalized to 0..1)
- VET_Ushort4Norm,
- VET_NORM_END = VET_Ushort4Norm,
-};
-
-int SizeOf(VertexElementType type);
-bool IsNormalized(VertexElementType type);
-
-enum IndexType {
- IT_16Bit,
- IT_32Bit,
-};
-
-int SizeOf(IndexType type);
-} // namespace Tags
-
struct GpuVertexBuffer : public RefCounted {
GLuint handle;
int sizeInBytes;
diff --git a/source/Player.cpp b/source/Player.cpp
index 2fa8d0d..47c3ccc 100644
--- a/source/Player.cpp
+++ b/source/Player.cpp
@@ -1,6 +1,7 @@
#include "Player.hpp"
#include "AppConfig.hpp"
+#include "ScopeGuard.hpp"
#include "Utils.hpp"
#include <cstdio>
@@ -80,21 +81,21 @@ static FILE* OpenPlayerConfigFile(Player* player, Utils::IoMode mode) {
bool Player::LoadFromFile() {
auto file = OpenPlayerConfigFile(this, Utils::Read);
if (!file) return false;
+ DEFER { fclose(file); };
// TODO input validation
PLAYERKEYBINDS_DO_IO(fscanf, &);
- fclose(file);
return true;
}
bool Player::SaveToFile() {
auto file = OpenPlayerConfigFile(this, Utils::WriteTruncate);
if (!file) return false;
+ DEFER { fclose(file); };
PLAYERKEYBINDS_DO_IO(fprintf, );
- fclose(file);
return true;
}
#pragma macro_pop("PLAYERKEYBINDS_DO_IO")
diff --git a/source/Player.hpp b/source/Player.hpp
index db8be33..4ae4f80 100644
--- a/source/Player.hpp
+++ b/source/Player.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "GameObject.hpp"
-#include "GameObjectTypeTag.hpp"
+#include "GameObjectTags.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
diff --git a/source/RapidJsonHelper.hpp b/source/RapidJsonHelper.hpp
new file mode 100644
index 0000000..ac1f664
--- /dev/null
+++ b/source/RapidJsonHelper.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <rapidjson/document.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#define BRUSSEL_JSON_GET(object, name, type, out, failAction) \
+ { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) failAction; \
+ auto& value = it->value; \
+ if (!value.Is<type>()) failAction; \
+ (out) = value.Get<type>(); \
+ }
+
+#define BRUSSEL_JSON_GET_DEFAULT(object, name, type, out, theDefault) \
+ do { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ auto& value = it->value; \
+ if (!value.Is<type>()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ (out) = value.Get<type>(); \
+ } while (0);
+
+namespace rapidjson {
+
+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 (std::memcmp(it->name.GetString(), name.data(), name.size())) continue;
+
+ return &it->value;
+ }
+ return nullptr;
+}
+
+inline std::string_view AsStringView(const Value& value) {
+ return std::string_view(value.GetString(), value.GetStringLength());
+}
+
+inline std::string_view AsStringView(const GenericStringRef<char>& strRef) {
+ return std::string_view(strRef.s, strRef.length);
+}
+
+inline std::string AsString(const Value& value) {
+ return std::string(value.GetString(), value.GetStringLength());
+}
+
+inline std::string AsString(const GenericStringRef<char>& strRef) {
+ return std::string(strRef.s, strRef.length);
+}
+
+// RapidJson itself already provides std::string and const char* overloads
+inline GenericStringRef<char> StringRef(std::string_view str) {
+ return GenericStringRef<char>(
+ str.data() ? str.data() : "",
+ str.size());
+}
+
+} // namespace rapidjson
diff --git a/source/Shader.cpp b/source/Shader.cpp
index a2d1c97..abb68bd 100644
--- a/source/Shader.cpp
+++ b/source/Shader.cpp
@@ -1,20 +1,110 @@
#include "Shader.hpp"
#include "AppConfig.hpp"
+#include "RapidJsonHelper.hpp"
#include "ScopeGuard.hpp"
#include "Utils.hpp"
#include <imgui.h>
+#include <rapidjson/document.h>
+#include <rapidjson/filereadstream.h>
+#include <rapidjson/filewritestream.h>
+#include <rapidjson/writer.h>
#include <cstddef>
#include <cstdlib>
-#include <filesystem>
#include <utility>
namespace fs = std::filesystem;
-using namespace std::literals::string_literals;
-using namespace std::literals::string_view_literals;
+using namespace std::literals;
-void ShaderDetails::ShowInspector() {
+bool ShaderInfo::SaveToFile(const fs::path& filePath) const {
+ rapidjson::Document root(rapidjson::kObjectType);
+
+ auto SaveInputOutputThings = [&](const char* name, const std::vector<InputOutputThing>& things) {
+ rapidjson::Value rvThings(rapidjson::kArrayType);
+ for (auto& thing : things) {
+ rapidjson::Value rvThing(rapidjson::kObjectType);
+ rvThing.AddMember("Name", thing.variable.name, root.GetAllocator());
+ rvThing.AddMember("Semantic", rapidjson::StringRef(Tags::NameOf(thing.semantic)), root.GetAllocator());
+ rvThing.AddMember("ScalarType", rapidjson::StringRef(Tags::NameOfGLType(thing.variable.scalarType)), root.GetAllocator());
+ rvThing.AddMember("Width", thing.variable.width, root.GetAllocator());
+ rvThing.AddMember("Height", thing.variable.height, root.GetAllocator());
+ rvThing.AddMember("ArrayLength", thing.variable.arrayLength, root.GetAllocator());
+ rvThing.AddMember("OpenGLLocation", thing.variable.location, root.GetAllocator());
+
+ rvThings.PushBack(rvThing, root.GetAllocator());
+ }
+ root.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator());
+ };
+ SaveInputOutputThings("Inputs", inputs);
+ SaveInputOutputThings("Outputs", outputs);
+
+ // TODO uniforms
+
+ 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 ShaderInfo::LoadFromFile(const fs::path& filePath) {
+ 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);
+
+ auto LoadInputOutputThings = [&](std::string_view name, std::vector<InputOutputThing>& things) {
+ auto rvThings = rapidjson::GetProperty(root, rapidjson::kObjectType, name);
+ if (!rvThings) return;
+ if (!rvThings->IsArray()) return;
+
+ for (auto& elm : rvThings->GetArray()) {
+ if (!elm.IsObject()) continue;
+ InputOutputThing thing;
+
+ BRUSSEL_JSON_GET(elm, "Name", std::string, thing.variable.name, continue);
+ { // Semantic
+ auto value = rapidjson::GetProperty(elm, rapidjson::kStringType, "Semantic"sv);
+ if (!value) {
+ thing.semantic = Tags::VES_Generic;
+ } else {
+ thing.semantic = Tags::FindVertexElementSemantic(rapidjson::AsStringView(*value));
+ }
+ }
+ { // Scalar type
+ auto value = rapidjson::GetProperty(elm, rapidjson::kStringType, "ScalarType"sv);
+ if (!value) continue;
+ thing.variable.scalarType = Tags::FindGLType(rapidjson::AsStringView(*value));
+ }
+ BRUSSEL_JSON_GET(elm, "Width", int, thing.variable.width, continue);
+ BRUSSEL_JSON_GET(elm, "Height", int, thing.variable.height, continue);
+ BRUSSEL_JSON_GET(elm, "OpenGLLocation", int, thing.variable.location, continue);
+ BRUSSEL_JSON_GET_DEFAULT(elm, "ArrayLength", int, thing.variable.arrayLength, 1);
+
+ things.push_back(std::move(thing));
+ }
+ };
+ LoadInputOutputThings("Inputs"sv, inputs);
+ LoadInputOutputThings("Outputs"sv, outputs);
+
+ // TODO uniforms
+
+ return true;
+}
+
+Shader::Shader(std::string name)
+ : mName{ name } {
}
Shader::~Shader() {
@@ -247,14 +337,275 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
#undef CATCH_ERROR
-void Shader::GatherDetailsIfAbsent() {
- if (mDetails) return;
+namespace ProjectBrussel_UNITY_ID {
+bool QueryMathInfo(GLenum type, GLenum& scalarType, int& width, int& height) {
+ auto DoOutput = [&](GLenum scalarTypeIn, int widthIn, int heightIn) {
+ width = widthIn;
+ height = heightIn;
+ scalarType = scalarTypeIn;
+ };
+
+ switch (type) {
+ case GL_FLOAT:
+ case GL_DOUBLE:
+ case GL_INT:
+ case GL_UNSIGNED_INT:
+ case GL_BOOL: {
+ DoOutput(type, 1, 1);
+ return true;
+ }
+
+ case GL_FLOAT_VEC2: DoOutput(GL_FLOAT, 1, 2); return true;
+ case GL_FLOAT_VEC3: DoOutput(GL_FLOAT, 1, 3); return true;
+ case GL_FLOAT_VEC4: DoOutput(GL_FLOAT, 1, 4); return true;
+ case GL_DOUBLE_VEC2: DoOutput(GL_DOUBLE, 1, 2); return true;
+ case GL_DOUBLE_VEC3: DoOutput(GL_DOUBLE, 1, 3); return true;
+ case GL_DOUBLE_VEC4: DoOutput(GL_DOUBLE, 1, 4); return true;
+ case GL_INT_VEC2: DoOutput(GL_INT, 1, 2); return true;
+ case GL_INT_VEC3: DoOutput(GL_INT, 1, 3); return true;
+ case GL_INT_VEC4: DoOutput(GL_INT, 1, 4); return true;
+ case GL_UNSIGNED_INT_VEC2: DoOutput(GL_UNSIGNED_INT, 1, 2); return true;
+ case GL_UNSIGNED_INT_VEC3: DoOutput(GL_UNSIGNED_INT, 1, 3); return true;
+ case GL_UNSIGNED_INT_VEC4: DoOutput(GL_UNSIGNED_INT, 1, 4); return true;
+ case GL_BOOL_VEC2: DoOutput(GL_BOOL, 1, 2); return true;
+ case GL_BOOL_VEC3: DoOutput(GL_BOOL, 1, 3); return true;
+ case GL_BOOL_VEC4: DoOutput(GL_BOOL, 1, 4); return true;
+
+ case GL_FLOAT_MAT2: DoOutput(GL_FLOAT, 2, 2); return true;
+ case GL_FLOAT_MAT3: DoOutput(GL_FLOAT, 3, 3); return true;
+ case GL_FLOAT_MAT4: DoOutput(GL_FLOAT, 4, 4); return true;
+ case GL_FLOAT_MAT2x3: DoOutput(GL_FLOAT, 2, 3); return true;
+ case GL_FLOAT_MAT2x4: DoOutput(GL_FLOAT, 2, 4); return true;
+ case GL_FLOAT_MAT3x2: DoOutput(GL_FLOAT, 3, 2); return true;
+ case GL_FLOAT_MAT3x4: DoOutput(GL_FLOAT, 3, 4); return true;
+ case GL_FLOAT_MAT4x2: DoOutput(GL_FLOAT, 4, 2); return true;
+ case GL_FLOAT_MAT4x3: DoOutput(GL_FLOAT, 4, 3); return true;
+
+ case GL_DOUBLE_MAT2: DoOutput(GL_DOUBLE, 2, 2); return true;
+ case GL_DOUBLE_MAT3: DoOutput(GL_DOUBLE, 3, 3); return true;
+ case GL_DOUBLE_MAT4: DoOutput(GL_DOUBLE, 4, 4); return true;
+ case GL_DOUBLE_MAT2x3: DoOutput(GL_DOUBLE, 2, 3); return true;
+ case GL_DOUBLE_MAT2x4: DoOutput(GL_DOUBLE, 2, 4); return true;
+ case GL_DOUBLE_MAT3x2: DoOutput(GL_DOUBLE, 3, 2); return true;
+ case GL_DOUBLE_MAT3x4: DoOutput(GL_DOUBLE, 3, 4); return true;
+ case GL_DOUBLE_MAT4x2: DoOutput(GL_DOUBLE, 4, 2); return true;
+ case GL_DOUBLE_MAT4x3: DoOutput(GL_DOUBLE, 4, 3); return true;
+ }
+
+ return false;
+}
+
+bool QuerySamplerInfo(GLenum type) {
+ switch (type) {
+ case GL_SAMPLER_1D:
+ case GL_SAMPLER_2D:
+ case GL_SAMPLER_3D:
+ case GL_SAMPLER_CUBE:
+ case GL_SAMPLER_1D_SHADOW:
+ case GL_SAMPLER_2D_SHADOW:
+ case GL_SAMPLER_1D_ARRAY:
+ case GL_SAMPLER_2D_ARRAY:
+ case GL_SAMPLER_1D_ARRAY_SHADOW:
+ case GL_SAMPLER_2D_ARRAY_SHADOW:
+ case GL_SAMPLER_2D_MULTISAMPLE:
+ case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_SAMPLER_CUBE_SHADOW:
+ case GL_SAMPLER_BUFFER:
+ case GL_SAMPLER_2D_RECT:
+ case GL_SAMPLER_2D_RECT_SHADOW:
+
+ case GL_INT_SAMPLER_1D:
+ case GL_INT_SAMPLER_2D:
+ case GL_INT_SAMPLER_3D:
+ case GL_INT_SAMPLER_CUBE:
+ case GL_INT_SAMPLER_1D_ARRAY:
+ case GL_INT_SAMPLER_2D_ARRAY:
+ case GL_INT_SAMPLER_2D_MULTISAMPLE:
+ case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_INT_SAMPLER_BUFFER:
+ case GL_INT_SAMPLER_2D_RECT:
- mDetails = std::make_unique<ShaderDetails>();
+ case GL_UNSIGNED_INT_SAMPLER_1D:
+ case GL_UNSIGNED_INT_SAMPLER_2D:
+ case GL_UNSIGNED_INT_SAMPLER_3D:
+ case GL_UNSIGNED_INT_SAMPLER_CUBE:
+ case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
+ case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
+ case GL_UNSIGNED_INT_SAMPLER_BUFFER:
+ case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
+ return true;
+ }
+
+ return false;
}
-const ShaderDetails* Shader::GetDetails() const {
- return mDetails.get();
+std::unique_ptr<ShaderVariable> CreateVariable(GLenum type, GLuint loc) {
+ GLenum scalarType;
+ int width;
+ int height;
+ if (QueryMathInfo(type, scalarType, width, height)) {
+ auto res = std::make_unique<ShaderMathVariable>();
+ res->location = loc;
+ res->scalarType = type;
+ res->width = width;
+ res->height = height;
+ return res;
+ }
+
+ if (QuerySamplerInfo(type)) {
+ auto res = std::make_unique<ShaderSamplerVariable>();
+ res->location = loc;
+ res->type = type;
+ return res;
+ }
+
+ return nullptr;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+bool Shader::CreateEmptyInfoIfAbsent() {
+ if (mInfo || !IsValid()) {
+ return false;
+ }
+
+ mInfo = std::make_unique<ShaderInfo>();
+ return true;
+}
+
+bool Shader::GatherInfoIfAbsent() {
+ using namespace ProjectBrussel_UNITY_ID;
+ using ThingId = ShaderInfo::ThingId;
+
+ if (mInfo || !IsValid()) {
+ return false;
+ }
+
+ mInfo = std::make_unique<ShaderInfo>();
+
+ // TODO handle differnt types of variables with the same name
+
+ // TODO work with OpenGL < 4.3, possibly with glslang
+#if 0
+ int inputCount;
+ glGetProgramInterfaceiv(mHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount);
+ int outputCount;
+ glGetProgramInterfaceiv(mHandle, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount);
+ int uniformBlockCount;
+ glGetProgramInterfaceiv(mHandle, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount);
+ int uniformCount;
+ glGetProgramInterfaceiv(mHandle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount);
+
+ // Gather inputs
+ auto GatherMathVars = [&](int count, GLenum resourceType, ShaderInfo::ThingKind 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)];
+ glGetProgramResourceiv(mHandle, resourceType, i, std::size(query), query, std::size(props), nullptr, props);
+ auto& nameLength = props[0];
+ auto& type = props[1];
+ auto& loc = props[2];
+ auto& arrayLength = props[3];
+
+ std::string fieldName(nameLength - 1, '\0');
+ glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
+
+ mInfo->things.insert(fieldName, ThingId{ resourceKind, i });
+
+ auto& thing = list.emplace_back(ShaderInfo::InputOutputThing{});
+ auto& var = thing.variable;
+ var.name = std::move(fieldName);
+ var.arrayLength = arrayLength;
+ 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);
+
+ // Gather uniform variables
+ for (int i = 0; i < uniformCount; ++i) {
+ const GLenum query[] = { GL_BLOCK_INDEX, GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE };
+ GLint props[std::size(query)];
+ glGetProgramResourceiv(mHandle, GL_UNIFORM, i, std::size(query), query, std::size(props), nullptr, props);
+ auto& blockIndex = props[0]; // Index in interface block
+ if (blockIndex != -1) { // If this is an interface block uniform, skip because it will be handled by our uniform blocks inspector
+ continue;
+ }
+ auto& nameLength = props[1];
+ auto& type = props[2];
+ auto& loc = props[3];
+ auto& arrayLength = props[4];
+
+ 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->uniforms.push_back(CreateVariable(type, loc));
+ }
+
+ // Gather uniform blocks
+ for (int i = 0; i < uniformBlockCount; ++i) {
+ const GLenum blockQuery[] = { GL_NAME_LENGTH, GL_NUM_ACTIVE_VARIABLES };
+ GLint blockProps[std::size(blockQuery)];
+ glGetProgramResourceiv(mHandle, GL_UNIFORM_BLOCK, i, std::size(blockQuery), blockQuery, std::size(blockProps), nullptr, blockProps);
+ auto& nameLength = blockProps[0];
+ auto& fieldCount = blockProps[1];
+
+ // glGetProgramResourceiv returns the length including the null terminator
+ std::string blockName(nameLength - 1, '\0');
+ glGetProgramResourceName(mHandle, GL_UNIFORM_BLOCK, i, nameLength, nullptr, blockName.data());
+
+ const GLenum fieldsQuery[] = { GL_ACTIVE_VARIABLES };
+ std::vector<GLint> fieldIndices(fieldCount);
+ glGetProgramResourceiv(mHandle, GL_UNIFORM_BLOCK, i, std::size(fieldsQuery), fieldsQuery, fieldIndices.size(), nullptr, fieldIndices.data());
+
+ ShaderUniformBlockVariable block;
+ block.index = i;
+ block.name = std::move(blockName);
+ // NOTE: blockName is invalid from this point on
+
+ for (GLint idx : fieldIndices) {
+ const GLenum fieldQuery[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE };
+ GLint fieldProps[std::size(fieldQuery)];
+ glGetProgramResourceiv(mHandle, GL_UNIFORM, idx, std::size(fieldQuery), fieldQuery, std::size(fieldProps), nullptr, fieldProps);
+ auto& nameLength = fieldProps[0];
+ auto& type = fieldProps[1];
+ auto& location = fieldProps[2];
+ auto& arrayLength = fieldProps[3];
+
+ std::string fieldName(nameLength - 1, '\0');
+ glGetProgramResourceName(mHandle, GL_UNIFORM, idx, nameLength, nullptr, fieldName.data());
+
+ auto var = CreateVariable(type, location);
+ if (var->kind == ShaderVariable::KD_Math) {
+ auto math = static_cast<ShaderMathVariable*>(var.get());
+ math->name = std::move(fieldName);
+ math->arrayLength = arrayLength;
+ } else if (var->kind == ShaderMathVariable::KD_Sampler) {
+ auto sampler = static_cast<ShaderSamplerVariable*>(var.get());
+ sampler->name = std::move(fieldName);
+ sampler->arrayLength = arrayLength;
+ }
+ block.items.push_back(std::move(var));
+ }
+
+ mInfo->things.insert(block.name, { ShaderInfo::TKD_UniformBlock, i });
+ mInfo->uniformBlocks.push_back(std::move(block));
+ }
+
+ mInfo->things.shrink_to_fit();
+#endif
+
+ return true;
+}
+
+ShaderInfo* Shader::GetInfo() const {
+ return mInfo.get();
+}
+
+const std::string& Shader::GetName() const {
+ return mName;
}
GLuint Shader::GetProgram() const {
@@ -269,30 +620,44 @@ void ShaderManager::DiscoverShaders() {
mShaders.clear();
auto path = AppConfig::assetDirPath / "Shaders";
+ if (!fs::exists(path)) {
+ return;
+ }
+
for (auto& item : fs::directory_iterator(path)) {
if (item.is_regular_file() && item.path().extension() == ".glsl") {
- auto file = Utils::OpenCstdioFile(item.path(), Utils::Read);
- if (!file) continue;
+ auto shaderFile = Utils::OpenCstdioFile(item.path(), Utils::Read);
+ if (!shaderFile) continue;
+ DEFER { fclose(shaderFile); };
- fseek(file, 0, SEEK_END);
- auto fileSize = ftell(file);
- rewind(file);
+ fseek(shaderFile, 0, SEEK_END);
+ auto shaderFileSize = ftell(shaderFile);
+ rewind(shaderFile);
// Also add \0 ourselves
- auto buffer = std::make_unique<char[]>(fileSize + 1);
- fread(buffer.get(), fileSize, 1, file);
- fclose(file);
- buffer[fileSize] = '\0';
- std::string_view source(buffer.get(), fileSize);
+ auto buffer = std::make_unique<char[]>(shaderFileSize + 1);
+ fread(buffer.get(), shaderFileSize, 1, shaderFile);
+ buffer[shaderFileSize] = '\0';
+ std::string_view source(buffer.get(), shaderFileSize);
+
+ RcPtr shader(new Shader(item.path().stem().string()));
+ std::string_view shaderName(shader->GetName());
- RcPtr shader(new Shader());
+ // Load shader
auto err = shader->InitFromSource(source);
if (err != Shader::Success) {
continue;
}
- auto shaderName = item.path().stem().string();
- mShaders.insert(shaderName, std::move(shader));
+ // Load shader info if present
+ fs::path infoPath(item.path());
+ infoPath.replace_extension(".json");
+ if (fs::exists(infoPath)) {
+ shader->CreateEmptyInfoIfAbsent();
+ shader->GetInfo()->LoadFromFile(infoPath);
+ }
+
+ mShaders.try_emplace(shaderName, std::move(shader));
}
}
}
@@ -300,7 +665,7 @@ void ShaderManager::DiscoverShaders() {
Shader* ShaderManager::FindShader(std::string_view name) {
auto iter = mShaders.find(name);
if (iter != mShaders.end()) {
- return iter.value().Get();
+ return iter->second.Get();
} else {
return nullptr;
}
diff --git a/source/Shader.hpp b/source/Shader.hpp
index e7a069b..8af5217 100644
--- a/source/Shader.hpp
+++ b/source/Shader.hpp
@@ -1,30 +1,102 @@
#pragma once
#include "EditorAttachment.hpp"
-#include "EditorInspector.hpp"
+#include "GraphicsTags.hpp"
#include "RcPtr.hpp"
+#include <absl/container/flat_hash_map.h>
#include <glad/glad.h>
-#include <tsl/array_map.h>
+#include <filesystem>
#include <memory>
#include <string_view>
+#include <vector>
-class ShaderDetails : public EditorAttachment, public IEditorInspectorTarget {
-public:
- std::string fileName;
+// TODO move to variable after pattern matching is in the language
-public:
- virtual void ShowInspector() override;
+struct ShaderVariable {
+ enum Kind {
+ KD_Math,
+ KD_Sampler,
+ KD_UniformBlock,
+ };
+
+ Kind kind;
+
+protected:
+ ShaderVariable(Kind kind)
+ : kind{ kind } {}
+};
+
+struct ShaderMathVariable : public ShaderVariable {
+ std::string name;
+ GLuint location;
+ GLenum scalarType;
+ int arrayLength;
+ int width;
+ int height;
+
+ ShaderMathVariable()
+ : ShaderVariable(KD_Math) {}
+};
+
+struct ShaderSamplerVariable : public ShaderVariable {
+ std::string name;
+ GLuint location;
+ GLenum type;
+ int arrayLength;
+
+ ShaderSamplerVariable()
+ : ShaderVariable(KD_Sampler) {}
+};
+
+struct ShaderUniformBlockVariable : public ShaderVariable {
+ std::string name;
+ /// Possible values: KD_Math
+ std::vector<std::unique_ptr<ShaderVariable>> items;
+ GLuint index;
+
+ ShaderUniformBlockVariable()
+ : ShaderVariable(KD_UniformBlock) {}
+};
+
+struct ShaderInfo {
+ enum ThingKind {
+ TKD_Input,
+ TKD_Output,
+ TKD_Uniform,
+ TKD_UniformBlock,
+ };
+
+ struct ThingId {
+ ThingKind kind;
+ int index;
+ };
+
+ struct InputOutputThing {
+ ShaderMathVariable variable;
+ Tags::VertexElementSemantic semantic = Tags::VES_Generic;
+ };
+
+ absl::flat_hash_map<std::string, ThingId> things;
+ std::vector<InputOutputThing> inputs;
+ std::vector<InputOutputThing> outputs;
+ /// Possible values: KD_Math, KD_Sampler
+ std::vector<std::unique_ptr<ShaderVariable>> uniforms;
+ std::vector<ShaderUniformBlockVariable> uniformBlocks;
+
+ bool SaveToFile(const std::filesystem::path& filePath) const;
+ bool LoadFromFile(const std::filesystem::path& filePath);
};
class Shader : public RefCounted {
private:
- std::unique_ptr<ShaderDetails> mDetails;
+ std::string mName;
+ std::unique_ptr<ShaderInfo> mInfo;
std::unique_ptr<EditorAttachment> mEditorAttachment;
GLuint mHandle = 0;
public:
- Shader() = default;
+ Shader(std::string name = "");
~Shader();
Shader(const Shader&) = delete;
Shader& operator=(const Shader&) = delete;
@@ -70,8 +142,11 @@ public:
EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); }
void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); }
- void GatherDetailsIfAbsent();
- const ShaderDetails* GetDetails() const;
+ bool CreateEmptyInfoIfAbsent();
+ bool GatherInfoIfAbsent();
+ ShaderInfo* GetInfo() const;
+ /// If not empty, this name must not duplicate with any other shader object in the process.
+ const std::string& GetName() const;
GLuint GetProgram() const;
bool IsValid() const;
@@ -82,11 +157,11 @@ public:
static inline ShaderManager* instance = nullptr;
private:
- tsl::array_map<char, RcPtr<Shader>> mShaders;
+ absl::flat_hash_map<std::string_view, RcPtr<Shader>> mShaders;
public:
void DiscoverShaders();
- const tsl::array_map<char, RcPtr<Shader>>& GetShaders() const { return mShaders; }
+ const auto& GetShaders() const { return mShaders; }
Shader* FindShader(std::string_view name);
};