aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-04-17 20:08:57 -0700
committerrtk0c <[email protected]>2022-04-17 20:08:57 -0700
commit5424a1d5434e3ddd911a504719918c2df027e2fd (patch)
tree6275aab13140d81dcc46c8290e73ac9a8bbb5605 /source
parentafcac59c7d04f4337d6b04ebed8cac7e871ccc50 (diff)
Changeset: 8 Initial work on sprites and texture system
Diffstat (limited to 'source')
-rw-r--r--source/App.cpp9
-rw-r--r--source/App.hpp5
-rw-r--r--source/CMakeLists.txt4
-rw-r--r--source/CpuMesh.cpp76
-rw-r--r--source/CpuMesh.hpp53
-rw-r--r--source/EditorAttachmentImpl.hpp6
-rw-r--r--source/EditorCore.cpp169
-rw-r--r--source/EditorCore.hpp12
-rw-r--r--source/EditorResources.cpp54
-rw-r--r--source/EditorResources.hpp8
-rw-r--r--source/Enum.hpp103
-rw-r--r--source/GraphicsTags.cpp105
-rw-r--r--source/GraphicsTags.hpp17
-rw-r--r--source/Image.cpp101
-rw-r--r--source/Image.hpp38
-rw-r--r--source/Ires.cpp176
-rw-r--r--source/Ires.hpp73
-rw-r--r--source/Material.cpp72
-rw-r--r--source/Material.hpp28
-rw-r--r--source/Mesh.cpp59
-rw-r--r--source/Mesh.hpp33
-rw-r--r--source/PodVector.hpp2
-rw-r--r--source/RapidJsonHelper.hpp32
-rw-r--r--source/RcPtr.hpp16
-rw-r--r--source/Rect.hpp164
-rw-r--r--source/Renderer.cpp43
-rw-r--r--source/Renderer.hpp46
-rw-r--r--source/Shader.cpp396
-rw-r--r--source/Shader.hpp67
-rw-r--r--source/Sprite.cpp163
-rw-r--r--source/Sprite.hpp98
-rw-r--r--source/Texture.cpp251
-rw-r--r--source/Texture.hpp92
-rw-r--r--source/Utils.cpp17
-rw-r--r--source/Utils.hpp32
-rw-r--r--source/main.cpp121
36 files changed, 2227 insertions, 514 deletions
diff --git a/source/App.cpp b/source/App.cpp
index ff0a5a5..ac5b319 100644
--- a/source/App.cpp
+++ b/source/App.cpp
@@ -30,13 +30,18 @@ void App::Shutdown() {
}
void App::Show() {
- mCurrentWorld->Draw();
-
if (mEditorShown) {
mEditor->Show();
}
}
+void App::Update() {
+}
+
+void App::Draw() {
+ mCurrentWorld->Draw();
+}
+
void App::HandleMouse(int button, int action) {
}
diff --git a/source/App.hpp b/source/App.hpp
index 731ab06..fbdbd43 100644
--- a/source/App.hpp
+++ b/source/App.hpp
@@ -28,7 +28,12 @@ public:
void Init();
void Shutdown();
+ // Do ImGui calls
void Show();
+ // Do regular calls
+ void Update();
+ void Draw();
+
void HandleMouse(int button, int action);
void HandleMouseMotion(double xOff, double yOff);
void HandleKey(GLFWkeyboard* keyboard, int key, int action);
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index f851bcb..bc4bfdb 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -10,13 +10,17 @@ PRIVATE
EditorUtils.cpp
GameObject.cpp
GraphicsTags.cpp
+ Image.cpp
+ Ires.cpp
Level.cpp
Material.cpp
Mesh.cpp
Player.cpp
+ Renderer.cpp
SceneThings.cpp
Shader.cpp
SmallVector.cpp
+ Sprite.cpp
Texture.cpp
World.cpp
)
diff --git a/source/CpuMesh.cpp b/source/CpuMesh.cpp
index 8e65395..15b0f54 100644
--- a/source/CpuMesh.cpp
+++ b/source/CpuMesh.cpp
@@ -1,58 +1,54 @@
#include "CpuMesh.hpp"
-bool CpuMesh::IsEmpty() const {
- return !mVertexFormat->elements.empty();
-}
+#include <cstring>
-std::byte* CpuMesh::GetVertices() const {
- return mVertexData.get();
+StandardCpuMesh::StandardCpuMesh()
+ : mGpuMesh(new GpuMesh()) {
+ mGpuMesh->vertFormat = gVformatStandard;
+ mGpuMesh->vertBufBindings.SetBinding(0, new GpuVertexBuffer());
+ mGpuMesh->vertBufBindings.SetBinding(1, new GpuVertexBuffer());
+ mGpuMesh->indexBuf.Attach(new GpuIndexBuffer());
}
-int CpuMesh::GetVertexNumBytes() const {
- return mVertexByteCount;
+StandardCpuMesh::~StandardCpuMesh() {
+ delete mData;
}
-std::byte* CpuMesh::GetIndices() const {
- return mIndexData.get();
+void StandardCpuMesh::CreateCpuData() {
+ if (!mData) {
+ mData = new StandardCpuMeshData();
+ }
}
-int CpuMesh::GetIndexNumBytes() const {
- return mIndexCount * Tags::SizeOf(mIndexType);
+GpuVertexBuffer* StandardCpuMesh::GetPosBuffer() const {
+ return mGpuMesh->vertBufBindings.bindings[0].Get();
}
-GpuMesh* CpuMesh::SyncToGpuCreate() const {
- if (IsEmpty()) return nullptr;
-
- auto vertexBuffer = new GpuVertexBuffer();
- vertexBuffer->Upload(mVertexData.get(), GetVertexNumBytes());
+GpuVertexBuffer* StandardCpuMesh::GetExtraBuffer() const {
+ return mGpuMesh->vertBufBindings.bindings[1].Get();
+}
- auto bindings = new BufferBindings();
- for (auto& elm : mVertexFormat->elements) {
- bindings->SetBinding(elm.bindingIndex, vertexBuffer);
+bool StandardCpuMesh::UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex) {
+ if (mData) {
+ std::memcpy(&mData->vertPositions[startVertIndex], pos, count * sizeof(glm::vec3));
}
-
- auto indexBuffer = new GpuIndexBuffer();
- indexBuffer->Upload(mIndexData.get(), mIndexType, mIndexCount);
-
- return new GpuMesh(mVertexFormat.Get(), bindings, indexBuffer);
+ auto posBuf = GetPosBuffer();
+ glBindBuffer(GL_ARRAY_BUFFER, posBuf->handle);
+ glBufferSubData(GL_ARRAY_BUFFER, startVertIndex * mGpuMesh->vertFormat->vertexSize, count * sizeof(glm::vec3), pos);
+ return true;
}
-void CpuMesh::SyncToGpu(GpuMesh& mesh) const {
- if (IsEmpty()) return;
-
- auto& oldFormat = mesh.vertFormat;
- auto& newFormat = this->mVertexFormat;
- if (oldFormat != newFormat) {
- auto buffer = new GpuVertexBuffer();
- buffer->Upload(mVertexData.get(), GetVertexNumBytes());
-
- mesh.vertBufBindings->Clear();
- for (auto& elm : newFormat->elements) {
- mesh.vertBufBindings->SetBinding(elm.bindingIndex, buffer);
- }
+bool StandardCpuMesh::UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex) {
+ if (!mData) return false;
+ // TODO
+}
- oldFormat = newFormat;
- }
+bool StandardCpuMesh::UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex) {
+ if (!mData) return false;
+ // TODO
+}
- mesh.indexBuf->Upload(mIndexData.get(), mIndexType, mIndexCount);
+bool StandardCpuMesh::UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex) {
+ if (!mData) return false;
+ // TODO
}
diff --git a/source/CpuMesh.hpp b/source/CpuMesh.hpp
index 7c6e2c8..9fcb00c 100644
--- a/source/CpuMesh.hpp
+++ b/source/CpuMesh.hpp
@@ -1,28 +1,51 @@
#pragma once
+#include "Color.hpp"
#include "Mesh.hpp"
+#include "PodVector.hpp"
#include "RcPtr.hpp"
#include <cstddef>
+#include <cstdint>
+#include <glm/glm.hpp>
#include <memory>
-class CpuMesh : public RefCounted {
+struct StandardVertex {
+ float x, y, z;
+ float u, v;
+ uint8_t r, g, b, a;
+};
+
+struct StandardVertexExtra {
+ float u, v;
+ uint8_t r, g, b, a;
+};
+
+class StandardCpuMeshData {
+public:
+ PodVector<glm::vec3> vertPositions;
+ PodVector<StandardVertexExtra> vertExtra;
+ PodVector<uint32_t> index;
+ size_t vertexCount;
+ size_t triangleCount;
+};
+
+class StandardCpuMesh {
private:
- std::unique_ptr<std::byte[]> mVertexData;
- std::unique_ptr<std::byte[]> mIndexData;
- RcPtr<GpuMesh> mGpuMesh;
- RcPtr<VertexFormat> mVertexFormat;
- Tags::IndexType mIndexType;
- int mVertexByteCount;
- int mIndexCount;
+ StandardCpuMeshData* mData = nullptr;
+ RcPtr<GpuMesh> mGpuMesh;
public:
- bool IsEmpty() const;
- std::byte* GetVertices() const;
- int GetVertexNumBytes() const;
- std::byte* GetIndices() const;
- int GetIndexNumBytes() const;
+ StandardCpuMesh();
+ ~StandardCpuMesh();
+
+ GpuVertexBuffer* GetPosBuffer() const;
+ GpuVertexBuffer* GetExtraBuffer() const;
+ GpuMesh* GetGpuMesh() const { return mGpuMesh.Get(); }
- GpuMesh* SyncToGpuCreate() const;
- void SyncToGpu(GpuMesh& mesh) const;
+ void CreateCpuData();
+ bool UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex);
+ bool UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex);
+ bool UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex);
+ bool UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex);
};
diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp
index cb96348..258e987 100644
--- a/source/EditorAttachmentImpl.hpp
+++ b/source/EditorAttachmentImpl.hpp
@@ -19,6 +19,12 @@ class EaLevelWrapper : public EditorAttachment {
public:
};
+class EaIresObject : public EditorAttachment {
+public:
+ std::string nameEditingScratch;
+ bool isEditingName = false;
+};
+
class EaShader : public EditorAttachment {
public:
Shader* shader;
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
index 1846962..0c2e6a2 100644
--- a/source/EditorCore.cpp
+++ b/source/EditorCore.cpp
@@ -97,6 +97,7 @@ void EditorInstance::Show() {
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_Ires: ShowInspector("", static_cast<IresObject*>(mSelectedItPtr)); break; // TODO
case ITT_None: break;
}
ImGui::End();
@@ -104,6 +105,8 @@ void EditorInstance::Show() {
if (mEdContentBrowserVisible) {
mEdContentBrowser.Show(&mEdContentBrowserVisible);
}
+
+ ShowSpriteViewer();
}
void EditorInstance::SelectIt(void* ptr, InspectorTargetType itt) {
@@ -129,50 +132,37 @@ void EditorInstance::ShowInspector(Shader* shader) {
shader->SetEditorAttachment(attachment);
}
- 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& info = shader->GetInfo();
auto& name = shader->GetName();
bool isAnnoymous = name.empty();
ShowShaderName(shader);
- if (ImGui::Button("Reimport metadata", isAnnoymous)) {
- info->LoadFromFile(shader->GetDesignatedMetadataPath());
+ if (ImGui::Button("Reload metadata", isAnnoymous)) {
+ shader->LoadMetadataFromFile(shader->GetDesignatedMetadataPath());
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Save metadata", isAnnoymous)) {
+ shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath());
}
ImGui::SameLine();
- if (ImGui::Button("Export metadata", isAnnoymous)) {
- info->SaveToFile(shader->GetDesignatedMetadataPath());
+ if (ImGui::Button("Gather info")) {
+ shader->GatherInfoShaderIntrospection();
}
- 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);
+ for (auto& input : info.inputs) {
+ input.ShowInfo();
+ }
}
if (ImGui::CollapsingHeader("Outputs")) {
- ShowThing(info->outputs);
+ for (auto& output : info.outputs) {
+ output.ShowInfo();
+ }
}
if (ImGui::CollapsingHeader("Uniforms")) {
- }
- if (ImGui::CollapsingHeader("Uniform blocks")) {
+ for (auto& uniform : info.uniforms) {
+ uniform->ShowInfo();
+ }
}
}
@@ -190,8 +180,6 @@ void EditorInstance::ShowInspector(Material* material) {
auto& name = material->GetName();
bool isAnnoymous = name.empty();
- auto shader = material->GetShader();
-
if (isAnnoymous) {
ImGui::Text("<Annoymous Material at %p>", (void*)(&material));
} else {
@@ -210,8 +198,6 @@ void EditorInstance::ShowInspector(Material* material) {
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);
@@ -223,6 +209,7 @@ void EditorInstance::ShowInspector(Material* material) {
}
}
+ auto shader = material->GetShader();
ShowShaderName(shader);
if (ImGui::BeginDragDropTarget()) {
if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_DRAG_DROP_SHADER)) {
@@ -237,6 +224,9 @@ void EditorInstance::ShowInspector(Material* material) {
mSelectedItPtr = shader;
}
+ if (!shader) return;
+ auto& info = shader->GetInfo();
+
if (ImGui::Button("Reload", isAnnoymous)) {
material->LoadFromFile(material->GetDesignatedPath());
}
@@ -246,19 +236,112 @@ void EditorInstance::ShowInspector(Material* material) {
}
for (auto& field : material->mBoundScalars) {
- // TODO
+ auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]);
+ decl.ShowInfo();
+
+ ImGui::Indent();
+ switch (decl.scalarType) {
+ case GL_FLOAT: ImGui::InputFloat("##", &field.floatValue); break;
+ case GL_INT: ImGui::InputInt("##", &field.intValue); break;
+ // TODO proper uint edit?
+ case GL_UNSIGNED_INT: ImGui::InputInt("##", (int32_t*)(&field.uintValue), 0, std::numeric_limits<int32_t>::max()); break;
+ default: ImGui::TextUnformatted("Unsupported scalar type"); break;
+ }
+ ImGui::Unindent();
}
for (auto& field : material->mBoundVectors) {
- // TODO
+ auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]);
+ decl.ShowInfo();
+
+ ImGui::Indent();
+ switch (decl.semantic) {
+ case VES_Color1:
+ case VES_Color2: {
+ ImGui::ColorEdit4("##", field.value);
+ } break;
+
+ default: {
+ ImGui::InputFloat4("##", field.value);
+ } break;
+ }
+ ImGui::Unindent();
}
for (auto& field : material->mBoundMatrices) {
+ auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]);
+ decl.ShowInfo();
+
// TODO
}
for (auto& field : material->mBoundTextures) {
+ auto& decl = static_cast<ShaderSamplerVariable&>(*info.uniforms[field.infoUniformIndex]);
+ decl.ShowInfo();
+
// TODO
}
}
+void EditorInstance::ShowInspector(const std::string& path, IresObject* genericIres) {
+ ImGui::Text("%s", path.c_str());
+
+ switch (genericIres->GetKind()) {
+ case IresObject::KD_Texture: {
+ ImGui::TextUnformatted("Texture");
+ ImGui::Separator();
+
+ // TODO
+ ImGui::TextUnformatted("Unimplemented");
+ } break;
+
+ case IresObject::KD_SpriteFiles: {
+ ImGui::TextUnformatted("Sprite Files");
+ ImGui::Separator();
+
+ // TODO
+ ImGui::TextUnformatted("Unimplemented");
+ } break;
+
+ case IresObject::KD_Spritesheet: {
+ ImGui::TextUnformatted("Spritesheet");
+ ImGui::Separator();
+
+ auto& ires = *static_cast<IresSpritesheet*>(genericIres);
+ auto instance = ires.GetInstance(); // NOTE: may be null
+
+ if (ImGui::Button("View Sprite", instance == nullptr)) {
+ OpenSpriteViewer(instance);
+ }
+
+ bool doInvalidateInstance = false;
+ if (ImGui::InputText("Spritesheet", &ires.spritesheetFile)) {
+ doInvalidateInstance = true;
+ }
+ if (ImGui::InputInt("Horizontal Split", &ires.sheetWSplit)) {
+ if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit);
+ }
+ if (ImGui::InputInt("Vertical Split", &ires.sheetHSplit)) {
+ if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit);
+ }
+
+ if (instance) {
+ auto atlas = instance->GetAtlas();
+ auto aspectRatio = (float)atlas->GetInfo().size.y / atlas->GetInfo().size.x;
+ ImVec2 size;
+ size.x = ImGui::GetContentRegionAvail().x;
+ size.y = aspectRatio * size.x;
+ ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), size);
+ } else {
+ ImGui::TextUnformatted("Sprite configuration invalid");
+ }
+
+ if (doInvalidateInstance) {
+ ires.InvalidateInstance();
+ }
+ } break;
+
+ case IresObject::KD_COUNT: break;
+ }
+}
+
void EditorInstance::ShowInspector(GameObject* object) {
using namespace Tags;
using namespace ProjectBrussel_UNITY_ID;
@@ -367,3 +450,15 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) {
ImGui::TreePop();
}
}
+
+void EditorInstance::OpenSpriteViewer(Sprite* sprite) {
+ mSpriteView_Instance.Attach(sprite);
+ ImGui::OpenPopup("Sprite Viewer");
+}
+
+void EditorInstance::ShowSpriteViewer() {
+ if (ImGui::BeginPopup("Sprite Viewer")) {
+ // TODO
+ ImGui::EndPopup();
+ }
+}
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
index 3d91fa6..c604f36 100644
--- a/source/EditorCore.hpp
+++ b/source/EditorCore.hpp
@@ -3,6 +3,9 @@
#include "EditorAttachment.hpp"
#include "EditorResources.hpp"
#include "GameObject.hpp"
+#include "Ires.hpp"
+#include "RcPtr.hpp"
+#include "Sprite.hpp"
#include "World.hpp"
#include <memory>
@@ -15,16 +18,19 @@ public:
ITT_GameObject,
ITT_Shader,
ITT_Material,
+ ITT_Ires,
ITT_None,
};
private:
App* mApp;
GameWorld* mWorld;
+ // TODO store more fields for ITT
void* mSelectedItPtr = nullptr;
+ RcPtr<Sprite> mSpriteView_Instance;
EditorContentBrowser mEdContentBrowser;
InspectorTargetType mSelectedItt = ITT_None;
- bool mEdContentBrowserVisible=false;
+ bool mEdContentBrowserVisible = false;
public:
EditorInstance(App* app, GameWorld* world);
@@ -41,8 +47,12 @@ private:
void ShowInspector(Shader* shader);
void ShowInspector(Material* material);
+ void ShowInspector(const std::string& path, IresObject* ires);
void ShowInspector(GameObject* object);
void ShowGameObjecetFields(GameObject* object);
void ShowGameObjectInTree(GameObject* object);
+
+ void OpenSpriteViewer(Sprite* sprite);
+ void ShowSpriteViewer();
};
diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp
index be7cefb..4f1a7c4 100644
--- a/source/EditorResources.cpp
+++ b/source/EditorResources.cpp
@@ -25,20 +25,20 @@ EditorContentBrowser::~EditorContentBrowser() {
void EditorContentBrowser::Show(bool* open) {
ImGuiWindowFlags windowFlags;
- if (docked) {
+ if (mDocked) {
// 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);
+ ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight);
windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
} else {
windowFlags = 0;
}
ImGui::Begin("Content Browser", open, windowFlags);
- ImGui::Splitter(true, kSplitterThickness, &splitterLeft, &splitterRight, kLeftPaneMinWidth, kRightPaneMinWidth);
+ ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth);
- ImGui::BeginChild("LeftPane", ImVec2(splitterLeft - kPadding, 0.0f));
+ ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f));
{
if (ImGui::Selectable("Settings", mPane == P_Settings)) {
mPane = P_Settings;
@@ -49,6 +49,9 @@ void EditorContentBrowser::Show(bool* open) {
if (ImGui::Selectable("Materials", mPane == P_Material)) {
mPane = P_Material;
}
+ if (ImGui::Selectable("Ires", mPane == P_Ires)) {
+ mPane = P_Ires;
+ }
}
ImGui::EndChild();
@@ -57,7 +60,8 @@ void EditorContentBrowser::Show(bool* open) {
{
switch (mPane) {
case P_Settings: {
- ImGui::Checkbox("Docked", &docked);
+ ImGui::Checkbox("Docked", &mDocked);
+ ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f);
} break;
case P_Shader: {
@@ -68,10 +72,7 @@ void EditorContentBrowser::Show(bool* open) {
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());
- }
+ shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath());
}
}
@@ -80,9 +81,6 @@ void EditorContentBrowser::Show(bool* open) {
auto shader = it->second.Get();
auto& name = shader->GetName();
- shader->GatherInfoIfAbsent();
- auto details = shader->GetInfo();
-
bool selected = mEditor->GetSelectedItPtr() == shader;
if (ImGui::Selectable(name.c_str(), selected)) {
mEditor->SelectIt(shader, EditorInstance::ITT_Shader);
@@ -100,7 +98,7 @@ void EditorContentBrowser::Show(bool* open) {
case P_Material: {
if (ImGui::Button("New")) {
int n = std::rand();
- auto mat = new Material(nullptr, "Unnamed Material " + std::to_string(n));
+ auto mat = new Material("Unnamed Material " + std::to_string(n));
auto guard = GuardDeletion(mat);
auto [DISCARD, inserted] = MaterialManager::instance->SaveMaterial(mat);
if (inserted) {
@@ -139,6 +137,36 @@ void EditorContentBrowser::Show(bool* open) {
}
}
} break;
+
+ case P_Ires: {
+ if (ImGui::Button("New")) {
+ ImGui::OpenPopup("NewIresMenu");
+ }
+ if (ImGui::BeginPopup("NewIresMenu")) {
+ for (int i = 0; i < IresObject::KD_COUNT; ++i) {
+ auto kind = static_cast<IresObject::Kind>(i);
+ if (ImGui::MenuItem(IresObject::ToString(kind).data())) {
+ auto ires = IresObject::Create(kind);
+ auto [DISCARD, success] = IresManager::instance->Add(ires.get());
+ if (success) {
+ (void)ires.release();
+ }
+ }
+ }
+ ImGui::EndPopup();
+ }
+
+ auto& objects = IresManager::instance->GetObjects();
+ for (auto it = objects.begin(); it != objects.end(); ++it) {
+ auto ires = it->second.Get();
+ auto& name = ires->GetName();
+
+ bool selected = mEditor->GetSelectedItPtr() == ires;
+ if (ImGui::Selectable(name.c_str(), selected)) {
+ mEditor->SelectIt(ires, EditorInstance::ITT_Ires);
+ }
+ }
+ } break;
}
}
ImGui::EndChild();
diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp
index 81145a3..65969f0 100644
--- a/source/EditorResources.hpp
+++ b/source/EditorResources.hpp
@@ -11,6 +11,7 @@ private:
P_Settings,
P_Shader,
P_Material,
+ P_Ires,
};
static constexpr float kSplitterThickness = 3.0f;
@@ -22,9 +23,10 @@ private:
EditorInstance* mEditor;
Pane mPane = P_Settings;
- float splitterLeft = kLeftPaneMinWidth;
- float splitterRight = 0.0f;
- bool docked = true;
+ float mBrowserHeight = 0.5f;
+ float mSplitterLeft = kLeftPaneMinWidth;
+ float mSplitterRight = 0.0f;
+ bool mDocked = true;
public:
EditorContentBrowser(EditorInstance* editor);
diff --git a/source/Enum.hpp b/source/Enum.hpp
new file mode 100644
index 0000000..5e106fe
--- /dev/null
+++ b/source/Enum.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <initializer_list>
+#include <type_traits>
+
+template <class TEnum>
+class EnumFlags {
+public:
+ using Enum = TEnum;
+ using Underlying = std::underlying_type_t<TEnum>;
+
+private:
+ Underlying mValue;
+
+public:
+ EnumFlags()
+ : mValue{ 0 } {
+ }
+
+ EnumFlags(TEnum e)
+ : mValue{ 1 << static_cast<Underlying>(e) } {
+ }
+
+ bool IsSet(EnumFlags mask) const {
+ return (mValue & mask.mValue) == mask.mValue;
+ }
+
+ bool IsSet(std::initializer_list<TEnum> enums) {
+ EnumFlags flags;
+ for (auto& e : enums) {
+ flags.mValue |= static_cast<Underlying>(e);
+ }
+ return IsSet(flags);
+ }
+
+ bool IsSetExclusive(EnumFlags mask) const {
+ return mValue == mask.mValue;
+ }
+
+ bool IsSetExclusive(std::initializer_list<TEnum> enums) {
+ EnumFlags flags;
+ for (auto& e : enums) {
+ flags.mValue |= static_cast<Underlying>(e);
+ }
+ return IsSetExclusive(flags);
+ }
+
+ void SetOn(EnumFlags mask) {
+ mValue |= mask.mValue;
+ }
+
+ void SetOff(EnumFlags mask) {
+ mValue &= ~mask.mValue;
+ }
+
+ void Set(EnumFlags mask, bool enabled) {
+ if (enabled) {
+ SetOn(mask);
+ } else {
+ SetOff(mask);
+ }
+ }
+
+ EnumFlags& operator|=(EnumFlags that) const {
+ mValue |= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator&=(EnumFlags that) const {
+ mValue &= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator^=(EnumFlags that) const {
+ mValue ^= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator|=(TEnum e) const {
+ mValue |= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags& operator&=(TEnum e) const {
+ mValue &= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags& operator^=(TEnum e) const {
+ mValue ^= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags operator|(EnumFlags that) const { return EnumFlags(mValue | that.mValue); }
+ EnumFlags operator&(EnumFlags that) const { return EnumFlags(mValue & that.mValue); }
+ EnumFlags operator^(EnumFlags that) const { return EnumFlags(mValue ^ that.mValue); }
+
+ EnumFlags operator|(TEnum e) const { return EnumFlags(mValue | 1 << static_cast<Underlying>(e)); }
+ EnumFlags operator&(TEnum e) const { return EnumFlags(mValue & 1 << static_cast<Underlying>(e)); }
+ EnumFlags operator^(TEnum e) const { return EnumFlags(mValue ^ 1 << static_cast<Underlying>(e)); }
+
+ EnumFlags operator~() const { return EnumFlags(~mValue); }
+};
diff --git a/source/GraphicsTags.cpp b/source/GraphicsTags.cpp
index d2da091..b389acf 100644
--- a/source/GraphicsTags.cpp
+++ b/source/GraphicsTags.cpp
@@ -1,6 +1,6 @@
#include "GraphicsTags.hpp"
-#include <absl/container/flat_hash_map.h>
+#include <robin_hood.h>
#include <cstddef>
#include <cstdint>
@@ -14,7 +14,10 @@ std::string_view Tags::NameOf(VertexElementSemantic semantic) {
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_Color3: return "Color3"sv;
+ case VES_TexCoords1: return "TexCoords1"sv;
+ case VES_TexCoords2: return "TexCoords2"sv;
+ case VES_TexCoords3: return "TexCoords3"sv;
case VES_Binormal: return "Binormal"sv;
case VES_Tangent: return "Tangent"sv;
case VES_Generic: return "Generic"sv;
@@ -30,7 +33,10 @@ Tags::VertexElementSemantic Tags::FindVertexElementSemantic(std::string_view nam
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 == "Color3"sv) return VES_Color3;
+ if (name == "TexCoords1"sv) return VES_TexCoords1;
+ if (name == "TexCoords2"sv) return VES_TexCoords2;
+ if (name == "TexCoords3"sv) return VES_TexCoords3;
if (name == "Binormal"sv) return VES_Binormal;
if (name == "Tangent"sv) return VES_Tangent;
if (name == "Generic"sv) return VES_Generic;
@@ -86,6 +92,86 @@ int Tags::SizeOf(VertexElementType type) {
return 0;
}
+int Tags::VectorLenOf(VertexElementType type) {
+ switch (type) {
+ case VET_Float1:
+ case VET_Double1:
+ case VET_Int1:
+ case VET_Uint1:
+ return 1;
+ case VET_Float2:
+ case VET_Double2:
+ case VET_Short2:
+ case VET_Short2Norm:
+ case VET_Ushort2:
+ case VET_Ushort2Norm:
+ case VET_Int2:
+ case VET_Uint2:
+ return 2;
+ case VET_Float3:
+ case VET_Double3:
+ case VET_Int3:
+ case VET_Uint3:
+ return 3;
+ case VET_Float4:
+ case VET_Double4:
+ case VET_Short4:
+ case VET_Short4Norm:
+ case VET_Ushort4:
+ case VET_Ushort4Norm:
+ case VET_Int4:
+ case VET_Uint4:
+ case VET_Byte4:
+ case VET_Byte4Norm:
+ case VET_Ubyte4:
+ case VET_Ubyte4Norm:
+ return 4;
+ }
+ return 0;
+}
+
+GLenum Tags::FindGLType(VertexElementType type) {
+ switch (type) {
+ case VET_Float1:
+ case VET_Float2:
+ case VET_Float3:
+ case VET_Float4:
+ return GL_FLOAT;
+ case VET_Double1:
+ case VET_Double2:
+ case VET_Double3:
+ case VET_Double4:
+ return GL_DOUBLE;
+ case VET_Short2:
+ case VET_Short2Norm:
+ case VET_Short4:
+ case VET_Short4Norm:
+ return GL_SHORT;
+ case VET_Ushort2:
+ case VET_Ushort2Norm:
+ case VET_Ushort4:
+ case VET_Ushort4Norm:
+ return GL_UNSIGNED_SHORT;
+ case VET_Int1:
+ case VET_Int2:
+ case VET_Int3:
+ case VET_Int4:
+ return GL_INT;
+ case VET_Uint1:
+ case VET_Uint2:
+ case VET_Uint3:
+ case VET_Uint4:
+ return GL_UNSIGNED_INT;
+ case VET_Byte4:
+ case VET_Byte4Norm:
+ return GL_BYTE;
+ case VET_Ubyte4:
+ case VET_Ubyte4Norm:
+ return GL_UNSIGNED_BYTE;
+ }
+ return 0;
+}
+
bool Tags::IsNormalized(VertexElementType type) {
return type >= VET_NORM_BEGIN && type <= VET_NORM_END;
}
@@ -98,11 +184,18 @@ int Tags::SizeOf(IndexType type) {
return 0;
}
-namespace ProjectBrussel_UNITY_ID {
+GLenum Tags::FindGLType(IndexType type) {
+ switch (type) {
+ case IT_16Bit: return GL_UNSIGNED_SHORT;
+ case IT_32Bit: return GL_UNSIGNED_BYTE;
+ }
+ return GL_NONE;
+}
+namespace ProjectBrussel_UNITY_ID {
struct GLTypeInfo {
- absl::flat_hash_map<GLenum, std::string_view> enum2Name;
- absl::flat_hash_map<std::string_view, GLenum> name2Enum;
+ robin_hood::unordered_flat_map<GLenum, std::string_view> enum2Name;
+ robin_hood::unordered_flat_map<std::string_view, GLenum> name2Enum;
GLTypeInfo() {
InsertEntry("float"sv, GL_FLOAT);
diff --git a/source/GraphicsTags.hpp b/source/GraphicsTags.hpp
index 465c57d..34c0885 100644
--- a/source/GraphicsTags.hpp
+++ b/source/GraphicsTags.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <glad/glad.h>
+#include <limits>
#include <string>
#include <string_view>
@@ -17,10 +18,12 @@ enum VertexElementSemantic {
VES_Normal,
/// Colour, typically VET_Ubyte4
VES_Color1,
- /// Secondary colour. Generally free for custom data. Means specular with OpenGL FFP.
VES_Color2,
+ VES_Color3,
/// Texture coordinates, typically VET_Float2
- VES_Texture_coordinates,
+ VES_TexCoords1,
+ VES_TexCoords2,
+ VES_TexCoords3,
/// Binormal (Y axis if normal is Z)
VES_Binormal,
/// Tangent (X axis if normal is Z)
@@ -71,6 +74,8 @@ enum VertexElementType {
};
int SizeOf(VertexElementType type);
+int VectorLenOf(VertexElementType type);
+GLenum FindGLType(VertexElementType type);
bool IsNormalized(VertexElementType type);
enum IndexType {
@@ -79,7 +84,15 @@ enum IndexType {
};
int SizeOf(IndexType type);
+GLenum FindGLType(IndexType type);
+
+enum TexFilter {
+ TF_Linear,
+ TF_Nearest,
+};
std::string_view NameOfGLType(GLenum);
GLenum FindGLType(std::string_view name);
+
+constexpr GLuint kInvalidLocation = std::numeric_limits<GLuint>::max();
} // namespace Tags
diff --git a/source/Image.cpp b/source/Image.cpp
new file mode 100644
index 0000000..3673acc
--- /dev/null
+++ b/source/Image.cpp
@@ -0,0 +1,101 @@
+#include "Image.hpp"
+
+#include <stb_image.h>
+#include <stb_rect_pack.h>
+#include <cstring>
+
+Image::Image()
+ : mSize{}
+ , mChannels{ 0 } {
+}
+
+bool Image::InitFromImageFile(const char* filePath, int desiredChannels) {
+ // Dimensions of the image in
+ int width, height;
+ // Number of channels that the image has, we'll get `desiredChannels` channels in our output (if it's non-0, which is the default argument)
+ int channels;
+
+ // NOTE: don't free, the data is passed to std::unique_ptr
+ auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, desiredChannels);
+ if (!result) {
+ return false;
+ }
+
+ mData.reset(result);
+ mSize = { width, height };
+ mChannels = desiredChannels == 0 ? channels : desiredChannels;
+ return true;
+}
+
+bool Image::InitFromImageData(std::span<uint8_t> data, int desiredChannels) {
+ int width, height;
+ int channels;
+
+ // NOTE: don't free, the data is passed to std::unique_ptr
+ auto result = (uint8_t*)stbi_load_from_memory(data.data(), data.size(), &width, &height, &channels, desiredChannels);
+ if (!result) {
+ return false;
+ }
+
+ mData.reset(result);
+ mSize = { width, height };
+ mChannels = desiredChannels == 0 ? channels : desiredChannels;
+ return true;
+}
+
+bool Image::InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels) {
+ mData = std::make_unique<uint8_t[]>(pixels.size());
+ std::memcpy(mData.get(), pixels.data(), pixels.size());
+ mSize = dimensions;
+ mChannels = channels;
+ return true;
+}
+
+bool Image::InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels) {
+ mData = std::move(pixels);
+ mSize = dimensions;
+ mChannels = channels;
+ return true;
+}
+
+RgbaColor Image::GetPixel(int x, int y) const {
+ size_t offset = (y * mSize.x + x) * mChannels;
+ RgbaColor color;
+ color.r = mData.get()[offset + 0];
+ color.g = mData.get()[offset + 1];
+ color.b = mData.get()[offset + 2];
+ color.a = mData.get()[offset + 3];
+ return color;
+}
+
+void Image::SetPixel(int x, int y, RgbaColor color) {
+ size_t offset = (y * mSize.x + x) * mChannels;
+ mData.get()[offset + 0] = color.r;
+ mData.get()[offset + 1] = color.g;
+ mData.get()[offset + 2] = color.b;
+ mData.get()[offset + 3] = color.a;
+}
+
+uint8_t* Image::GetDataPtr() const {
+ return mData.get();
+}
+
+size_t Image::GetDataLength() const {
+ return mSize.x * mSize.y * mChannels * sizeof(uint8_t);
+}
+
+std::span<uint8_t> Image::GetData() const {
+ return { mData.get(), GetDataLength() };
+}
+
+glm::ivec2 Image::GetSize() const {
+ return mSize;
+}
+
+int Image::GetChannels() const {
+ return mChannels;
+}
+
+bool Image::IsEmpty() const {
+ return mSize.x == 0 || mSize.y == 0;
+}
diff --git a/source/Image.hpp b/source/Image.hpp
new file mode 100644
index 0000000..c577c24
--- /dev/null
+++ b/source/Image.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "Color.hpp"
+#include "RcPtr.hpp"
+
+#include <cstdint>
+#include <glm/glm.hpp>
+#include <memory>
+#include <span>
+
+/// Image is a 2d array of pixels, stored as a continuous array in memory, with the first pixel
+/// being the top-left pixel. If a vertically flipped image data is needed, load using stb_image
+/// yourself, or flip the data here.
+class Image : public RefCounted {
+private:
+ std::unique_ptr<uint8_t[]> mData;
+ glm::ivec2 mSize;
+ int mChannels;
+
+public:
+ Image();
+
+ bool InitFromImageFile(const char* filePath, int desiredChannels = 0);
+ bool InitFromImageData(std::span<uint8_t> data, int desiredChannels = 0);
+ bool InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels);
+ bool InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels);
+
+ /// Get the pixel at the given location.
+ RgbaColor GetPixel(int x, int y) const;
+ void SetPixel(int x, int y, RgbaColor color);
+
+ uint8_t* GetDataPtr() const;
+ size_t GetDataLength() const;
+ std::span<uint8_t> GetData() const;
+ glm::ivec2 GetSize() const;
+ int GetChannels() const;
+ bool IsEmpty() const;
+};
diff --git a/source/Ires.cpp b/source/Ires.cpp
new file mode 100644
index 0000000..f700ed6
--- /dev/null
+++ b/source/Ires.cpp
@@ -0,0 +1,176 @@
+#include "Ires.hpp"
+
+#include "AppConfig.hpp"
+#include "RapidJsonHelper.hpp"
+#include "ScopeGuard.hpp"
+#include "Sprite.hpp"
+#include "Texture.hpp"
+#include "Utils.hpp"
+
+#include <rapidjson/document.h>
+#include <rapidjson/filereadstream.h>
+#include <rapidjson/filewritestream.h>
+#include <rapidjson/writer.h>
+#include <algorithm>
+
+namespace fs = std::filesystem;
+using namespace std::literals;
+
+IresObject::IresObject(Kind kind)
+ : mKind{ kind } {
+}
+
+std::string_view IresObject::ToString(Kind kind) {
+ switch (kind) {
+ case KD_Texture: return "Texture"sv;
+ case KD_SpriteFiles: return "SpriteFiles"sv;
+ case KD_Spritesheet: return "Spritesheet"sv;
+ case KD_COUNT: break;
+ }
+ return std::string_view();
+}
+
+IresObject::Kind IresObject::FromString(std::string_view name) {
+ if (name == "Texture"sv) return KD_Texture;
+ if (name == "SpriteFiles"sv) return KD_SpriteFiles;
+ if (name == "Spritesheet"sv) return KD_Spritesheet;
+ return KD_COUNT;
+}
+
+std::unique_ptr<IresObject> IresObject::Create(Kind kind) {
+ switch (kind) {
+ case KD_Texture: return std::make_unique<IresTexture>();
+ case KD_SpriteFiles: return std::make_unique<IresSpriteFiles>();
+ case KD_Spritesheet: return std::make_unique<IresSpritesheet>();
+ case KD_COUNT: break;
+ }
+ return nullptr;
+}
+
+bool IresObject::IsAnnoymous() const {
+ return mName.empty();
+}
+
+rapidjson::Value IresObject::WriteFull(const IresObject& ires, rapidjson::Document& root) {
+ rapidjson::Value rvIres;
+ ires.Write(rvIres, root);
+
+ rapidjson::Value result;
+ result.AddMember("Type", rapidjson::StringRef(ToString(ires.GetKind())), root.GetAllocator());
+ result.AddMember("Value", rvIres, root.GetAllocator());
+ return result;
+}
+
+std::unique_ptr<IresObject> IresObject::ReadFull(const rapidjson::Value& value) {
+ auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv);
+ if (!rvType) return nullptr;
+ auto kind = FromString(rapidjson::AsStringView(*rvType));
+ auto ires = Create(kind);
+ if (!ires) return nullptr;
+
+ auto rvValue = rapidjson::GetProperty(value, "Value"sv);
+ if (!rvValue) return nullptr;
+ ires->Read(*rvValue);
+
+ return ires;
+}
+
+void IresManager::DiscoverFilesDesignatedLocation() {
+ auto path = AppConfig::assetDirPath / "Ires";
+ DiscoverFiles(path);
+}
+
+void IresManager::DiscoverFiles(const fs::path& dir) {
+ // NOTE: by default does not follow symlinks
+ // for (auto& item : fs::recursive_directory_iterator(dir)) {
+ for (auto& item : fs::directory_iterator(dir)) {
+ if (!item.is_regular_file()) {
+ continue;
+ }
+ if (item.path().extension() != ".json") {
+ continue;
+ }
+
+ auto file = Utils::OpenCstdioFile(item.path(), Utils::Read);
+ if (!file) continue;
+ DEFER { fclose(file); };
+
+ char readerBuffer[65536];
+ rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer));
+
+ rapidjson::Document root;
+ root.ParseStream(stream);
+
+ auto ires = IresObject::ReadFull(root);
+ if (!ires) {
+ continue;
+ }
+
+ auto iden = fs::path(item.path()).replace_extension().lexically_relative(dir).string();
+ std::replace(iden.begin(), iden.end(), '\\', '/');
+
+#if 0
+ std::string_view idenView(iden);
+
+ // Trim heading slashes
+ while (idenView.front() == '/') {
+ idenView = std::string_view(idenView.data() + 1, idenView.size());
+ }
+ // Trim trailing slashes
+ while (idenView.back() == '/') {
+ idenView = std::string_view(idenView.data(), idenView.size() - 1);
+ }
+#endif
+ mObjects.try_emplace(std::move(iden), ires.release());
+ }
+}
+
+std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) {
+ auto& name = ires->mName;
+ if (name.empty()) {
+ int n = std::rand();
+ // NOTE: does not include null-terminator
+ int size = snprintf(nullptr, 0, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n);
+ name.resize(size); // std::string::resize handles storage for null-terminator alreaedy
+ snprintf(name.data(), size, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n);
+ }
+
+ auto [iter, inserted] = mObjects.try_emplace(name, ires);
+ if (inserted) {
+ ires->mMan = this;
+ return { ires, true };
+ } else {
+ return { iter->second.Get(), false };
+ }
+}
+
+void IresManager::Delete(IresObject* ires) {
+ // TODO
+}
+
+bool IresManager::Rename(IresObject* ires, std::string newName) {
+ if (mObjects.contains(newName)) {
+ return false;
+ }
+
+ // Keep the material from being deleted, in case the old entry in map is the only one existing
+ RcPtr rc(ires);
+
+ // Remove old entry (must do before replacing Material::mName, because the std::string_view in the map is a reference to it)
+ mObjects.erase(ires->GetName());
+
+ // Add new entry
+ ires->mName = std::move(newName);
+ mObjects.try_emplace(ires->GetName(), ires);
+
+ return true;
+}
+
+IresObject* IresManager::FindIres(std::string_view path) {
+ auto iter = mObjects.find(path);
+ if (iter != mObjects.end()) {
+ return iter->second.Get();
+ } else {
+ return nullptr;
+ }
+}
diff --git a/source/Ires.hpp b/source/Ires.hpp
new file mode 100644
index 0000000..66a931e
--- /dev/null
+++ b/source/Ires.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "EditorAttachment.hpp"
+#include "RcPtr.hpp"
+#include "Utils.hpp"
+
+#include <rapidjson/fwd.h>
+#include <robin_hood.h>
+#include <filesystem>
+#include <memory>
+#include <string_view>
+
+// Forward declarations
+class IresObject;
+class IresManager;
+
+class IresObject : public RefCounted {
+ friend class IresManager;
+
+public:
+ enum Kind {
+ KD_Texture,
+ KD_SpriteFiles,
+ KD_Spritesheet,
+ KD_COUNT,
+ };
+
+private:
+ std::string mName;
+ std::unique_ptr<EditorAttachment> mEditorAttachment;
+ IresManager* mMan = nullptr;
+ Kind mKind;
+
+public:
+ IresObject(Kind kind);
+ virtual ~IresObject() = default;
+
+ static std::string_view ToString(Kind kind);
+ static Kind FromString(std::string_view name);
+ static std::unique_ptr<IresObject> Create(Kind kind);
+ Kind GetKind() const { return mKind; }
+
+ IresManager* GetAssociatedManager() const { return mMan; }
+ bool IsAnnoymous() const;
+ const std::string& GetName() const { return mName; }
+
+ EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); }
+ void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); }
+
+ static rapidjson::Value WriteFull(const IresObject& ires, rapidjson::Document& root);
+ static std::unique_ptr<IresObject> ReadFull(const rapidjson::Value& value);
+ virtual void Write(rapidjson::Value& value, rapidjson::Document& root) const = 0;
+ virtual void Read(const rapidjson::Value& value) = 0;
+};
+
+class IresManager {
+public:
+ static inline IresManager* instance = nullptr;
+
+private:
+ robin_hood::unordered_map<std::string_view, RcPtr<IresObject>, StringHash, StringEqual> mObjects;
+
+public:
+ void DiscoverFilesDesignatedLocation();
+ void DiscoverFiles(const std::filesystem::path& dir);
+
+ std::pair<IresObject*, bool> Add(IresObject* mat);
+ void Delete(IresObject* ires);
+ bool Rename(IresObject* ires, std::string newName);
+
+ const auto& GetObjects() const { return mObjects; }
+ IresObject* FindIres(std::string_view path);
+};
diff --git a/source/Material.cpp b/source/Material.cpp
index db76b21..5e42b96 100644
--- a/source/Material.cpp
+++ b/source/Material.cpp
@@ -16,18 +16,18 @@
namespace fs = std::filesystem;
using namespace std::literals;
-Material::Material(Shader* shader, std::string name)
- : mShader(shader)
- , mName(std::move(name)) {
+Material::Material() {
+}
+
+Material::Material(std::string name)
+ : 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& info = shader->GetInfo();
+ auto iter = info.things.find(name);
+ if (iter == info.things.end()) return false;
auto& id = iter->second;
if (id.kind != ShaderThingId::KD_Uniform) return false;
@@ -111,7 +111,7 @@ Material::MatrixUniform ReadMatrixFromjson(const rapidjson::Value& rv) {
assert(row.Size() == w);
for (int x = 0; x < w; ++x) {
auto& val = row[x];
- assert(val.IsFloat());
+ assert(val.IsNumber());
result.value[x * h + y] = val.GetFloat();
}
}
@@ -224,8 +224,48 @@ Shader* Material::GetShader() const {
}
void Material::SetShader(Shader* shader) {
- // TODO validate uniforms?
mShader.Attach(shader);
+ auto& info = shader->GetInfo();
+
+ mBoundScalars.clear();
+ mBoundVectors.clear();
+ mBoundMatrices.clear();
+ mBoundTextures.clear();
+ for (int i = 0; i < info.uniforms.size(); ++i) {
+ auto& decl = info.uniforms[i];
+ switch (decl->kind) {
+ case ShaderVariable::KD_Math: {
+ auto& mathDecl = static_cast<ShaderMathVariable&>(*decl);
+ if (mathDecl.width == 1) {
+ if (mathDecl.height == 1) {
+ // Scalar
+ auto& scalar = mBoundScalars.emplace_back();
+ scalar.location = decl->location;
+ scalar.infoUniformIndex = i;
+ } else {
+ // Vector
+ auto& vec = mBoundVectors.emplace_back();
+ vec.location = decl->location;
+ vec.infoUniformIndex = i;
+ vec.actualLength = mathDecl.height;
+ }
+ } else {
+ // Matrix
+ auto& mat = mBoundMatrices.emplace_back();
+ mat.location = decl->location;
+ mat.infoUniformIndex = i;
+ mat.actualWidth = mathDecl.width;
+ mat.actualHeight = mathDecl.height;
+ }
+ } break;
+
+ case ShaderVariable::KD_Sampler: {
+ auto& uniform = mBoundTextures.emplace_back();
+ uniform.location = decl->location;
+ uniform.infoUniformIndex = i;
+ } break;
+ }
+ }
}
const std::string& Material::GetName() const {
@@ -306,7 +346,7 @@ bool Material::SaveToFile(const fs::path& filePath) const {
if (IsAnnoymous()) return false;
if (!IsValid()) return false;
- auto info = mShader->GetInfo();
+ auto& info = mShader->GetInfo();
rapidjson::Document root(rapidjson::kObjectType);
@@ -316,7 +356,7 @@ bool Material::SaveToFile(const fs::path& filePath) const {
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("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;
@@ -327,14 +367,14 @@ bool Material::SaveToFile(const fs::path& filePath) const {
}
for (auto& vector : mBoundVectors) {
rapidjson::Value rvField(rapidjson::kObjectType);
- if (info) rvField.AddMember("Name", info->uniforms[vector.infoUniformIndex]->name, root.GetAllocator());
+ 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("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());
@@ -388,6 +428,7 @@ bool Material::LoadFromFile(const fs::path& filePath) {
mShader.Attach(shader);
}
+ auto& shaderInfo = mShader->GetInfo();
auto fields = rapidjson::GetProperty(root, rapidjson::kArrayType, "Fields"sv);
if (!fields) return false;
@@ -407,6 +448,7 @@ bool Material::LoadFromFile(const fs::path& filePath) {
ScalarUniform uniform;
if (rvName) {
TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex);
+ uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location;
}
if (rvValue->IsFloat()) {
uniform.actualType = GL_FLOAT;
@@ -423,12 +465,14 @@ bool Material::LoadFromFile(const fs::path& filePath) {
auto uniform = ReadVectorFromJson(*rvValue);
if (rvName) {
TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex);
+ uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location;
}
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);
+ uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location;
}
mBoundMatrices.push_back(uniform);
} else if (type == "Texture"sv) {
diff --git a/source/Material.hpp b/source/Material.hpp
index 6b431be..bf1c988 100644
--- a/source/Material.hpp
+++ b/source/Material.hpp
@@ -6,6 +6,7 @@
#include "Texture.hpp"
#include <glad/glad.h>
+#include <robin_hood.h>
#include <cstddef>
#include <cstdint>
#include <filesystem>
@@ -15,11 +16,23 @@
#include <string_view>
#include <vector>
+// TODO migrate material editor to Ires
class MaterialManager;
class Material : public RefCounted {
public: // NOTE: public for internal helpers and editor
// NOTE: specialize between scalar vs matrix vs vector to save memory
+ enum UniformType : uint16_t {
+ UT_Scalar,
+ UT_Vector,
+ UT_Matrix,
+ };
+
+ struct UniformIndex {
+ UniformType type;
+ uint16_t index;
+ };
+
struct ScalarUniform {
union {
float floatValue;
@@ -27,14 +40,14 @@ public: // NOTE: public for internal helpers and editor
uint32_t uintValue;
};
GLenum actualType;
- /* Saves 'name' */ int infoUniformIndex;
+ /* Transient */ int infoUniformIndex;
/* Transient */ GLint location;
};
struct VectorUniform {
float value[4];
int actualLength;
- /* Saves 'name' */ int infoUniformIndex;
+ /* Transient */ int infoUniformIndex;
/* Transient */ GLint location;
};
@@ -42,13 +55,13 @@ public: // NOTE: public for internal helpers and editor
float value[16];
int actualWidth;
int actualHeight;
- /* Saves 'name' */ int infoUniformIndex;
+ /* Transient */ int infoUniformIndex;
/* Transient */ GLint location;
};
struct TextureUniform {
RcPtr<Texture> value;
- /* Saves 'name' */ int infoUniformIndex;
+ /* Transient */ int infoUniformIndex;
/* Transient */ GLint location;
};
@@ -61,9 +74,8 @@ public: // NOTE: public for internal helpers and editor
std::vector<TextureUniform> mBoundTextures;
public:
- // NOTE: constructs invalid object
- Material() = default;
- Material(Shader* shader, std::string name = "");
+ Material();
+ Material(std::string name);
void SetFloat(const char* name, float value);
void SetInt(const char* name, int32_t value);
@@ -106,7 +118,7 @@ public:
static inline MaterialManager* instance = nullptr;
private:
- absl::flat_hash_map<std::string_view, RcPtr<Material>> mMaterials;
+ robin_hood::unordered_map<std::string_view, RcPtr<Material>> mMaterials;
public:
void DiscoverMaterials();
diff --git a/source/Mesh.cpp b/source/Mesh.cpp
index 3622d42..5b4f708 100644
--- a/source/Mesh.cpp
+++ b/source/Mesh.cpp
@@ -69,6 +69,15 @@ int VertexElementFormat::GetStride() const {
void VertexFormat::AddElement(VertexElementFormat element) {
vertexSize += element.GetStride();
+
+ int lastIdx = (int)elements.size() - 1;
+ if (lastIdx >= 0) {
+ auto& last = elements[lastIdx];
+ element.offset = last.offset + last.GetStride();
+ } else {
+ element.offset = 0;
+ }
+
elements.push_back(std::move(element));
}
@@ -78,56 +87,12 @@ void VertexFormat::RemoveElement(int index) {
elements.erase(elements.begin() + index);
}
-void VertexFormat::Sort() {
- std::sort(elements.begin(), elements.end());
-}
-
-void VertexFormat::CompactBindingIndex() {
- if (elements.empty()) {
- return;
- }
-
- Sort();
-
- int targetIdx = 0;
- int lastIdx = elements[0].bindingIndex;
- int c = 0;
- for (auto& elm : elements) {
- if (lastIdx != elm.bindingIndex) {
- targetIdx++;
- lastIdx = elm.bindingIndex;
- }
- if (targetIdx != elm.bindingIndex) {
- elements[c] = elm;
- elements[c].bindingIndex = targetIdx;
- }
- ++c;
- }
+GpuMesh::GpuMesh() {
}
-GpuMesh::GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer)
- : vertFormat(vertexFormat)
- , vertBufBindings(bindings)
- , indexBuf(indexBuffer) {
+GpuMesh::~GpuMesh() {
}
bool GpuMesh::IsEmpty() const {
- return vertFormat != nullptr;
-}
-
-void GpuMesh::SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings) {
- vertFormat.Attach(vertexFormat);
- vertBufBindings.Attach(bindings);
-}
-
-VertexFormat* GpuMesh::GetVertexFormat() const {
- return vertFormat.Get();
-}
-
-BufferBindings* GpuMesh::GetVertexBufferBindings() const {
- return vertBufBindings.Get();
-}
-
-GpuIndexBuffer* GpuMesh::GetIndexBuffer() const {
- return indexBuf.Get();
+ return vertFormat == nullptr || indexBuf == nullptr;
}
diff --git a/source/Mesh.hpp b/source/Mesh.hpp
index a1c0984..bc755a3 100644
--- a/source/Mesh.hpp
+++ b/source/Mesh.hpp
@@ -31,7 +31,7 @@ struct GpuIndexBuffer : public RefCounted {
void Upload(const std::byte* data, Tags::IndexType type, size_t count);
};
-struct BufferBindings : public RefCounted {
+struct BufferBindings {
SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings;
int GetMaxBindingIndex() const;
@@ -44,14 +44,15 @@ struct BufferBindings : public RefCounted {
};
struct VertexElementFormat {
- int offset;
- int bindingIndex;
- Tags::VertexElementType type;
- Tags::VertexElementSemantic semantic;
+ /// NOTE:
+ /// "Automatic" means it will be set inside VertexFormat::AddElement()
+ /// "Parameter" means it must be set by the user
+ /* Automatic */ int offset;
+ /* Parameter */ int bindingIndex;
+ /* Parameter */ Tags::VertexElementType type;
+ /* Parameter */ Tags::VertexElementSemantic semantic;
int GetStride() const;
-
- auto operator<=>(const VertexElementFormat&) const = default;
};
struct VertexFormat : public RefCounted {
@@ -61,24 +62,22 @@ struct VertexFormat : public RefCounted {
const std::vector<VertexElementFormat>& GetElements() { return elements; }
void AddElement(VertexElementFormat element);
void RemoveElement(int index);
-
- void Sort();
- void CompactBindingIndex();
};
+// Initialized in main()
+inline RcPtr<VertexFormat> gVformatStandard{};
+inline RcPtr<VertexFormat> gVformatStandardPacked{};
+
+// TODO handle immutability
class GpuMesh : public RefCounted {
public:
RcPtr<VertexFormat> vertFormat;
- RcPtr<BufferBindings> vertBufBindings;
+ BufferBindings vertBufBindings;
RcPtr<GpuIndexBuffer> indexBuf;
public:
- GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer);
+ GpuMesh();
+ ~GpuMesh();
bool IsEmpty() const;
- void SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings);
- VertexFormat* GetVertexFormat() const;
- BufferBindings* GetVertexBufferBindings() const;
- void SetIndex(GpuIndexBuffer* buffer);
- GpuIndexBuffer* GetIndexBuffer() const;
};
diff --git a/source/PodVector.hpp b/source/PodVector.hpp
index 8f0281b..74e99d6 100644
--- a/source/PodVector.hpp
+++ b/source/PodVector.hpp
@@ -95,6 +95,8 @@ public:
T* end() { return mData + mSize; }
const T* end() const { return mData + mSize; }
+ T* data() { return mData; }
+
T& front() {
assert(mSize > 0);
return mData[0];
diff --git a/source/RapidJsonHelper.hpp b/source/RapidJsonHelper.hpp
index 9dc0701..75cd93a 100644
--- a/source/RapidJsonHelper.hpp
+++ b/source/RapidJsonHelper.hpp
@@ -75,4 +75,36 @@ inline GenericStringRef<char> StringRef(std::string_view str) {
str.size());
}
+template <class TIter, class TSentienl>
+rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) {
+ using TElement = typename TIter::value_type;
+
+ rapidjson::Value list;
+ while (begin != end) {
+ if constexpr (std::is_same_v<TElement, std::string>) {
+ auto& elm = *begin;
+ list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator());
+ } else {
+ list.PushBack(*begin, root.GetAllocator());
+ }
+ ++begin;
+ }
+ return list;
+}
+
+template <class TContainer>
+bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) {
+ using TElement = typename TContainer::value_type;
+
+ if (!value.IsArray()) return false;
+
+ list.reserve(value.Size());
+ for (auto& elm : value.GetArray()) {
+ if (!elm.Is<TElement>()) return {};
+ list.push_back(elm.Get<TElement>());
+ }
+
+ return true;
+}
+
} // namespace rapidjson
diff --git a/source/RcPtr.hpp b/source/RcPtr.hpp
index 5958db4..130b2b2 100644
--- a/source/RcPtr.hpp
+++ b/source/RcPtr.hpp
@@ -4,13 +4,15 @@
#include "TypeTraits.hpp"
#include <cstddef>
+#include <cstdint>
#include <optional>
#include <type_traits>
class RefCounted {
public:
// DO NOT MODIFY this field, unless explicitly documented the use
- size_t refCount = 0;
+ uint32_t refCount = 0;
+ uint32_t weakCount = 0; // TODO implement
};
template <class T, class TDeleter = DefaultDeleter<T>>
@@ -27,7 +29,7 @@ public:
explicit RcPtr(T* ptr)
: mPtr{ ptr } {
if (ptr) {
- ++ptr->refCount;
+ ++ptr->RefCounted::refCount;
}
}
@@ -39,7 +41,7 @@ public:
CleanUp();
mPtr = ptr;
if (ptr) {
- ++ptr->refCount;
+ ++ptr->RefCounted::refCount;
}
}
@@ -51,7 +53,7 @@ public:
RcPtr(const RcPtr& that)
: mPtr{ that.mPtr } {
if (mPtr) {
- ++mPtr->refCount;
+ ++mPtr->RefCounted::refCount;
}
}
@@ -59,7 +61,7 @@ public:
CleanUp();
mPtr = that.mPtr;
if (mPtr) {
- ++mPtr->refCount;
+ ++mPtr->RefCounted::refCount;
}
return *this;
}
@@ -109,8 +111,8 @@ public:
private:
void CleanUp() {
if (mPtr) {
- --mPtr->refCount;
- if (mPtr->refCount == 0) {
+ --mPtr->RefCounted::refCount;
+ if (mPtr->RefCounted::refCount == 0) {
TDeleter::operator()(mPtr);
}
}
diff --git a/source/Rect.hpp b/source/Rect.hpp
new file mode 100644
index 0000000..89d9b01
--- /dev/null
+++ b/source/Rect.hpp
@@ -0,0 +1,164 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+/// Rect is a rectangle representation based on a point and a dimensions, in television coordinate space
+/// (x increases from left to right, y increases from top to bottom).
+template <class T>
+class Rect {
+public:
+ using ScalarType = T;
+ using VectorType = glm::vec<2, T>;
+
+public:
+ T x;
+ T y;
+ T width;
+ T height;
+
+public:
+ Rect()
+ : x{ 0 }, y{ 0 }, width{ 0 }, height{ 0 } {
+ }
+
+ Rect(T x, T y, T width, T height)
+ : x{ x }, y{ y }, width{ width }, height{ height } {
+ }
+
+ Rect(VectorType pos, VectorType size)
+ : x{ pos.x }
+ , y{ pos.y }
+ , width{ size.x }
+ , height{ size.y } {
+ }
+
+ T x0() const { return x; }
+ T y0() const { return y; }
+ T x1() const { return x + width; }
+ T y1() const { return y + height; }
+
+ VectorType TopLeft() const {
+ return VectorType{ x, y };
+ }
+
+ VectorType TopRight() const {
+ return VectorType{ x + width, y };
+ }
+
+ VectorType BottomLeft() const {
+ return VectorType{ x, y + height };
+ }
+
+ VectorType BottomRight() const {
+ return VectorType{ x + width, y + height };
+ }
+
+ VectorType Center() const {
+ return TopLeft() + VectorType{ width / 2, height / 2 };
+ }
+
+ VectorType Dimensions() const {
+ return VectorType{ width, height };
+ }
+
+ VectorType Extents() const {
+ return VectorType{ width / 2, height / 2 };
+ }
+
+ /// Assumes `bySize * 2` is smaller than both `width` and `height` (does not produce a negative-dimension rectangle).
+ Rect Shrink(T bySize) const {
+ T two = bySize * 2;
+ return Rect{ x + bySize, y + bySize, width - two, height - two };
+ }
+
+ Rect Shrink(T left, T top, T right, T bottom) const {
+ return Rect{
+ x + left,
+ y + top,
+ width - left - right,
+ height - top - bottom,
+ };
+ }
+
+ Rect Expand(T bySize) const {
+ T two = bySize * 2;
+ return Rect{ x - bySize, y - bySize, width + two, height + two };
+ }
+
+ Rect Expand(T left, T top, T right, T bottom) const {
+ return Rect{
+ x - left,
+ y - top,
+ width + left + right,
+ height + top + bottom,
+ };
+ }
+
+ bool Contains(VectorType point) const {
+ return point.x >= x &&
+ point.y >= y &&
+ point.x < x + width &&
+ point.y < y + height;
+ }
+
+ bool Intersects(const Rect& that) const {
+ bool xBetween = x > that.x0() && x < that.x1();
+ bool yBetween = y > that.y0() && y < that.y1();
+ return xBetween && yBetween;
+ }
+
+ // Write min()/max() tenary by hand so that we don't have to include <algorithm>
+ // This file is practically going to be included in every file in this project
+
+ static Rect Intersection(const Rect& a, const Rect& b) {
+ auto x0 = a.x0() > b.x0() ? a.x0() : b.x0(); // Max
+ auto y0 = a.y0() > b.y0() ? a.y0() : b.y0(); // Max
+ auto x1 = a.x1() < b.x1() ? a.x1() : b.x1(); // Min
+ auto y1 = a.y1() < b.y1() ? a.y1() : b.y1(); // Min
+ auto width = x1 - x0;
+ auto height = y1 - x0;
+ return Rect{ x0, y0, width, height };
+ }
+
+ static Rect Union(const Rect& a, const Rect& b) {
+ auto x0 = a.x0() < b.x0() ? a.x0() : b.x0(); // Min
+ auto y0 = a.y0() < b.y0() ? a.y0() : b.y0(); // Min
+ auto x1 = a.x1() > b.x1() ? a.x1() : b.x1(); // Max
+ auto y1 = a.y1() > b.y1() ? a.y1() : b.y1(); // Max
+ auto width = x1 - x0;
+ auto height = y1 - x0;
+ return Rect{ x0, y0, width, height };
+ }
+
+ friend bool operator==(const Rect<T>&, const Rect<T>&) = default;
+
+ Rect operator+(glm::vec<2, T> offset) const {
+ return { x + offset.x, y + offset.y, width, height };
+ }
+
+ Rect operator-(glm::vec<2, T> offset) const {
+ return { x - offset.x, y - offset.y, width, height };
+ }
+
+ Rect& operator+=(glm::vec<2, T> offset) {
+ x += offset.x;
+ y += offset.y;
+ return *this;
+ }
+
+ Rect& operator-=(glm::vec<2, T> offset) {
+ x -= offset.x;
+ y -= offset.y;
+ return *this;
+ }
+
+ template <class TTarget>
+ Rect<TTarget> Cast() const {
+ return {
+ static_cast<TTarget>(x),
+ static_cast<TTarget>(y),
+ static_cast<TTarget>(width),
+ static_cast<TTarget>(height),
+ };
+ }
+};
diff --git a/source/Renderer.cpp b/source/Renderer.cpp
new file mode 100644
index 0000000..dec24ea
--- /dev/null
+++ b/source/Renderer.cpp
@@ -0,0 +1,43 @@
+#include "Renderer.hpp"
+
+RenderObject::RenderObject(GpuMesh* mesh, Material* material) {
+ glGenVertexArrays(1, &mVao);
+ glBindVertexArray(mVao);
+
+ auto& vf = mesh->vertFormat;
+ auto& vBindings = mesh->vertBufBindings.bindings;
+ auto& shaderInfo = material->GetShader()->GetInfo();
+
+ // Setup vertex buffers
+ for (auto& elm : vf->elements) {
+ assert(elm.bindingIndex < vBindings.size());
+ auto& buffer = vBindings[elm.bindingIndex];
+
+ int index = material->GetShader()->GetInfo().FindInputLocation(elm.semantic);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer->handle);
+ glEnableVertexAttribArray(index);
+ glVertexAttribPointer(
+ index,
+ Tags::VectorLenOf(elm.type),
+ Tags::FindGLType(elm.type),
+ Tags::IsNormalized(elm.type),
+ vf->vertexSize,
+ (void*)(uintptr_t)elm.offset);
+ }
+
+ // Setup index buffer
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->indexBuf->handle);
+
+ glBindVertexArray(GL_NONE);
+}
+
+RenderObject::~RenderObject() {
+ glDeleteVertexArrays(1, &mVao);
+}
+
+void Renderer::Draw(RenderObject& object) {
+ auto indexType = object.GetMesh()->indexBuf->indexType;
+
+ glBindVertexArray(object.GetGLVao());
+ glDrawElements(GL_TRIANGLES, 0, Tags::FindGLType(indexType), 0);
+}
diff --git a/source/Renderer.hpp b/source/Renderer.hpp
new file mode 100644
index 0000000..18a1e6e
--- /dev/null
+++ b/source/Renderer.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "Material.hpp"
+#include "Mesh.hpp"
+#include "RcPtr.hpp"
+
+#include <glad/glad.h>
+#include <glm/glm.hpp>
+
+class RenderObject {
+public:
+ glm::mat4 worldMatrix;
+
+private:
+ RcPtr<Material> mMaterial;
+ RcPtr<GpuMesh> mMesh;
+ GLuint mVao;
+
+public:
+ RenderObject(GpuMesh* mesh, Material* material);
+ ~RenderObject();
+
+ GLuint GetGLVao() const { return mVao; }
+ Material* GetMaterial() const { return mMaterial.Get(); }
+ GpuMesh* GetMesh() const { return mMesh.Get(); }
+};
+
+class Camera {
+public:
+ glm::mat4 viewMatrix;
+ glm::mat4 projectionMatrix;
+
+public:
+ void Move(glm::vec3 pos);
+ void LookAt(glm::vec3 pos);
+};
+
+class Renderer {
+private:
+ Camera* mCam;
+
+public:
+ void BeginFrame();
+ void EndFrame();
+ void Draw(RenderObject& object);
+};
diff --git a/source/Shader.cpp b/source/Shader.cpp
index 4a576d0..61e80a1 100644
--- a/source/Shader.cpp
+++ b/source/Shader.cpp
@@ -18,114 +18,56 @@
namespace fs = std::filesystem;
using namespace std::literals;
-bool ShaderThingId::IsValid() const {
- return kind == KD_Invalid;
+void ShaderMathVariable::ShowInfo() const {
+ ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: %s %dx%d",
+ location,
+ name.c_str(),
+ Tags::NameOf(semantic).data(),
+ Tags::NameOfGLType(scalarType).data(),
+ width,
+ height);
}
-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;
+void ShaderSamplerVariable::ShowInfo() const {
+ ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: Sampler",
+ location,
+ name.c_str(),
+ Tags::NameOf(semantic).data());
}
-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 ShaderThingId::IsValid() const {
+ return kind == KD_Invalid;
}
-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));
+namespace ProjectBrussel_UNITY_ID {
+GLuint FindLocation(const std::vector<ShaderMathVariable>& vars, Tags::VertexElementSemantic semantic) {
+ for (auto& var : vars) {
+ if (var.semantic == semantic) {
+ return var.location;
}
- };
- LoadInputOutputThings("Inputs"sv, inputs);
- LoadInputOutputThings("Outputs"sv, outputs);
-
- // TODO uniforms
+ }
+ return Tags::kInvalidLocation;
+}
+} // namespace ProjectBrussel_UNITY_ID
- return true;
+GLuint ShaderInfo::FindInputLocation(Tags::VertexElementSemantic semantic) {
+ using namespace ProjectBrussel_UNITY_ID;
+ return FindLocation(inputs, semantic);
}
-void ShaderInfo::LoadLocations(const Shader& ownerShader) {
- GLuint program = ownerShader.GetProgram();
+GLuint ShaderInfo::FindOutputLocation(Tags::VertexElementSemantic semantic) {
+ using namespace ProjectBrussel_UNITY_ID;
+ return FindLocation(outputs, semantic);
+}
- // TODO inputs
- // TODO outputs
- for (auto& uniform : uniforms) {
- uniform->location = glGetUniformLocation(ownerShader.GetProgram(), uniform->name.c_str());
+ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) {
+ switch (thing.kind) {
+ case ShaderThingId::KD_Input: return &inputs[thing.index];
+ case ShaderThingId::KD_Output: return &outputs[thing.index];
+ case ShaderThingId::KD_Uniform: return uniforms[thing.index].get();
+ case ShaderThingId::KD_Invalid: break;
}
+ return nullptr;
}
Shader::Shader(std::string name)
@@ -133,7 +75,7 @@ Shader::Shader(std::string name)
}
Shader::~Shader() {
- glDeleteProgram(mHandle);
+ glDeleteProgram(mProgram);
}
namespace ProjectBrussel_UNITY_ID {
@@ -155,10 +97,10 @@ Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int e
std::string log(len, '\0');
glGetShaderInfoLog(out, len, nullptr, log.data());
- return Shader ::CompilationFailed;
+ return Shader ::EC_CompilationFailed;
}
- return Shader::Success;
+ return Shader::EC_Success;
}
Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) {
@@ -176,17 +118,17 @@ Shader::ErrorCode LinkShaderProgram(GLuint program) {
std::string log(len, '\0');
glGetProgramInfoLog(program, len, nullptr, log.data());
- return Shader::LinkingFailed;
+ return Shader::EC_LinkingFailed;
}
- return Shader::Success;
+ return Shader::EC_Success;
}
} // namespace ProjectBrussel_UNITY_ID
-#define CATCH_ERROR_IMPL(x, name) \
- auto name = x; \
- if (name != Shader::Success) { \
- return name; \
+#define CATCH_ERROR_IMPL(x, name) \
+ auto name = x; \
+ if (name != Shader::EC_Success) { \
+ return name; \
}
#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result))
@@ -194,7 +136,7 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
using namespace ProjectBrussel_UNITY_ID;
if (IsValid()) {
- return ShaderAlreadyCreated;
+ return EC_AlreadyInitialized;
}
GLuint program = glCreateProgram();
@@ -238,9 +180,9 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
CATCH_ERROR(LinkShaderProgram(program));
sg.Dismiss();
- mHandle = program;
+ mProgram = program;
- return Success;
+ return EC_Success;
}
Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
@@ -268,7 +210,7 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
auto CommitSection = [&]() -> ErrorCode {
if (prevBegin == -1 || prevEnd == -1) {
// Not actually "succeeding" here, but we just want to skip this call and continue
- return Success;
+ return EC_Success;
}
if (prevShaderVariant == "vertex" && !vertex) {
@@ -282,14 +224,14 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
} else if (prevShaderVariant == "fragment" && !fragment) {
CATCH_ERROR(CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER));
} else {
- return InvalidShaderVariant;
+ return EC_InvalidShaderVariant;
}
prevBegin = -1;
prevEnd = -1;
prevShaderVariant.clear();
- return Success;
+ return EC_Success;
};
constexpr const char* kMarker = "#type ";
@@ -355,9 +297,9 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
CATCH_ERROR(LinkShaderProgram(program));
sg.Dismiss();
- mHandle = program;
+ mProgram = program;
- return Success;
+ return EC_Success;
}
#undef CATCH_ERROR
@@ -498,23 +440,10 @@ std::unique_ptr<ShaderVariable> CreateVariable(GLenum type, GLuint loc) {
}
} // namespace ProjectBrussel_UNITY_ID
-bool Shader::CreateEmptyInfoIfAbsent() {
- if (mInfo || !IsValid()) {
- return false;
- }
-
- mInfo = std::make_unique<ShaderInfo>();
- return true;
-}
-
-bool Shader::GatherInfoIfAbsent() {
+bool Shader::GatherInfoShaderIntrospection() {
using namespace ProjectBrussel_UNITY_ID;
- if (mInfo || !IsValid()) {
- return false;
- }
-
- mInfo = std::make_unique<ShaderInfo>();
+ mInfo = {};
// TODO handle differnt types of variables with the same name
@@ -522,45 +451,44 @@ bool Shader::GatherInfoIfAbsent() {
return true;
int inputCount;
- glGetProgramInterfaceiv(mHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount);
+ glGetProgramInterfaceiv(mProgram, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount);
int outputCount;
- glGetProgramInterfaceiv(mHandle, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount);
+ glGetProgramInterfaceiv(mProgram, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount);
int uniformBlockCount;
- glGetProgramInterfaceiv(mHandle, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount);
+ glGetProgramInterfaceiv(mProgram, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount);
int uniformCount;
- glGetProgramInterfaceiv(mHandle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount);
+ glGetProgramInterfaceiv(mProgram, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount);
// Gather inputs
- auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderInfo::InputOutputThing>& list) {
+ auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderMathVariable>& 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);
+ glGetProgramResourceiv(mProgram, 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());
+ glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
- mInfo->things.try_emplace(fieldName, ShaderThingId{ resourceKind, i });
+ mInfo.things.try_emplace(fieldName, ShaderThingId{ 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);
+ auto& thing = list.emplace_back();
+ thing.name = std::move(fieldName);
+ thing.arrayLength = arrayLength;
+ QueryMathInfo(type, thing.scalarType, thing.width, thing.height);
}
};
- GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo->inputs);
- GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_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) {
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);
+ glGetProgramResourceiv(mProgram, 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;
@@ -571,29 +499,182 @@ bool Shader::GatherInfoIfAbsent() {
auto& arrayLength = props[4];
std::string fieldName(nameLength - 1, '\0');
- glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
+ glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data());
- mInfo->things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i });
- mInfo->uniforms.push_back(CreateVariable(type, loc));
+ mInfo.things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i });
+ mInfo.uniforms.push_back(CreateVariable(type, loc));
}
return true;
}
-ShaderInfo* Shader::GetInfo() const {
- return mInfo.get();
+bool Shader::IsAnnoymous() const {
+ return mName.empty();
}
-const std::string& Shader::GetName() const {
- return mName;
+bool Shader::IsValid() const {
+ return mProgram != 0;
}
-GLuint Shader::GetProgram() const {
- return mHandle;
+namespace ProjectBrussel_UNITY_ID {
+void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) {
+ value.AddMember("Name", var.name, root.GetAllocator());
+ value.AddMember("Semantic", rapidjson::StringRef(Tags::NameOf(var.semantic)), root.GetAllocator());
+ value.AddMember("OpenGLLocation", var.location, root.GetAllocator());
}
-bool Shader::IsValid() const {
- return mHandle != 0;
+bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) {
+ BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false);
+ { // Semantic
+ auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv);
+ if (!rvSemantic) {
+ var.semantic = Tags::VES_Generic;
+ } else {
+ var.semantic = Tags::FindVertexElementSemantic(rapidjson::AsStringView(*rvSemantic));
+ }
+ }
+ BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0);
+ return true;
+}
+
+void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) {
+ WriteShaderVariable(value, root, var);
+ value.AddMember("ScalarType", rapidjson::StringRef(Tags::NameOfGLType(var.scalarType)), root.GetAllocator());
+ value.AddMember("Width", var.width, root.GetAllocator());
+ value.AddMember("Height", var.height, root.GetAllocator());
+ value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator());
+}
+
+bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& var) {
+ if (!ReadShaderVariable(value, var)) return false;
+ {
+ auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv);
+ if (!rvScalar) return false;
+ var.scalarType = Tags::FindGLType(rapidjson::AsStringView(*rvScalar));
+ }
+ BRUSSEL_JSON_GET(value, "Width", int, var.width, return false);
+ BRUSSEL_JSON_GET(value, "Height", int, var.height, return false);
+ BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1);
+ return true;
+}
+
+void WriteShaderSamplerVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderSamplerVariable& var) {
+ WriteShaderVariable(value, root, var);
+ // TODO
+}
+
+bool ReadShaderSamplerVariable(const rapidjson::Value& value, ShaderSamplerVariable& var) {
+ if (!ReadShaderVariable(value, var)) return false;
+ BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1);
+ return true;
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+bool Shader::SaveMetadataToFile(const fs::path& filePath) const {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ rapidjson::Document root(rapidjson::kObjectType);
+
+ auto SaveMathVars = [&](const char* name, const std::vector<ShaderMathVariable>& vars) {
+ rapidjson::Value rvThings(rapidjson::kArrayType);
+ for (auto& thing : vars) {
+ rapidjson::Value rvThing(rapidjson::kObjectType);
+ WriteShaderMathVariable(rvThing, root, thing);
+
+ rvThings.PushBack(rvThing, root.GetAllocator());
+ }
+ root.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator());
+ };
+ SaveMathVars("Inputs", mInfo.inputs);
+ SaveMathVars("Outputs", mInfo.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 Shader::LoadMetadataFromFile(const fs::path& filePath) {
+ using namespace ProjectBrussel_UNITY_ID;
+
+ mInfo = {};
+
+ 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 LoadMathVars = [&](std::string_view name, ShaderThingId::Kind kind, std::vector<ShaderMathVariable>& vars) {
+ auto rvThings = rapidjson::GetProperty(root, rapidjson::kArrayType, name);
+ if (!rvThings) return;
+
+ for (auto& rv : rvThings->GetArray()) {
+ if (!rv.IsObject()) continue;
+ ShaderMathVariable thing;
+ ReadShaderMathVariable(rv, thing);
+
+ mInfo.things.try_emplace(thing.name, ShaderThingId{ kind, (int)vars.size() });
+ vars.push_back(std::move(thing));
+ }
+ };
+ LoadMathVars("Inputs"sv, ShaderThingId::KD_Input, mInfo.inputs);
+ LoadMathVars("Outputs"sv, ShaderThingId::KD_Output, mInfo.outputs);
+
+ auto rvUniforms = rapidjson::GetProperty(root, rapidjson::kArrayType, "Uniforms"sv);
+ if (!rvUniforms) return false;
+ for (auto& rvUniform : rvUniforms->GetArray()) {
+ if (!rvUniform.IsObject()) continue;
+
+ auto rvType = rapidjson::GetProperty(rvUniform, rapidjson::kStringType, "Type"sv);
+ if (!rvType) continue;
+ auto type = rapidjson::AsStringView(*rvType);
+
+ auto rvValue = rapidjson::GetProperty(rvUniform, rapidjson::kObjectType, "Value"sv);
+ if (!rvValue) continue;
+
+ bool autoFill; // TODO store autofill uniforms somewhere else
+ BRUSSEL_JSON_GET_DEFAULT(rvUniform, "AutoFill", bool, autoFill, false);
+ if (autoFill) continue;
+
+ auto uniform = [&]() -> std::unique_ptr<ShaderVariable> {
+ if (type == "Math"sv) {
+ auto uniform = std::make_unique<ShaderMathVariable>();
+ ReadShaderMathVariable(*rvValue, *uniform);
+
+ return uniform;
+ } else if (type == "Sampler"sv) {
+ auto uniform = std::make_unique<ShaderSamplerVariable>();
+ ReadShaderSamplerVariable(*rvValue, *uniform);
+
+ return uniform;
+ }
+
+ return nullptr;
+ }();
+ if (uniform) {
+ mInfo.things.try_emplace(uniform->name, ShaderThingId{ ShaderThingId::KD_Uniform, (int)mInfo.uniforms.size() });
+ mInfo.uniforms.emplace_back(std::move(uniform));
+ }
+ }
+
+ for (auto& uniform : mInfo.uniforms) {
+ uniform->location = glGetUniformLocation(mProgram, uniform->name.c_str());
+ }
+
+ return true;
}
void ShaderManager::DiscoverShaders() {
@@ -625,7 +706,7 @@ void ShaderManager::DiscoverShaders() {
// Load shader
auto err = shader->InitFromSource(source);
- if (err != Shader::Success) {
+ if (err != Shader::EC_Success) {
continue;
}
@@ -633,8 +714,7 @@ void ShaderManager::DiscoverShaders() {
fs::path infoPath(item.path());
infoPath.replace_extension(".json");
if (fs::exists(infoPath)) {
- shader->CreateEmptyInfoIfAbsent();
- shader->GetInfo()->LoadFromFile(infoPath);
+ shader->LoadMetadataFromFile(infoPath);
}
mShaders.try_emplace(shaderName, std::move(shader));
diff --git a/source/Shader.hpp b/source/Shader.hpp
index 79262a6..7ad2bbb 100644
--- a/source/Shader.hpp
+++ b/source/Shader.hpp
@@ -3,15 +3,17 @@
#include "EditorAttachment.hpp"
#include "GraphicsTags.hpp"
#include "RcPtr.hpp"
+#include "Utils.hpp"
-#include <absl/container/flat_hash_map.h>
#include <glad/glad.h>
+#include <robin_hood.h>
#include <filesystem>
#include <memory>
#include <string_view>
#include <vector>
// TODO move to variable after pattern matching is in the language
+// TODO migrate shader editor to Ires
// Forward declarations
class Shader;
@@ -25,6 +27,9 @@ struct ShaderVariable {
std::string name;
Kind kind;
GLuint location;
+ Tags::VertexElementSemantic semantic = Tags::VES_Generic;
+
+ virtual void ShowInfo() const = 0;
protected:
ShaderVariable(Kind kind)
@@ -39,6 +44,8 @@ struct ShaderMathVariable : public ShaderVariable {
ShaderMathVariable()
: ShaderVariable(KD_Math) {}
+
+ virtual void ShowInfo() const override;
};
struct ShaderSamplerVariable : public ShaderVariable {
@@ -47,6 +54,8 @@ struct ShaderSamplerVariable : public ShaderVariable {
ShaderSamplerVariable()
: ShaderVariable(KD_Sampler) {}
+
+ virtual void ShowInfo() const override;
};
struct ShaderThingId {
@@ -64,29 +73,27 @@ struct ShaderThingId {
};
struct ShaderInfo {
- struct InputOutputThing {
- ShaderMathVariable variable;
- Tags::VertexElementSemantic semantic = Tags::VES_Generic;
- };
-
- absl::flat_hash_map<std::string, ShaderThingId> things;
- std::vector<InputOutputThing> inputs;
- std::vector<InputOutputThing> outputs;
+ robin_hood::unordered_map<std::string, ShaderThingId, StringHash, StringEqual> things;
+ std::vector<ShaderMathVariable> inputs;
+ std::vector<ShaderMathVariable> outputs;
std::vector<std::unique_ptr<ShaderVariable>> uniforms;
+ GLuint FindInputLocation(Tags::VertexElementSemantic semantic);
+ GLuint FindOutputLocation(Tags::VertexElementSemantic semantic);
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 {
private:
std::string mName;
- std::unique_ptr<ShaderInfo> mInfo;
+ ShaderInfo mInfo;
std::unique_ptr<EditorAttachment> mEditorAttachment;
- GLuint mHandle = 0;
+ GLuint mProgram = 0;
+
+public:
+ GLuint autofillLoc_Transform = Tags::kInvalidLocation;
+ GLuint autofillLoc_Time = Tags::kInvalidLocation;
+ GLuint autofillLoc_DeltaTime = Tags::kInvalidLocation;
public:
Shader(std::string name = "");
@@ -97,14 +104,14 @@ public:
Shader& operator=(Shader&&) = default;
enum ErrorCode {
- Success,
+ EC_Success,
/// Generated when Init*() functions are called on an already initialized Shader object.
- ShaderAlreadyCreated,
+ EC_AlreadyInitialized,
/// Generated when the one-source-file text contains invalid or duplicate shader variants.
- InvalidShaderVariant,
- FileIOFailed,
- CompilationFailed,
- LinkingFailed,
+ EC_InvalidShaderVariant,
+ EC_FileIoFailed,
+ EC_CompilationFailed,
+ EC_LinkingFailed,
};
struct ShaderSources {
@@ -138,14 +145,20 @@ public:
void GetDesignatedMetadataPath(char* buffer, int bufferSize);
std::filesystem::path GetDesignatedMetadataPath();
- bool CreateEmptyInfoIfAbsent();
- bool GatherInfoIfAbsent();
- ShaderInfo* GetInfo() const;
+ /// Rebuild info object using OpenGL shader introspection API. Requires OpenGL 4.3 or above. Overrides existing info object.
+ bool GatherInfoShaderIntrospection();
+ const ShaderInfo& GetInfo() const { return mInfo; }
+ ShaderInfo& GetInfo() { return mInfo; }
/// If not empty, this name must not duplicate with any other shader object in the process.
- const std::string& GetName() const;
- GLuint GetProgram() const;
+ const std::string& GetName() const { return mName; }
+ GLuint GetProgram() const { return mProgram; }
+ bool IsAnnoymous() const;
bool IsValid() const;
+
+ bool SaveMetadataToFile(const std::filesystem::path& filePath) const;
+ /// Overrides existing info object.
+ bool LoadMetadataFromFile(const std::filesystem::path& filePath);
};
class ShaderManager {
@@ -153,7 +166,7 @@ public:
static inline ShaderManager* instance = nullptr;
private:
- absl::flat_hash_map<std::string_view, RcPtr<Shader>> mShaders;
+ robin_hood::unordered_map<std::string_view, RcPtr<Shader>> mShaders;
public:
void DiscoverShaders();
diff --git a/source/Sprite.cpp b/source/Sprite.cpp
new file mode 100644
index 0000000..cb8d327
--- /dev/null
+++ b/source/Sprite.cpp
@@ -0,0 +1,163 @@
+#include "Sprite.hpp"
+
+#include "Image.hpp"
+#include "RapidJsonHelper.hpp"
+
+#include <rapidjson/document.h>
+#include <memory>
+
+using namespace std::literals;
+
+bool Sprite::IsValid() const {
+ return mAtlas != nullptr;
+}
+
+bool IresSpriteFiles::IsValid() const {
+ return !spriteFiles.empty();
+}
+
+Sprite* IresSpriteFiles::CreateInstance() const {
+ if (IsValid()) {
+ return nullptr;
+ }
+
+ std::vector<Texture::AtlasSource> sources;
+ sources.resize(spriteFiles.size());
+ for (auto& file : spriteFiles) {
+ }
+
+ Texture::AtlasOutput atlasOut;
+ Texture::AtlasInput atlasIn{
+ .sources = sources,
+ .packingMode = Texture::PM_KeepSquare,
+ };
+ atlasIn.sources = sources;
+ auto atlas = std::make_unique<Texture>();
+ if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) {
+ return nullptr;
+ }
+
+ auto sprite = std::make_unique<Sprite>();
+ sprite->mAtlas.Attach(atlas.release());
+ sprite->mBoundingBox = atlasOut.elements[0].subregionSize;
+ sprite->mFrames.reserve(atlasOut.elements.size());
+ for (auto& elm : atlasOut.elements) {
+ // Validate bounding box
+ if (sprite->mBoundingBox != elm.subregionSize) {
+ return nullptr;
+ }
+
+ // Copy frame subregion
+ sprite->mFrames.push_back(elm.subregion);
+ }
+ return sprite.release();
+}
+
+Sprite* IresSpriteFiles::GetInstance() {
+ if (mInstance == nullptr) {
+ mInstance.Attach(CreateInstance());
+ }
+ return mInstance.Get();
+}
+
+void IresSpriteFiles::InvalidateInstance() {
+ mInstance.Attach(nullptr);
+}
+
+void IresSpriteFiles::Write(rapidjson::Value& value, rapidjson::Document& root) const {
+ value.AddMember("Sprites", rapidjson::WriteVectorPrimitives(root, spriteFiles.begin(), spriteFiles.end()), root.GetAllocator());
+}
+
+void IresSpriteFiles::Read(const rapidjson::Value& value) {
+ auto rvFileList = rapidjson::GetProperty(value, rapidjson::kArrayType, "Sprites"sv);
+ if (!rvFileList) return;
+ spriteFiles.clear();
+ rapidjson::ReadVectorPrimitives<decltype(spriteFiles)>(*rvFileList, spriteFiles);
+}
+
+bool IresSpritesheet::IsValid() const {
+ return !spritesheetFile.empty() &&
+ sheetWSplit != 0 &&
+ sheetHSplit != 0;
+}
+
+void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit) {
+ auto atlas = sprite->GetAtlas();
+ auto size = atlas->GetInfo().size;
+ int frameWidth = size.x / wSplit;
+ int frameHeight = size.y / hSplit;
+
+ sprite->mBoundingBox = { frameWidth, frameHeight };
+ sprite->mFrames.clear();
+ sprite->mFrames.reserve(wSplit * hSplit);
+
+ // Width and height in UV coordinates for each frame
+ float deltaU = 1.0f / wSplit;
+ float deltaV = 1.0f / hSplit;
+ for (int y = 0; y < hSplit; ++y) {
+ for (int x = 0; x < wSplit; ++x) {
+ auto& subregion = sprite->mFrames.emplace_back();
+ // Top left
+ subregion.u0 = deltaU * x;
+ subregion.v0 = deltaV * y;
+ // Bottom right
+ subregion.u1 = subregion.u0 + deltaU;
+ subregion.u1 = subregion.v0 + deltaV;
+ }
+ }
+}
+
+Sprite* IresSpritesheet::CreateInstance() const {
+ if (!IsValid()) {
+ return nullptr;
+ }
+
+ auto atlas = std::make_unique<Texture>();
+ if (!atlas->InitFromFile(spritesheetFile.c_str())) {
+ return nullptr;
+ }
+
+ auto sprite = std::make_unique<Sprite>();
+ sprite->mAtlas.Attach(atlas.release());
+ ResplitSpritesheet(sprite.get(), sheetWSplit, sheetHSplit);
+ return sprite.release();
+}
+
+Sprite* IresSpritesheet::GetInstance() {
+ if (mInstance == nullptr) {
+ mInstance.Attach(CreateInstance());
+ }
+ return mInstance.Get();
+}
+
+void IresSpritesheet::InvalidateInstance() {
+ mInstance.Attach(nullptr);
+}
+
+void IresSpritesheet::Write(rapidjson::Value& value, rapidjson::Document& root) const {
+ value.AddMember("SpriteSheet", spritesheetFile, root.GetAllocator());
+ value.AddMember("WSplit", sheetWSplit, root.GetAllocator());
+ value.AddMember("HSplit", sheetHSplit, root.GetAllocator());
+}
+
+void IresSpritesheet::Read(const rapidjson::Value& value) {
+ BRUSSEL_JSON_GET(value, "SpriteSheet", std::string, spritesheetFile, return );
+ BRUSSEL_JSON_GET(value, "WSplit", int, sheetWSplit, return );
+ BRUSSEL_JSON_GET(value, "HSplit", int, sheetHSplit, return );
+}
+
+SpriteMesh::SpriteMesh(Sprite* sprite)
+ : mSprite(sprite) {
+}
+
+void SpriteMesh::SetFrame(int frame) {
+ // TODO
+}
+
+void SpriteMesh::PlayFrame() {
+ // TODO
+}
+
+void SpriteMesh::SetPlaybackSpeed(int speed) {
+ // TODO
+}
diff --git a/source/Sprite.hpp b/source/Sprite.hpp
new file mode 100644
index 0000000..ec25fbd
--- /dev/null
+++ b/source/Sprite.hpp
@@ -0,0 +1,98 @@
+#pragma once
+
+#include "CpuMesh.hpp"
+#include "Ires.hpp"
+#include "Mesh.hpp"
+#include "PodVector.hpp"
+#include "RcPtr.hpp"
+#include "Texture.hpp"
+
+#include <rapidjson/fwd.h>
+#include <glm/glm.hpp>
+#include <string>
+#include <string_view>
+#include <vector>
+
+class Sprite : public RefCounted {
+ friend class IresSpriteFiles;
+ friend class IresSpritesheet;
+
+private:
+ RcPtr<Texture> mAtlas;
+ glm::ivec2 mBoundingBox;
+ std::vector<Subregion> mFrames;
+
+public:
+ bool IsValid() const;
+ Texture* GetAtlas() const { return mAtlas.Get(); }
+ glm::ivec2 GetBoundingBox() const { return mBoundingBox; }
+ const decltype(mFrames)& GetFrames() const { return mFrames; }
+};
+
+class IresSpriteFiles : public IresObject {
+public:
+ RcPtr<Sprite> mInstance;
+ std::vector<std::string> spriteFiles;
+
+public:
+ IresSpriteFiles()
+ : IresObject(KD_SpriteFiles) {}
+
+ // NOTE: does not check whether all specified files have the same dimensions
+ bool IsValid() const;
+
+ Sprite* CreateInstance() const;
+ Sprite* GetInstance();
+ void InvalidateInstance();
+
+ void Write(rapidjson::Value& value, rapidjson::Document& root) const override;
+ void Read(const rapidjson::Value& value) override;
+};
+
+class IresSpritesheet : public IresObject {
+public:
+ RcPtr<Sprite> mInstance;
+ std::string spritesheetFile;
+ int sheetWSplit;
+ int sheetHSplit;
+
+public:
+ IresSpritesheet()
+ : IresObject(KD_Spritesheet) {}
+
+ bool IsValid() const;
+
+ static void ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit);
+
+ Sprite* CreateInstance() const;
+ Sprite* GetInstance();
+ void InvalidateInstance();
+
+ void Write(rapidjson::Value& value, rapidjson::Document& root) const override;
+ void Read(const rapidjson::Value& value) override;
+};
+
+class SpriteMesh {
+private:
+ RcPtr<GpuMesh> mMesh;
+ RcPtr<Sprite> mSprite;
+ PodVector<StandardVertex> mVertices;
+ PodVector<uint16_t> mIndices;
+ int mCurrentFrame = 0;
+ // # of frames per second
+ int mPlaybackSpeed = 5;
+
+public:
+ SpriteMesh(Sprite* sprite);
+
+ Sprite* GetSprite() const { return mSprite.Get(); }
+ GpuMesh* GetGpuMesh() const { return mMesh.Get(); }
+
+ int GetFrame() const { return mCurrentFrame; }
+ void SetFrame(int frame);
+ // Update as if a render frame has passed
+ void PlayFrame();
+
+ int GetPlaybackSpeed() const { return mPlaybackSpeed; }
+ void SetPlaybackSpeed(int speed);
+};
diff --git a/source/Texture.cpp b/source/Texture.cpp
index 08641e1..8fd74ac 100644
--- a/source/Texture.cpp
+++ b/source/Texture.cpp
@@ -1,90 +1,220 @@
#include "Texture.hpp"
+#include "Macros.hpp"
#include "PodVector.hpp"
+#include "ScopeGuard.hpp"
#include <stb_image.h>
#include <stb_rect_pack.h>
-#include <cstdint>
+#include <bit>
#include <cstring>
-#include <deque>
-#include <filesystem>
-#include <fstream>
-#include <stdexcept>
#include <utility>
Texture::~Texture() {
glDeleteTextures(1, &mHandle);
}
-static GLenum MapTextureFilteringToGL(Texture::Filtering option) {
+static GLenum MapTextureFilteringToGL(Tags::TexFilter option) {
+ using namespace Tags;
switch (option) {
- case Texture::LinearFilter: return GL_LINEAR;
- case Texture::NearestFilter: return GL_NEAREST;
+ case TF_Linear: return GL_LINEAR;
+ case TF_Nearest: return GL_NEAREST;
}
return 0;
}
-bool Texture::InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically) {
+Texture::ErrorCode Texture::InitFromFile(const char* filePath) {
+ if (IsValid()) {
+ return EC_AlreadyInitialized;
+ }
+
int width, height;
int channels;
- stbi_set_flip_vertically_on_load(flipVertically);
auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, 4);
if (!result) {
- return false;
+ return EC_FileIoFailed;
}
+ DEFER { stbi_image_free(result); };
- glDeleteTextures(1, &mHandle); // In case the caller gave us
glGenTextures(1, &mHandle);
glBindTexture(GL_TEXTURE_2D, mHandle);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter));
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter));
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result);
mInfo.size = { width, height };
- mInfo.isAtlas = false;
- return true;
+
+ return EC_Success;
}
-// bool Texture::InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically) {
-// GLenum sourceFormat;
-// switch (image.GetChannels()) {
-// case 1: sourceFormat = GL_RED; break;
-// case 2: sourceFormat = GL_RG; break;
-// case 3: sourceFormat = GL_RGB; break;
-// case 4: sourceFormat = GL_RGBA; break;
-// default: return false;
-// }
+Texture::ErrorCode Texture::InitFromImage(const Image& image) {
+ if (IsValid()) {
+ return EC_AlreadyInitialized;
+ }
+
+ GLenum sourceFormat;
+ switch (image.GetChannels()) {
+ case 1: sourceFormat = GL_RED; break;
+ case 2: sourceFormat = GL_RG; break;
+ case 3: sourceFormat = GL_RGB; break;
+ case 4: sourceFormat = GL_RGBA; break;
+ default: return EC_InvalidImage;
+ }
+
+ auto size = image.GetSize();
+ uint8_t* dataPtr = image.GetDataPtr();
+
+ glGenTextures(1, &mHandle);
+ glBindTexture(GL_TEXTURE_2D, mHandle);
-// auto size = image.GetSize();
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.x, size.y, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr);
-// std::unique_ptr<uint8_t[]> dataStorage;
-// uint8_t* dataPtr;
-// if (flipVertically) {
-// dataStorage = std::make_unique<uint8_t[]>(image.GetDataLength());
-// dataPtr = dataStorage.get();
+ mInfo.size = size;
-// size_t rowStride = size.width * image.GetChannels() * sizeof(uint8_t);
-// for (size_t y = 0; y < size.height; ++y) {
-// size_t invY = (size.height - 1) - y;
-// std::memcpy(dataPtr + invY * rowStride, image.GetDataPtr() + y * rowStride, rowStride);
-// }
-// } else {
-// // dataStorage is unused, we read pixels directly from `image`
-// dataPtr = image.GetDataPtr();
-// }
+ return EC_Success;
+}
-// glDeleteTextures(1, &mHandle);
-// glGenTextures(1, &mHandle);
-// glBindTexture(GL_TEXTURE_2D, mHandle);
-// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter));
-// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter));
-// glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.width, size.height, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr);
+Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) {
+ // Force RGBA for easier time uploading to GL texture
+ constexpr int kDesiredChannels = 4;
-// mInfo.size = size;
-// mInfo.isAtlas = false;
-// return true;
-// }
+ PodVector<stbrp_rect> rects;
+ rects.resize(in.sources.size());
+
+ for (size_t i = 0; i < in.sources.size(); ++i) {
+ auto size = in.sources[i].image.GetSize();
+ auto& rect = rects[i];
+ rect.w = static_cast<stbrp_coord>(size.x);
+ rect.h = static_cast<stbrp_coord>(size.y);
+ }
+
+ int atlasWidth;
+ int atlasHeight;
+
+ // 1. Pack the candidate rectanges onto the (not yet allocated) atlas
+ // Note that the coordinates here are top-left origin
+ switch (in.packingMode) {
+ case PM_KeepSquare: {
+ atlasWidth = 512;
+ atlasHeight = 512;
+
+ PodVector<stbrp_node> nodes;
+ while (true) {
+ // No need to zero initialize stbrp_node, library will take care of that
+ nodes.resize(atlasWidth);
+
+ stbrp_context ctx;
+ stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], (int)nodes.size());
+ int result = stbrp_pack_rects(&ctx, rects.data(), (int)rects.size());
+
+ if (result != 1) {
+ atlasWidth *= 2;
+ atlasHeight *= 2;
+ } else {
+ // Break out of the while loop
+ break;
+ }
+ }
+ } break;
+
+ case PM_VerticalExtension:
+ case PM_HorizontalExtension: {
+ constexpr int kMaxHeight = 1024 * 32;
+ atlasWidth = 0;
+ atlasHeight = 0;
+
+ PodVector<stbrp_node> nodes;
+ stbrp_context ctx;
+ stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], nodes.size());
+ stbrp_pack_rects(&ctx, rects.data(), rects.size());
+
+ // Calculate width/height needed for atlas
+ auto& limiter = in.packingMode == PM_VerticalExtension ? atlasHeight : atlasWidth;
+ for (auto& rect : rects) {
+ int bottom = rect.y + rect.h;
+ limiter = std::max(limiter, bottom);
+ }
+ limiter = std::bit_ceil<uint32_t>(limiter);
+ } break;
+ }
+
+ // 2. Allocate atlas bitmap
+
+ // Number of bytes in *bitmap*
+ auto bytes = atlasWidth * atlasHeight * kDesiredChannels * sizeof(uint8_t);
+ // Note that the origin (first pixel) is the bottom-left corner, to be consistent with OpenGL
+ auto bitmap = std::make_unique<uint8_t[]>(bytes);
+ std::memset(bitmap.get(), 0, bytes * sizeof(uint8_t));
+
+ // 3. Put all candidate images to the atlas bitmap
+ // TODO don't flip
+ // We essentially flip the candidate images vertically when putting into the atlas bitmap, so that when OpenGL reads
+ // these bytes, it sees the "bottom row" (if talking in top-left origin) first
+ // (empty spots are set with 0, "flipping" doesn't apply to them)
+ //
+ // Conceptually, we flip the atlas bitmap vertically so that the origin is at bottom-left
+ // i.e. all the coordinates we talk (e.g. rect.x/y) are still in top-left origin
+
+ // Unit: bytes
+ size_t bitmapRowStride = atlasWidth * kDesiredChannels * sizeof(uint8_t);
+ for (size_t i = 0; i < in.sources.size(); ++i) {
+ auto& rect = rects[i];
+ // Data is assumed to be stored in top-left origin
+ auto data = in.sources[i].image.GetDataPtr();
+
+ // We need to copy row by row, because the candidate image bytes won't land in a continuous chunk in our atlas bitmap
+ // Unit: bytes
+ size_t incomingRowStride = rect.w * kDesiredChannels * sizeof(uint8_t);
+ // Unit: bytes
+ size_t bitmapX = rect.x * kDesiredChannels * sizeof(uint8_t);
+ for (int y = 0; y < rect.h; ++y) {
+ auto src = data + y * incomingRowStride;
+
+ int bitmapY = y;
+ auto dst = bitmap.get() + bitmapY * bitmapRowStride + bitmapX;
+
+ std::memcpy(dst, src, incomingRowStride);
+ }
+ }
+
+ // 4. Upload to VRAM
+ GLuint atlasTexture;
+ glGenTextures(1, &atlasTexture);
+ glBindTexture(GL_TEXTURE_2D, atlasTexture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlasWidth, atlasHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.get());
+
+ // 5. Generate atlas texture info
+ mHandle = atlasTexture;
+ mInfo.size = { atlasWidth, atlasHeight };
+
+ // 6. Generate output information
+ if (out) {
+ out->elements.reserve(in.sources.size());
+ for (size_t i = 0; i < in.sources.size(); ++i) {
+ auto& rect = rects[i];
+ auto& source = in.sources[i];
+ out->elements.push_back(AltasElement{
+ .name = source.name,
+ .subregion = Subregion{
+ .u0 = (float)(rect.x) / atlasWidth,
+ .v0 = (float)(rect.y + rect.h) / atlasHeight,
+ .u1 = (float)(rect.x + rect.w) / atlasWidth,
+ .v1 = (float)(rect.y) / atlasHeight,
+ },
+ .subregionSize = glm::ivec2(rect.w, rect.h),
+ });
+ }
+ }
+
+ return EC_Success;
+}
const TextureInfo& Texture::GetInfo() const {
return mInfo;
@@ -98,15 +228,20 @@ bool Texture::IsValid() const {
return mHandle != 0;
}
-void TextureManager::DiscoverTextures() {
- // TODO
+Texture* IresTexture::CreateInstance() const {
}
-Texture* TextureManager::FindTexture(std::string_view name) {
- auto iter = mTextures.find(name);
- if (iter != mTextures.end()) {
- return iter->second.Get();
- } else {
- return nullptr;
+Texture* IresTexture::GetInstance() {
+ if (mInstance == nullptr) {
+ mInstance.Attach(CreateInstance());
}
+ return mInstance.Get();
+}
+
+void IresTexture::Write(rapidjson::Value& value, rapidjson::Document& root) const {
+ // TODO
+}
+
+void IresTexture::Read(const rapidjson::Value& value) {
+ // TODO
}
diff --git a/source/Texture.hpp b/source/Texture.hpp
index c330bb3..ef4d136 100644
--- a/source/Texture.hpp
+++ b/source/Texture.hpp
@@ -1,18 +1,28 @@
#pragma once
+#include "GraphicsTags.hpp"
+#include "Image.hpp"
+#include "Ires.hpp"
#include "RcPtr.hpp"
-#include <absl/container/flat_hash_map.h>
#include <glad/glad.h>
+#include <cstdint>
#include <glm/glm.hpp>
-#include <memory>
+#include <span>
// TODO abstract texture traits such as component sizes from OpenGL
-class TextureInfo {
-public:
+struct Subregion {
+ float u0 = 0.0f;
+ float v0 = 0.0f;
+ float u1 = 0.0f;
+ float v1 = 0.0f;
+};
+
+struct TextureInfo {
glm::ivec2 size;
- bool isAtlas = false;
+ Tags::TexFilter minifyingFilter = Tags::TF_Linear;
+ Tags::TexFilter magnifyingFilter = Tags::TF_Linear;
};
class Texture : public RefCounted {
@@ -31,18 +41,41 @@ public:
Texture(Texture&&) = default;
Texture& operator=(Texture&&) = default;
- enum Filtering {
- LinearFilter,
- NearestFilter,
+ enum ErrorCode {
+ EC_Success,
+ EC_AlreadyInitialized,
+ EC_FileIoFailed,
+ EC_InvalidImage,
};
- struct TextureProperties {
- Filtering minifyingFilter = LinearFilter;
- Filtering magnifyingFilter = LinearFilter;
+ ErrorCode InitFromFile(const char* filePath);
+ ErrorCode InitFromImage(const Image& image);
+
+ struct AtlasSource {
+ std::string name;
+ Image image;
};
- bool InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically = false);
- // bool InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically = false);
+ struct AltasElement {
+ std::string name;
+ Subregion subregion;
+ glm::ivec2 subregionSize;
+ };
+
+ enum PackingMode {
+ PM_KeepSquare,
+ PM_VerticalExtension,
+ PM_HorizontalExtension,
+ };
+
+ struct AtlasInput {
+ std::span<AtlasSource> sources;
+ PackingMode packingMode;
+ };
+ struct AtlasOutput {
+ std::vector<AltasElement> elements;
+ };
+ ErrorCode InitAtlas(const AtlasInput& in, AtlasOutput* out = nullptr);
const TextureInfo& GetInfo() const;
GLuint GetHandle() const;
@@ -50,32 +83,17 @@ public:
bool IsValid() const;
};
-/// A pure numerical subregion of a texture. u0/v0 are the UV coordinates of bottom left
-/// corner, and u1/v1 are the top left corner.
-struct Subregion {
- /// Bottom left corner
- float u0 = 0.0f;
- float v0 = 0.0f;
- /// Top right corner
- float u1 = 0.0f;
- float v1 = 0.0f;
-};
-
-/// A subregion of a specific texture.
-struct TextureSubregion : public Subregion {
- RcPtr<Texture> atlasTexture;
-};
-
-class TextureManager {
+class IresTexture : public IresObject {
public:
- static inline TextureManager* instance = nullptr;
-
-private:
- absl::flat_hash_map<std::string_view, RcPtr<Texture>> mTextures;
+ RcPtr<Texture> mInstance;
public:
- void DiscoverTextures();
+ IresTexture()
+ : IresObject(KD_Texture) {}
+
+ Texture* CreateInstance() const;
+ Texture* GetInstance();
- const auto& GetTextures() const { return mTextures; }
- Texture* FindTexture(std::string_view name);
+ void Write(rapidjson::Value& value, rapidjson::Document& root) const override;
+ void Read(const rapidjson::Value& value) override;
};
diff --git a/source/Utils.cpp b/source/Utils.cpp
index d47f35b..5083eb7 100644
--- a/source/Utils.cpp
+++ b/source/Utils.cpp
@@ -56,3 +56,20 @@ FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) {
return fopen(path, ::GetModeString(mode, binary));
#endif
}
+
+bool Utils::InRangeInclusive(int n, int lower, int upper) {
+ if (lower > upper) {
+ std::swap(lower, upper);
+ }
+ return n >= lower && n <= upper;
+}
+
+bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) {
+ bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x);
+ bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y);
+ return verticalLine && horizontalLine;
+}
+
+bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) {
+ return p1.x == p2.x || p1.y == p2.y;
+}
diff --git a/source/Utils.hpp b/source/Utils.hpp
index 03fdfed..6239667 100644
--- a/source/Utils.hpp
+++ b/source/Utils.hpp
@@ -1,7 +1,10 @@
#pragma once
+#include <robin_hood.h>
#include <cstdio>
+#include <cstring>
#include <filesystem>
+#include <glm/glm.hpp>
namespace Utils {
@@ -18,4 +21,33 @@ constexpr float Abs(float v) noexcept {
return v < 0.0f ? -v : v;
}
+bool InRangeInclusive(int n, int lower, int upper);
+bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate);
+bool IsColinear(glm::ivec2 p1, glm::ivec2 p2);
+
} // namespace Utils
+
+struct StringHash {
+ using is_transparent = void;
+
+ std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); }
+ std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); }
+ std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); }
+};
+
+struct StringEqual {
+ using is_transparent = int;
+
+ bool operator()(std::string_view lhs, const std::string& rhs) const {
+ const std::string_view view = rhs;
+ return lhs == view;
+ }
+
+ bool operator()(const char* lhs, const std::string& rhs) const {
+ return std::strcmp(lhs, rhs.c_str()) == 0;
+ }
+
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ return lhs == rhs;
+ }
+};
diff --git a/source/main.cpp b/source/main.cpp
index 8f16403..3d02f8d 100644
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -2,14 +2,16 @@
#include "AppConfig.hpp"
#include "EditorNotification.hpp"
+#include "Ires.hpp"
#include "Material.hpp"
+#include "Mesh.hpp"
#include "Shader.hpp"
-#include "Texture.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <backends/imgui_impl_glfw.h>
+#include <backends/imgui_impl_opengl2.h>
#include <backends/imgui_impl_opengl3.h>
#include <glad/glad.h>
#include <imgui.h>
@@ -55,18 +57,35 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio
}
int main(int argc, char* argv[]) {
+ using namespace Tags;
+
+ constexpr auto kImGuiBackend = "imgui-backend";
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()
+ (kImGuiBackend, "ImGui backend. Options: opengl2, opengl3. Leave empty to default.", cxxopts::value<std::string>())
(kGameAssetDir, "Directory in which assets are looked up from. Can be relative paths to the executable.", cxxopts::value<std::string>()->default_value("."))
(kGameDataDir, "Directory in which game data (such as saves and options) are saved to. Leave empty to use the default directory on each platform.", cxxopts::value<std::string>())
- ;
+ ;
// clang-format on
auto args = options.parse(argc, argv);
+ bool imguiUseOpenGL3;
+ {
+ auto imguiBackend = args[kImGuiBackend].as<std::string>();
+ if (imguiBackend == "opengl2") {
+ imguiUseOpenGL3 = false;
+ } else if (imguiBackend == "opengl3") {
+ imguiUseOpenGL3 = true;
+ } else {
+ // TODO support more backends?
+ imguiUseOpenGL3 = false;
+ }
+ }
+
{
auto assetDir = args[kGameAssetDir].as<std::string>();
@@ -103,20 +122,20 @@ int main(int argc, char* argv[]) {
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
- const char* glsl_version = "#version 100";
+ const char* imguiGlslVersion = "#version 100";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
// GL 3.2 + GLSL 150
- const char* glsl_version = "#version 150";
+ const char* imguiGlslVersion = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
// GL 3.0 + GLSL 130
- const char* glsl_version = "#version 130";
+ const char* imguiGlslVersion = "#version 130";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
@@ -146,41 +165,99 @@ int main(int argc, char* argv[]) {
ImGui::CreateContext();
ImGui_ImplGlfw_InitForOpenGL(window, true);
- ImGui_ImplOpenGL3_Init(glsl_version);
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_Init(imguiGlslVersion);
+ } else {
+ ImGui_ImplOpenGL2_Init();
+ }
ShaderManager::instance = new ShaderManager();
- TextureManager::instance = new TextureManager();
MaterialManager::instance = new MaterialManager();
+ IresManager::instance = new IresManager();
ShaderManager::instance->DiscoverShaders();
- TextureManager::instance->DiscoverTextures();
MaterialManager::instance->DiscoverMaterials();
+ IresManager::instance->DiscoverFilesDesignatedLocation();
+
+ gVformatStandard.Attach(new VertexFormat());
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float3,
+ .semantic = VES_Position,
+ });
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 1,
+ .type = VET_Float2,
+ .semantic = VES_TexCoords1,
+ });
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 1,
+ .type = VET_Ubyte4Norm,
+ .semantic = VES_Color1,
+ });
+
+ gVformatStandardPacked.Attach(new VertexFormat());
+ gVformatStandardPacked->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float3,
+ .semantic = VES_Position,
+ });
+ gVformatStandardPacked->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float2,
+ .semantic = VES_TexCoords1,
+ });
+ gVformatStandardPacked->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Ubyte4Norm,
+ .semantic = VES_Color1,
+ });
app.Init();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
- ImGui_ImplOpenGL3_NewFrame();
- ImGui_ImplGlfw_NewFrame();
- ImGui::NewFrame();
+ int fbWidth, fbHeight;
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ glViewport(0, 0, fbWidth, fbHeight);
+ auto clearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+ glClearColor(clearColor.x * clearColor.w, clearColor.y * clearColor.w, clearColor.z * clearColor.w, clearColor.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ { // Regular draw
+ app.Update();
+ app.Draw();
+ }
- app.Show();
- ImGui::ShowNotifications();
+ { // ImGui stuff
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_NewFrame();
+ } else {
+ ImGui_ImplOpenGL2_NewFrame();
+ }
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
- ImGui::Render();
- int display_w, display_h;
- glfwGetFramebufferSize(window, &display_w, &display_h);
- glViewport(0, 0, display_w, display_h);
- auto clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
- 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());
+ app.Show();
+ ImGui::ShowNotifications();
+
+ ImGui::Render();
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ } else {
+ ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
+ }
+ }
glfwSwapBuffers(window);
}
app.Shutdown();
- ImGui_ImplOpenGL3_Shutdown();
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_Shutdown();
+ } else {
+ ImGui_ImplOpenGL2_Shutdown();
+ }
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();