From f163e8f37123e651ea80b690793845b31ddb8639 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Wed, 6 Apr 2022 20:52:51 -0700 Subject: Changeset: 2 Work on moving infrastruture to this project --- source/App.cpp | 30 +++- source/App.hpp | 22 ++- source/CMakeLists.txt | 11 +- source/GameObject.cpp | 136 ++++++++++++++++++ source/GameObject.hpp | 51 +++++++ source/GameObjectTypeTag.hpp | 16 +++ source/Level.cpp | 7 + source/Level.hpp | 16 +++ source/Macros.hpp | 29 ++++ source/Material.cpp | 169 ++++++++++++++++++++++ source/Material.hpp | 76 ++++++++++ source/Mesh.cpp | 1 + source/Mesh.hpp | 17 +++ source/Player.cpp | 50 +++++++ source/Player.hpp | 42 ++++++ source/PodVector.hpp | 295 ++++++++++++++++++++++++++++++++++++++ source/RcPtr.hpp | 113 +++++++++++++++ source/SceneThings.cpp | 19 +++ source/SceneThings.hpp | 21 +++ source/ScopeGuard.hpp | 53 +++++++ source/Shader.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++ source/Shader.hpp | 81 +++++++++++ source/Texture.cpp | 99 +++++++++++++ source/Texture.hpp | 66 +++++++++ source/TypeTraits.hpp | 19 +++ source/World.cpp | 68 +++++++++ source/World.hpp | 31 ++++ source/main.cpp | 19 ++- 28 files changed, 1879 insertions(+), 5 deletions(-) create mode 100644 source/GameObject.cpp create mode 100644 source/GameObject.hpp create mode 100644 source/GameObjectTypeTag.hpp create mode 100644 source/Level.cpp create mode 100644 source/Level.hpp create mode 100644 source/Macros.hpp create mode 100644 source/Material.cpp create mode 100644 source/Material.hpp create mode 100644 source/Mesh.cpp create mode 100644 source/Mesh.hpp create mode 100644 source/Player.cpp create mode 100644 source/Player.hpp create mode 100644 source/PodVector.hpp create mode 100644 source/RcPtr.hpp create mode 100644 source/SceneThings.cpp create mode 100644 source/SceneThings.hpp create mode 100644 source/ScopeGuard.hpp create mode 100644 source/Shader.cpp create mode 100644 source/Shader.hpp create mode 100644 source/Texture.cpp create mode 100644 source/Texture.hpp create mode 100644 source/TypeTraits.hpp create mode 100644 source/World.cpp create mode 100644 source/World.hpp (limited to 'source') diff --git a/source/App.cpp b/source/App.cpp index 35fb5f7..c85dd9e 100644 --- a/source/App.cpp +++ b/source/App.cpp @@ -1,7 +1,35 @@ #include "App.hpp" #include +#include + +void App::Init() { + mCurrentWorld = std::make_unique(); + auto worldRoot = mCurrentWorld->GetRoot(); + + constexpr int kPlayerCount = 2; + for (int i = 0; i < kPlayerCount; ++i) { + auto player = new Player(mCurrentWorld.get()); + worldRoot.AddChild(player); + mPlayers.push_back(player); + } +} + +void App::Shutdown() { + mCurrentWorld = nullptr; + mPlayers.clear(); +} void App::Show() { - // Application goes here + mCurrentWorld->Draw(); +} + +void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) { + for (auto& player : mPlayers) { + for (auto playerKeyboard : player->boundKeyboards) { + if (playerKeyboard == keyboard) { + player->HandleKeyInput(key, action); + } + } + } } diff --git a/source/App.hpp b/source/App.hpp index bc99354..5a701d0 100644 --- a/source/App.hpp +++ b/source/App.hpp @@ -1,6 +1,24 @@ #pragma once +#include "Player.hpp" +#include "PodVector.hpp" +#include "World.hpp" + +#define GLFW_INCLUDE_NONE +#include + +#include +#include + class App { +private: + PodVector mPlayers; + std::unique_ptr mCurrentWorld; + public: - void Show(); -}; \ No newline at end of file + void Init(); + void Shutdown(); + + void Show(); + void HandleKey(GLFWkeyboard* keyboard, int key, int action); +}; diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index a76475b..9a67cb5 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,4 +1,13 @@ target_sources(${PROJECT_NAME} PRIVATE - App.hpp App.cpp + App.cpp + GameObject.cpp + Level.cpp + Material.cpp + Mesh.cpp + Player.cpp + SceneThings.cpp + Shader.cpp + Texture.cpp + World.cpp ) diff --git a/source/GameObject.cpp b/source/GameObject.cpp new file mode 100644 index 0000000..bbddb20 --- /dev/null +++ b/source/GameObject.cpp @@ -0,0 +1,136 @@ +#include "GameObject.hpp" + +#include "World.hpp" + +void GameObject::FreeRecursive(GameObject* obj) { + auto gomm = obj->GetMemoryManagement(); + bool freeSelf = gomm != Tags::GOMM_SelfAndAllChildren; + bool freeChildren = gomm != Tags::GOMM_SelfAndAllChildren && gomm != Tags::GOMM_AllChildren; + + if (freeChildren) { + for (auto child : obj->GetChildren()) { + FreeRecursive(obj); + } + } + if (freeSelf) { + delete obj; + } +} + +GameObject::GameObject(GameWorld* world) + : mWorld{ world } { +} + +GameObject::~GameObject() { + RemoveAllChildren(); + if (mParent) { + mParent->RemoveChild(this); + // NOTE: from this point on, mParent will be nullptr + } +} + +GameWorld* GameObject::GetWorld() const { + return mWorld; +} + +GameObject* GameObject::GetParent() const { + return mParent; +} + +const PodVector& GameObject::GetChildren() const { + return mChildren; +} + +namespace ProjectBrussel_UNITY_ID { +bool ValidateGameObjectChild(GameObject* parent, GameObject* child) { + return parent->GetWorld() == child->GetWorld(); +} +} // namespace ProjectBrussel_UNITY_ID + +void GameObject::AddChild(GameObject* child) { + if (child->mParent) { + return; + } + if (!ProjectBrussel_UNITY_ID::ValidateGameObjectChild(this, child)) { + return; + } + + mChildren.push_back(child); + child->SetParent(this); +} + +GameObject* GameObject::RemoveChild(int index) { + if (index < 0 || index >= mChildren.size()) { + return nullptr; + } + + auto it = mChildren.begin() + index; + auto child = *it; + + // cancelUpdate(ret); + + std::swap(*it, mChildren.back()); + mChildren.pop_back(); + child->SetParent(nullptr); + return child; +} + +GameObject* GameObject::RemoveChild(GameObject* child) { + if (child) { + for (auto it = mChildren.begin(); it != mChildren.end(); ++it) { + if (*it == child) { + // cancelUpdate(child); + + std::swap(*it, mChildren.back()); + mChildren.pop_back(); + child->SetParent(nullptr); + return child; + } + } + } + return nullptr; +} + +PodVector GameObject::RemoveAllChildren() { + for (auto& child : mChildren) { + child->SetParent(nullptr); + } + + auto result = std::move(mChildren); + // Moving from STL object leaves it in a valid but _unspecified_ state, call std::vector::clear() to guarantee it's empty + // NOTE: even though we have the source code of PodVector, we still do this to follow convention + mChildren.clear(); + return result; +} + +Tags::GameObjectMemoryManagement GameObject::GetMemoryManagement() const { + return Tags::GOMM_None; +}; + +Tags::GameObjectType GameObject::GetTypeTag() const { + return Tags::GOT_Generic; +} + +const Material* GameObject::GetMeshMaterial() const { + return nullptr; +} + +const Mesh* GameObject::GetMesh() const { + return nullptr; +} + +void GameObject::Awaken() { +} + +void GameObject::Resleep() { +} + +void GameObject::Update() { +} + +void GameObject::SetParent(GameObject* parent) { + if (mParent != parent) { + mParent = parent; + // needUpdate(); + } +} diff --git a/source/GameObject.hpp b/source/GameObject.hpp new file mode 100644 index 0000000..9567edd --- /dev/null +++ b/source/GameObject.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "GameObjectTypeTag.hpp" +#include "Material.hpp" +#include "Mesh.hpp" +#include "PodVector.hpp" + +#include +#include +#include + +class GameWorld; +class GameObject { +private: + GameWorld* mWorld; + GameObject* mParent; + PodVector mChildren; + glm::quat mRot; + glm::vec3 mPos; + +public: + static void FreeRecursive(GameObject* object); + + // TODO allow moving between worlds + explicit GameObject(GameWorld* world); + virtual ~GameObject(); + + GameWorld* GetWorld() const; + GameObject* GetParent() const; + const PodVector& GetChildren() const; + void AddChild(GameObject* child); + GameObject* RemoveChild(int index); + GameObject* RemoveChild(GameObject* child); + PodVector RemoveAllChildren(); + + // Tag + virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const; + virtual Tags::GameObjectType GetTypeTag() const; + + // Visuals + virtual const Material* GetMeshMaterial() const; + virtual const Mesh* GetMesh() const; + + // Lifetime hooks + virtual void Awaken(); + virtual void Resleep(); + virtual void Update(); + +protected: + void SetParent(GameObject* parent); +}; diff --git a/source/GameObjectTypeTag.hpp b/source/GameObjectTypeTag.hpp new file mode 100644 index 0000000..6455507 --- /dev/null +++ b/source/GameObjectTypeTag.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace Tags { +enum GameObjectMemoryManagement : int { + GOMM_None, + GOMM_AllChildren, + GOMM_SelfAndAllChildren, +}; + +enum GameObjectType : int { + GOT_Generic, ///< All uncategorized game objects. + GOT_Player, + GOT_Building, + GOT_LevelWrapper, +}; +} // namespace Tags diff --git a/source/Level.cpp b/source/Level.cpp new file mode 100644 index 0000000..71a7473 --- /dev/null +++ b/source/Level.cpp @@ -0,0 +1,7 @@ +#include "Level.hpp" + +LevelWrapperObject::~LevelWrapperObject() { + for (auto child : GetChildren()) { + FreeRecursive(child); + } +} diff --git a/source/Level.hpp b/source/Level.hpp new file mode 100644 index 0000000..2359c1d --- /dev/null +++ b/source/Level.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "GameObject.hpp" + +class LevelWrapperObject : public GameObject { +public: + using GameObject::GameObject; + ~LevelWrapperObject() override; + + virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const override { return Tags::GOMM_AllChildren; } + virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_LevelWrapper; } +}; + +/// Represents a seralized GameObject tree. +class Level { +}; diff --git a/source/Macros.hpp b/source/Macros.hpp new file mode 100644 index 0000000..71a438a --- /dev/null +++ b/source/Macros.hpp @@ -0,0 +1,29 @@ +#pragma once + +#define STRINGIFY_IMPL(text) #text +#define STRINGIFY(text) STRINGIFY_IMPL(text) + +#define CONCAT_IMPL(a, b) a##b +#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c)) +#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d)) + +#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) +#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) +#define DISCARD UNIQUE_NAME(_discard) + +#define UNUSED(x) (void)x; + +#if defined(_MSC_VER) +# define UNREACHABLE __assume(0) +#elif defined(__GNUC__) || defined(__clang__) +# define UNREACHABLE __builtin_unreachable() +#else +# define UNREACHABLE +#endif + +#if defined(DOCTEST_CONFIG_DISABLE) +# define TESTED_MEMEBERS_VISBILITY private +#else +# define TESTED_MEMEBERS_VISBILITY public +#endif diff --git a/source/Material.cpp b/source/Material.cpp new file mode 100644 index 0000000..138434c --- /dev/null +++ b/source/Material.cpp @@ -0,0 +1,169 @@ +#include "Material.hpp" + +#include +#include + +Material::Material(Shader* shader) + : mShader(shader) { +} + +namespace ProjectBrussel_UNITY_ID { +template +TUniform& ObtainUniform(std::vector& uniforms, GLint location) { + for (auto& uniform : uniforms) { + if (uniform.location == location) { + return uniform; + } + } + + auto& uniform = uniforms.emplace_back(); + uniform.location = location; + return uniform; +} +} // namespace ProjectBrussel_UNITY_ID + +void Material::SetFloat(const char* name, float value) { + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location); + uniform.floatValue = value; + uniform.actualType = GL_FLOAT; +} + +void Material::SetInt(const char* name, int32_t value) { + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location); + uniform.intValue = value; + uniform.actualType = GL_INT; +} + +void Material::SetUInt(const char* name, uint32_t value) { + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundScalars, location); + uniform.uintValue = value; + uniform.actualType = GL_UNSIGNED_INT; +} + +template +void Material::SetVector(const char* name, const glm::vec& vec) { + static_assert(length >= 1 && length <= 4); + + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundVectors, location); + uniform.actualLength = length; + std::memset(uniform.value, 0, sizeof(uniform.value)); + std::memcpy(uniform.value, &vec[0], length * sizeof(float)); +} + +template void Material::SetVector<1>(const char*, const glm::vec<1, float>&); +template void Material::SetVector<2>(const char*, const glm::vec<2, float>&); +template void Material::SetVector<3>(const char*, const glm::vec<3, float>&); +template void Material::SetVector<4>(const char*, const glm::vec<4, float>&); + +template +void Material::SetMatrix(const char* name, const glm::mat& mat) { + static_assert(width >= 1 && width <= 4); + static_assert(height >= 1 && height <= 4); + + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + auto& uniform = ProjectBrussel_UNITY_ID::ObtainUniform(mBoundMatrices, location); + uniform.actualWidth = width; + uniform.actualHeight = height; + std::memset(uniform.value, 0, sizeof(uniform.value)); + std::memcpy(uniform.value, &mat[0][0], width * height * sizeof(float)); +} + +template void Material::SetMatrix<2, 2>(const char*, const glm::mat<2, 2, float>&); +template void Material::SetMatrix<3, 3>(const char*, const glm::mat<3, 3, float>&); +template void Material::SetMatrix<4, 4>(const char*, const glm::mat<4, 4, float>&); + +template void Material::SetMatrix<2, 3>(const char*, const glm::mat<2, 3, float>&); +template void Material::SetMatrix<3, 2>(const char*, const glm::mat<3, 2, float>&); + +template void Material::SetMatrix<2, 4>(const char*, const glm::mat<2, 4, float>&); +template void Material::SetMatrix<4, 2>(const char*, const glm::mat<4, 2, float>&); + +template void Material::SetMatrix<3, 4>(const char*, const glm::mat<3, 4, float>&); +template void Material::SetMatrix<4, 3>(const char*, const glm::mat<4, 3, float>&); + +void Material::SetTexture(const char* name, Texture* texture) { + GLint location = glGetUniformLocation(mShader->GetProgram(), name); + + for (auto& uniform : mBoundTextures) { + if (uniform.location == location) { + uniform.value.Attach(texture); + return; + } + } + + auto& uniform = mBoundTextures.emplace_back(); + uniform.value.Attach(texture); + uniform.location = location; +} + +std::span Material::GetVectors() const { + return mBoundVectors; +} + +std::span Material::GetMatrices() const { + return mBoundMatrices; +} + +std::span Material::GetTextures() const { + return mBoundTextures; +} + +const Shader& Material::GetShader() const { + return *mShader; +} + +static constexpr int IdentifyMatrixSize(int width, int height) { + return width * 10 + height; +} + +void Material::UseUniforms() const { + for (auto& uniform : mBoundScalars) { + switch (uniform.actualType) { + case GL_FLOAT: glUniform1f(uniform.location, uniform.intValue); break; + case GL_INT: glUniform1i(uniform.location, uniform.intValue); break; + case GL_UNSIGNED_INT: glUniform1ui(uniform.location, uniform.intValue); break; + default: break; + } + } + + for (auto& uniform : mBoundVectors) { + switch (uniform.actualLength) { + case 1: glUniform1fv(uniform.location, 1, &uniform.value[0]); break; + case 2: glUniform2fv(uniform.location, 1, &uniform.value[0]); break; + case 3: glUniform3fv(uniform.location, 1, &uniform.value[0]); break; + case 4: glUniform4fv(uniform.location, 1, &uniform.value[0]); break; + default: break; + } + } + + for (auto& uniform : mBoundMatrices) { + switch (IdentifyMatrixSize(uniform.actualWidth, uniform.actualHeight)) { + case IdentifyMatrixSize(2, 2): glUniformMatrix2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(3, 3): glUniformMatrix3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 4): glUniformMatrix4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(2, 3): glUniformMatrix2x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(3, 2): glUniformMatrix3x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(2, 4): glUniformMatrix2x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 2): glUniformMatrix4x2fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + case IdentifyMatrixSize(3, 4): glUniformMatrix3x4fv(uniform.location, 1, GL_FALSE, uniform.value); break; + case IdentifyMatrixSize(4, 3): glUniformMatrix4x3fv(uniform.location, 1, GL_FALSE, uniform.value); break; + + default: break; + } + } + + int i = 0; + for (auto& uniform : mBoundTextures) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, uniform.value->GetHandle()); + glUniform1i(uniform.location, i); + ++i; + } +} diff --git a/source/Material.hpp b/source/Material.hpp new file mode 100644 index 0000000..6290a25 --- /dev/null +++ b/source/Material.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "RcPtr.hpp" +#include "Shader.hpp" +#include "Texture.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO support multiple sizes of vectors and matrices +class Material : public RefCounted { +public: + struct ScalarUniform { + union { + float floatValue; + int32_t intValue; + uint32_t uintValue; + }; + GLenum actualType; + GLint location; + }; + + struct VectorUniform { + float value[4]; + int actualLength; + GLint location; + }; + + struct MatrixUniform { + float value[16]; + int actualWidth; + int actualHeight; + GLint location; + }; + + struct TextureUniform { + RcPtr value; + GLint location; + }; + + RcPtr mShader; + std::vector mBoundScalars; + std::vector mBoundVectors; + std::vector mBoundMatrices; + std::vector mBoundTextures; + +public: + Material(Shader* shader); + + void SetFloat(const char* name, float value); + void SetInt(const char* name, int32_t value); + void SetUInt(const char* name, uint32_t value); + + /// Instanciated for length == 1, 2, 3, 4 + template + void SetVector(const char* name, const glm::vec& vec); + + /// Instanciated for sizes (2,2) (3,3) (4,4) (2,3) (3,2) (2,4) (4,2) (3,4) (4,3) + template + void SetMatrix(const char* name, const glm::mat& mat); + + void SetTexture(const char* name, Texture* texture); + + std::span GetVectors() const; + std::span GetMatrices() const; + std::span GetTextures() const; + const Shader& GetShader() const; + + void UseUniforms() const; +}; diff --git a/source/Mesh.cpp b/source/Mesh.cpp new file mode 100644 index 0000000..3b0ee1f --- /dev/null +++ b/source/Mesh.cpp @@ -0,0 +1 @@ +#include "Mesh.hpp" diff --git a/source/Mesh.hpp b/source/Mesh.hpp new file mode 100644 index 0000000..d383087 --- /dev/null +++ b/source/Mesh.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "PodVector.hpp" +#include "RcPtr.hpp" + +#include + +struct MeshVertex { + float x, y, z; + float u, v; + uint8_t r, g, b, a; +}; + +class Mesh : public RefCounted { +private: + PodVector mVertex; +}; diff --git a/source/Player.cpp b/source/Player.cpp new file mode 100644 index 0000000..6575820 --- /dev/null +++ b/source/Player.cpp @@ -0,0 +1,50 @@ +#include "Player.hpp" + +// Keep the same number as # of fields in `struct {}` in PlayerKeyBinds +constexpr int kPlayerKeyBindCount = 4; + +// Here be dragons: this treats consecutive fiels as an array, technically UB +std::span PlayerKeyBinds::GetKeyArray() { + return { &keyLeft, kPlayerKeyBindCount }; +} +std::span PlayerKeyBinds::GetKeyStatusArray() { + return { &pressedLeft, kPlayerKeyBindCount }; +} + +void Player::Awaken() { +} + +void Player::Resleep() { +} + +void Player::Update() { + if (keybinds.pressedLeft) { + } + if (keybinds.pressedRight) { + } + + // TODO jump controller + + // TODO attack controller +} + +void Player::HandleKeyInput(int key, int action) { + bool pressed; + if (action == GLFW_PRESS) { + pressed = true; + } else if (action == GLFW_REPEAT) { + return; + } else /* action == GLFW_RELEASE */ { + pressed = false; + } + + for (int i = 0; i < kPlayerKeyBindCount; ++i) { + int kbKey = keybinds.GetKeyArray()[i]; + bool& kbStatus = keybinds.GetKeyStatusArray()[i]; + + if (kbKey == key) { + kbStatus = pressed; + break; + } + } +} diff --git a/source/Player.hpp b/source/Player.hpp new file mode 100644 index 0000000..1ed30cd --- /dev/null +++ b/source/Player.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "GameObject.hpp" +#include "GameObjectTypeTag.hpp" + +#define GLFW_INCLUDE_NONE +#include + +#include +#include + +struct PlayerKeyBinds { + int keyLeft = GLFW_KEY_A; + int keyRight = GLFW_KEY_D; + int keyJump = GLFW_KEY_SPACE; + int keyAttack = GLFW_KEY_J; + + bool pressedLeft = 0; + bool pressedRight = 0; + bool pressedJump = 0; + bool pressedAttack = 0; + + std::span GetKeyArray(); + std::span GetKeyStatusArray(); +}; + +class Player : public GameObject { +public: + std::vector boundKeyboards; + PlayerKeyBinds keybinds; + +public: + using GameObject::GameObject; + + virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Player; } + + virtual void Awaken() override; + virtual void Resleep() override; + virtual void Update() override; + + void HandleKeyInput(int key, int action); +}; diff --git a/source/PodVector.hpp b/source/PodVector.hpp new file mode 100644 index 0000000..8f0281b --- /dev/null +++ b/source/PodVector.hpp @@ -0,0 +1,295 @@ +// File adapted from dear-imgui's ImVector, implemented in https://github.com/ocornut/imgUI/blob/master/imgui.h +#pragma once + +#include +#include +#include +#include +#include +#include + +template +class PodVector { +public: + using value_type = T; + using iterator = value_type*; + using const_iterator = const value_type*; + +private: + int mSize; + int mCapacity; + T* mData; + +public: + PodVector() { + mSize = mCapacity = 0; + mData = nullptr; + } + + PodVector(const PodVector& src) { + mSize = mCapacity = 0; + mData = nullptr; + operator=(src); + } + + PodVector& operator=(const PodVector& src) { + clear(); + resize(src.mSize); + std::memcpy(mData, src.mData, (size_t)mSize * sizeof(T)); + return *this; + } + + PodVector(PodVector&& src) { + mSize = src.mSize; + mCapacity = src.mCapacity; + mData = src.mData; + + src.mSize = src.mCapacity = 0; + src.mData = nullptr; + } + + PodVector& operator=(PodVector&& src) { + if (this != &src) { + std::free(mData); + + mSize = src.mSize; + mCapacity = src.mCapacity; + mData = src.mData; + + src.mSize = src.mCapacity = 0; + src.mData = nullptr; + } + return *this; + } + + ~PodVector() { + std::free(mData); + } + + bool empty() const { return mSize == 0; } + int size() const { return mSize; } + int size_in_bytes() const { return mSize * (int)sizeof(T); } + int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } + int capacity() const { return mCapacity; } + + T& operator[](int i) { + assert(i >= 0 && i < mSize); + return mData[i]; + } + + const T& operator[](int i) const { + assert(i >= 0 && i < mSize); + return mData[i]; + } + + void clear() { + if (mData) { + mSize = mCapacity = 0; + std::free(mData); + mData = nullptr; + } + } + + T* begin() { return mData; } + const T* begin() const { return mData; } + T* end() { return mData + mSize; } + const T* end() const { return mData + mSize; } + + T& front() { + assert(mSize > 0); + return mData[0]; + } + + const T& front() const { + assert(mSize > 0); + return mData[0]; + } + + T& back() { + assert(mSize > 0); + return mData[mSize - 1]; + } + + const T& back() const { + assert(mSize > 0); + return mData[mSize - 1]; + } + + void swap(PodVector& rhs) { + int rhs_size = rhs.mSize; + rhs.mSize = mSize; + mSize = rhs_size; + int rhs_cap = rhs.mCapacity; + rhs.mCapacity = mCapacity; + mCapacity = rhs_cap; + T* rhs_mDataTmp = rhs.mData; + rhs.mData = mData; + mData = rhs_mDataTmp; + } + + int grow_capacity(int sz) const { + int newCapacity = mCapacity ? (mCapacity + mCapacity / 2) : 8; + return newCapacity > sz ? newCapacity : sz; + } + + void resize(int new_size) { + if (new_size > mCapacity) reserve(grow_capacity(new_size)); + mSize = new_size; + } + + void resize_more(int size) { + resize(mSize + size); + } + + void resize(int new_size, const T& v) { + if (new_size > mCapacity) reserve(grow_capacity(new_size)); + if (new_size > mSize) { + for (int n = mSize; n < new_size; n++) { + std::memcpy(&mData[n], &v, sizeof(v)); + } + } + mSize = new_size; + } + + void resize_more(int size, const T& v) { + resize(mSize + size, v); + } + + void shrink(int new_size) { + assert(new_size <= mSize); + mSize = new_size; + } + + /// Resize a vector to a smaller mSize, guaranteed not to cause a reallocation + void reserve(int newCapacity) { + if (newCapacity <= mCapacity) return; + auto tmp = (T*)std::malloc((size_t)newCapacity * sizeof(T)); + if (mData) { + std::memcpy(tmp, mData, (size_t)mSize * sizeof(T)); + std::free(mData); + } + mData = tmp; + mCapacity = newCapacity; + } + + void reserve_more(int size) { + reserve(mSize + size); + } + + /// NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the PodVector data itself! e.g. v.push_back(v[10]) is forbidden. + void push_back(const T& v) { + if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); + std::memcpy(&mData[mSize], &v, sizeof(v)); + mSize++; + } + + void pop_back() { + assert(mSize > 0); + mSize--; + } + + void push_front(const T& v) { + if (mSize == 0) { + push_back(v); + } else { + insert(mData, v); + } + } + + T* erase(const T* it) { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + std::memmove(mData + off, mData + off + 1, ((size_t)mSize - (size_t)off - 1) * sizeof(T)); + mSize--; + return mData + off; + } + + T* erase(const T* it, const T* it_last) { + assert(it >= mData && it < mData + mSize && it_last > it && it_last <= mData + mSize); + const ptrdiff_t count = it_last - it; + const ptrdiff_t off = it - mData; + std::memmove(mData + off, mData + off + count, ((size_t)mSize - (size_t)off - count) * sizeof(T)); + mSize -= (int)count; + return mData + off; + } + + T* erase_unsorted(const T* it) { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + if (it < mData + mSize - 1) std::memcpy(mData + off, mData + mSize - 1, sizeof(T)); + mSize--; + return mData + off; + } + + T* insert(const T* it, const T& v) { + assert(it >= mData && it <= mData + mSize); + const ptrdiff_t off = it - mData; + if (mSize == mCapacity) reserve(grow_capacity(mSize + 1)); + if (off < (int)mSize) std::memmove(mData + off + 1, mData + off, ((size_t)mSize - (size_t)off) * sizeof(T)); + std::memcpy(&mData[off], &v, sizeof(v)); + mSize++; + return mData + off; + } + + bool contains(const T& v) const { + const T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) { + if (*data++ == v) return true; + } + return false; + } + + T* find(const T& v) { + T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) + if (*data == v) + break; + else + ++data; + return data; + } + + const T* find(const T& v) const { + const T* data = mData; + const T* dataEnd = mData + mSize; + while (data < dataEnd) + if (*data == v) + break; + else + ++data; + return data; + } + + bool find_erase(const T& v) { + const T* it = find(v); + if (it < mData + mSize) { + erase(it); + return true; + } + return false; + } + + bool find_erase_unsorted(const T& v) { + const T* it = find(v); + if (it < mData + mSize) { + erase_unsorted(it); + return true; + } + return false; + } + + int index_from_ptr(const T* it) const { + assert(it >= mData && it < mData + mSize); + const ptrdiff_t off = it - mData; + return (int)off; + } + + // Custom utility functions + + std::span as_span() { return { mData, (size_t)mSize }; } + std::span as_data_span() { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } + std::span as_span() const { return { mData, (size_t)mSize }; } + std::span as_data_span() const { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; } +}; diff --git a/source/RcPtr.hpp b/source/RcPtr.hpp new file mode 100644 index 0000000..90097e1 --- /dev/null +++ b/source/RcPtr.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include "Macros.hpp" +#include "TypeTraits.hpp" + +#include +#include +#include + +class RefCounted { +public: + // DO NOT MODIFY this field, unless explicitly documented the use + size_t refCount = 0; +}; + +template > +class RcPtr : TDeleter { +private: + static_assert(std::is_base_of_v); + T* mPtr; + +public: + RcPtr() + : mPtr{ nullptr } { + } + + explicit RcPtr(T* ptr) + : mPtr{ ptr } { + if (ptr) { + ++ptr->refCount; + } + } + + ~RcPtr() { + CleanUp(); + } + + void Attach(T* ptr) { + CleanUp(); + mPtr = ptr; + if (ptr) { + ++ptr->refCount; + } + } + + void Detatch() { + CleanUp(); + mPtr = nullptr; + } + + RcPtr(const RcPtr& that) + : mPtr{ that.mPtr } { + if (mPtr) { + ++mPtr->refCount; + } + } + + RcPtr& operator=(const RcPtr& that) { + CleanUp(); + mPtr = that.mPtr; + if (mPtr) { + ++mPtr->refCount; + } + return *this; + } + + RcPtr(RcPtr&& that) + : mPtr{ that.mPtr } { + that.mPtr = nullptr; + } + + RcPtr& operator=(RcPtr&& that) { + CleanUp(); + mPtr = that.mPtr; + that.mPtr = nullptr; + return *this; + } + + template + requires std::is_base_of_v + operator RcPtr() const { + return RcPtr(mPtr); + } + + bool operator==(std::nullptr_t ptr) const { + return mPtr == nullptr; + } + + bool operator==(const T* ptr) const { + return mPtr == ptr; + } + + bool operator==(T* ptr) const { + return mPtr == ptr; + } + + T* Get() const { + return mPtr; + } + + T& operator*() const { return *mPtr; } + T* operator->() const { return mPtr; } + +private: + void CleanUp() { + if (mPtr) { + --mPtr->refCount; + if (mPtr->refCount == 0) { + TDeleter::operator()(mPtr); + } + } + } +}; diff --git a/source/SceneThings.cpp b/source/SceneThings.cpp new file mode 100644 index 0000000..ec7a82f --- /dev/null +++ b/source/SceneThings.cpp @@ -0,0 +1,19 @@ +#include "SceneThings.hpp" + +void BuildingObject::SetMeshMaterial(Material* material) { + mMaterial.Attach(material); + // TODO update render +} + +const Material* BuildingObject::GetMeshMaterial() const { + return mMaterial.Get(); +} + +void BuildingObject::SetMesh(Mesh* mesh) { + mMesh.Attach(mesh); + // TODO update render +} + +const Mesh* BuildingObject::GetMesh() const { + return mMesh.Get(); +} diff --git a/source/SceneThings.hpp b/source/SceneThings.hpp new file mode 100644 index 0000000..0e213a6 --- /dev/null +++ b/source/SceneThings.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "GameObject.hpp" + +#include + +class BuildingObject : public GameObject { +private: + RcPtr mMesh; + RcPtr mMaterial; + +public: + using GameObject::GameObject; + + virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Building; } + + void SetMeshMaterial(Material* material); + virtual const Material* GetMeshMaterial() const override; + void SetMesh(Mesh* mesh); + virtual const Mesh* GetMesh() const override; +}; diff --git a/source/ScopeGuard.hpp b/source/ScopeGuard.hpp new file mode 100644 index 0000000..b4a1749 --- /dev/null +++ b/source/ScopeGuard.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "Macros.hpp" + +#include + +template +class ScopeGuard { +private: + TCleanupFunc mFunc; + bool mDismissed = false; + +public: + /// Specifically left this implicit so that constructs like + /// \code + /// ScopeGuard sg = [&]() { res.Cleanup(); }; + /// \endcode + /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function + /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here. + // Deliberately not explicit to allow usages like: ScopeGuard var = lambda; + ScopeGuard(TCleanupFunc&& function) noexcept + : mFunc{ std::move(function) } { + } + + ~ScopeGuard() noexcept { + if (!mDismissed) { + mFunc(); + } + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + + ScopeGuard(ScopeGuard&& that) noexcept + : mFunc{ std::move(that.mFunc) } { + that.Cancel(); + } + + ScopeGuard& operator=(ScopeGuard&& that) noexcept { + if (!mDismissed) { + mFunc(); + } + this->mFunc = std::move(that.mFunc); + this->cancelled = std::exchange(that.cancelled, true); + } + + void Dismiss() noexcept { + mDismissed = true; + } +}; + +#define SCOPE_GUARD(name) ScopeGuard name = [&]() +#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]() diff --git a/source/Shader.cpp b/source/Shader.cpp new file mode 100644 index 0000000..458491a --- /dev/null +++ b/source/Shader.cpp @@ -0,0 +1,327 @@ +#include "Shader.hpp" + +#include "ScopeGuard.hpp" + +#include +#include +#include +#include +#include + +using namespace std::literals::string_literals; +using namespace std::literals::string_view_literals; + +Shader::~Shader() { + glDeleteProgram(mHandle); +} + +namespace ProjectBrussel_UNITY_ID { +Shader::ErrorCode LoadFile(std::string& out, const char* filePath) { + std::ifstream ifs(filePath); + if (!ifs) { + return Shader::FileIOFailed; + } + + std::stringstream buf; + buf << ifs.rdbuf(); + out = buf.str(); + + return Shader::Success; +} + +// Grabs section [begin, end) +Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int endIdx, GLenum type) { + out = glCreateShader(type); + + const GLchar* begin = &src[beginIdx]; + const GLint len = endIdx - beginIdx; + glShaderSource(out, 1, &begin, &len); + + glCompileShader(out); + GLint compileStatus; + glGetShaderiv(out, GL_COMPILE_STATUS, &compileStatus); + if (compileStatus == GL_FALSE) { + GLint len; + glGetShaderiv(out, GL_INFO_LOG_LENGTH, &len); + + std::string log(len, '\0'); + glGetShaderInfoLog(out, len, nullptr, log.data()); + + return Shader ::CompilationFailed; + } + + return Shader::Success; +} + +Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) { + return CreateShader(out, str.data(), 0, str.size(), type); +} + +Shader::ErrorCode LinkShaderProgram(GLuint program) { + glLinkProgram(program); + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus == GL_FALSE) { + GLint len; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); + + std::string log(len, '\0'); + glGetProgramInfoLog(program, len, nullptr, log.data()); + + return Shader::LinkingFailed; + } + + return Shader::Success; +} +} // namespace ProjectBrussel_UNITY_ID + +#define CATCH_ERROR_IMPL(x, name) \ + auto name = x; \ + if (name != Shader::Success) { \ + return name; \ + } +#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result)) + +Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { + if (IsValid()) { + return ShaderAlreadyCreated; + } + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + if (!sources.vertex.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER)); + glAttachShader(program, vertex); + } + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + if (!sources.geometry.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER)); + glAttachShader(program, geometry); + } + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + if (!sources.tessControl.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER)); + glAttachShader(program, tessControl); + } + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + if (!sources.tessEval.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER)); + glAttachShader(program, tessEval); + } + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + if (!sources.fragment.empty()) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER)); + glAttachShader(program, fragment); + } + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) { + if (IsValid()) { + return ShaderAlreadyCreated; + } + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + if (files.vertex) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.vertex)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, src, GL_VERTEX_SHADER)); + glAttachShader(program, vertex); + } + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + if (files.geometry) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.geometry)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, src, GL_GEOMETRY_SHADER)); + glAttachShader(program, geometry); + } + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + if (files.tessControl) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessControl)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER)); + glAttachShader(program, tessControl); + } + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + if (files.tessEval) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessEval)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER)); + glAttachShader(program, tessEval); + } + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + if (files.fragment) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.fragment)); + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, src, GL_FRAGMENT_SHADER)); + glAttachShader(program, fragment); + } + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromSource(std::string_view source) { + GLuint vertex = 0; + DEFER { glDeleteShader(vertex); }; + + GLuint geometry = 0; + DEFER { glDeleteShader(geometry); }; + + GLuint tessControl = 0; + DEFER { glDeleteShader(tessControl); }; + + GLuint tessEval = 0; + DEFER { glDeleteShader(tessEval); }; + + GLuint fragment = 0; + DEFER { glDeleteShader(fragment); }; + + int prevBegin = -1; // Excluding #type marker + int prevEnd = -1; // [begin, end) + std::string prevShaderVariant; + + auto CommitSection = [&]() -> ErrorCode { + if (prevBegin == -1 || prevEnd == -1) { + // Not actually "succeeding" here, but we just want to skip this call and continue + return Success; + } + + if (prevShaderVariant == "vertex" && !vertex) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER)); + } else if (prevShaderVariant == "geometry" && !geometry) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER)); + } else if (prevShaderVariant == "tessellation_control" && !tessControl) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER)); + } else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER)); + } else if (prevShaderVariant == "fragment" && !fragment) { + CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER)); + } else { + return InvalidShaderVariant; + } + + prevBegin = -1; + prevEnd = -1; + prevShaderVariant.clear(); + + return Success; + }; + + constexpr const char* kMarker = "#type "; + bool matchingDirective = true; // If true, we are matching marker pattern; if false, we are accumulating shader variant identifier + int matchIndex = 0; // Current index of the pattern trying to match + std::string shaderVariant; + + // Don't use utf8 iterator, shader sources are expected to be ASCII only + for (size_t i = 0; i < source.size(); ++i) { + char c = source[i]; + + if (matchingDirective) { + if (c == kMarker[matchIndex]) { + // Matched the expected character, go to next char in pattern + matchIndex++; + + // If we are at the end of the marker pattern... + if (kMarker[matchIndex] == '\0') { + matchingDirective = false; + matchIndex = 0; + continue; + } + + // This might be a shader variant directive -> might be end of a section + if (c == '#') { + prevEnd = i; + continue; + } + } else { + // Unexpected character, rollback to beginning + matchIndex = 0; + } + } else { + if (c == '\n') { + // Found complete shader variant directive + + CATCH_ERROR(CommitSection()); // Try commit section, for the first apparent of #type this should do nothing, as `prevEnd` will still be -1 + prevBegin = i + 1; // +1 to skip new line (technically not needed) + prevShaderVariant = std::move(shaderVariant); + + matchingDirective = true; + shaderVariant.clear(); + } else { + // Simply accumulate to shader variant buffer + shaderVariant += c; + } + } + } + + // Commit the last section + prevEnd = static_cast(source.size()); + CATCH_ERROR(CommitSection()); + + GLuint program = glCreateProgram(); + ScopeGuard sg = [&]() { glDeleteProgram(program); }; + + if (vertex) glAttachShader(program, vertex); + if (geometry) glAttachShader(program, geometry); + if (tessControl) glAttachShader(program, tessControl); + if (tessEval) glAttachShader(program, tessEval); + if (fragment) glAttachShader(program, fragment); + + CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program)); + + sg.Dismiss(); + mHandle = program; + + return Success; +} + +Shader::ErrorCode Shader::InitFromFile(const char* filePath) { + std::string src; + CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, filePath)); + + return InitFromSource(src); +} + +#undef CATCH_ERROR + +GLuint Shader::GetProgram() const { + return mHandle; +} + +bool Shader::IsValid() const { + return mHandle != 0; +} diff --git a/source/Shader.hpp b/source/Shader.hpp new file mode 100644 index 0000000..b481bd6 --- /dev/null +++ b/source/Shader.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "RcPtr.hpp" + +#include +#include +#include + +class ShaderDetails : public RefCounted { +public: +}; + +class Shader : public RefCounted { +private: + GLuint mHandle = 0; + +public: + Shader() = default; + ~Shader(); + Shader(const Shader&) = delete; + Shader& operator=(const Shader&) = delete; + Shader(Shader&&) = default; + Shader& operator=(Shader&&) = default; + + enum ErrorCode { + Success, + /// Generated when Init*() functions are called on an already initialized Shader object. + ShaderAlreadyCreated, + /// Generated when the one-source-file text contains invalid or duplicate shader variants. + InvalidShaderVariant, + FileIOFailed, + CompilationFailed, + LinkingFailed, + }; + + struct ShaderSources { + std::string_view vertex; + std::string_view geometry; + std::string_view tessControl; + std::string_view tessEval; + std::string_view fragment; + }; + + /// Create shader by compiling each shader source file separately and then combining them together + /// into a Shader object. + ErrorCode InitFromSources(const ShaderSources& sources); + + struct ShaderFilePaths { + const char* vertex = nullptr; + const char* geometry = nullptr; + const char* tessControl = nullptr; + const char* tessEval = nullptr; + const char* fragment = nullptr; + }; + + /// Create shader by loading each specified file and combining them together to form a shader object. + /// Use the mental model that this function simply loads the files from disk (synchronously) and then + /// passing them to `FromSource(const ShaderSources& source)`. + ErrorCode InitFromFiles(const ShaderFilePaths& files); + + /// The given text will be split into different shader sections according to #type directives, + /// and combined to form a Shader object. + /// For OpenGL, this process involves compililng each section separately and then linking them + /// together. + /// + /// There are a directive for each shader type + /// - `#type vertex`: Vertex shader + /// - `#type geometry`: Geometry shader + /// - `#type tessellation_control`: Tessellation control shader + /// - `#type tessellation_evaluation`: Tessellation evaluation shader + /// - `#type fragment`: Fragment shader + ErrorCode InitFromSource(std::string_view source); + + /// Create shader by loading the file at the `file`, and then giving the contents to + /// `FromSource(std::string_view source)`. + ErrorCode InitFromFile(const char* file); + + GLuint GetProgram() const; + + bool IsValid() const; +}; diff --git a/source/Texture.cpp b/source/Texture.cpp new file mode 100644 index 0000000..968c4bc --- /dev/null +++ b/source/Texture.cpp @@ -0,0 +1,99 @@ +#include "Texture.hpp" + +#include "PodVector.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Texture::~Texture() { + glDeleteTextures(1, &mHandle); +} + +static GLenum MapTextureFilteringToGL(Texture::Filtering option) { + switch (option) { + case Texture::LinearFilter: return GL_LINEAR; + case Texture::NearestFilter: return GL_NEAREST; + } + return 0; +} + +bool Texture::InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically) { + 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; + } + + 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)); + 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; +} + +// 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; +// } + +// auto size = image.GetSize(); + +// std::unique_ptr dataStorage; +// uint8_t* dataPtr; +// if (flipVertically) { +// dataStorage = std::make_unique(image.GetDataLength()); +// dataPtr = dataStorage.get(); + +// 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(); +// } + +// 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); + +// mInfo.size = size; +// mInfo.isAtlas = false; +// return true; +// } + +const TextureInfo& Texture::GetInfo() const { + return mInfo; +} + +GLuint Texture::GetHandle() const { + return mHandle; +} + +bool Texture::IsValid() const { + return mHandle != 0; +} diff --git a/source/Texture.hpp b/source/Texture.hpp new file mode 100644 index 0000000..c372998 --- /dev/null +++ b/source/Texture.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "RcPtr.hpp" + +#include +#include +#include + +// TODO abstract texture traits such as component sizes from OpenGL + +class TextureInfo { +public: + glm::ivec2 size; + bool isAtlas = false; +}; + +class Texture : public RefCounted { + friend class TextureStitcher; + +private: + TextureInfo mInfo; + GLuint mHandle = 0; + +public: + Texture() = default; + ~Texture(); + + Texture(const Texture&) = delete; + Texture& operator=(const Texture&) = delete; + Texture(Texture&&) = default; + Texture& operator=(Texture&&) = default; + + enum Filtering { + LinearFilter, + NearestFilter, + }; + + struct TextureProperties { + Filtering minifyingFilter = LinearFilter; + Filtering magnifyingFilter = LinearFilter; + }; + + bool InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically = false); + // bool InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically = false); + + const TextureInfo& GetInfo() const; + GLuint GetHandle() const; + + 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 atlasTexture; +}; diff --git a/source/TypeTraits.hpp b/source/TypeTraits.hpp new file mode 100644 index 0000000..cca9a1f --- /dev/null +++ b/source/TypeTraits.hpp @@ -0,0 +1,19 @@ +#pragma once + +template +struct DefaultDeleter { + void operator()(T* ptr) const { + delete ptr; + } +}; + +template +struct RemoveMemberPtrImpl {}; + +template +struct RemoveMemberPtrImpl { + using Type = U; +}; + +template +using RemoveMemberPtr = typename RemoveMemberPtrImpl::Type; diff --git a/source/World.cpp b/source/World.cpp new file mode 100644 index 0000000..907f056 --- /dev/null +++ b/source/World.cpp @@ -0,0 +1,68 @@ +#include "World.hpp" + +#include + +namespace ProjectBrussel_UNITY_ID { +template +void CallGameObjectRecursive(GameObject* start, TFunction&& func) { + PodVector stack; + stack.push_back(start); + + while (!stack.empty()) { + auto obj = stack.back(); + stack.pop_back(); + + for (auto child : obj->GetChildren()) { + stack.push_back(child); + } + + func(obj); + } +} + +struct DrawCall { + GLuint vao; + GLuint vbo; +}; +} // namespace ProjectBrussel_UNITY_ID + +struct GameWorld::RenderData { + void SubmitDrawCalls() { + // TODO + } +}; + +GameWorld::GameWorld() + : mRender{ new RenderData() } + , mRoot(this) { +} + +GameWorld::~GameWorld() { + delete mRender; + for (auto child : mRoot.GetChildren()) { + GameObject::FreeRecursive(child); + } +} + +void GameWorld::Awaken() { + if (mAwakened) return; + + ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Awaken(); }); + mAwakened = true; +} + +void GameWorld::Resleep() { + if (!mAwakened) return; + + ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Resleep(); }); + mAwakened = false; +} + +void GameWorld::Update() { + ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [this](GameObject* obj) { + obj->Update(); + }); +} + +void GameWorld::Draw() { +} diff --git a/source/World.hpp b/source/World.hpp new file mode 100644 index 0000000..ab33ecb --- /dev/null +++ b/source/World.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "GameObject.hpp" + +class GameWorld { +private: + class RenderData; + RenderData* mRender; + + GameObject mRoot; + bool mAwakened = false; + +public: + GameWorld(); + ~GameWorld(); + + GameWorld(const GameWorld&) = delete; + GameWorld& operator=(const GameWorld&) = delete; + GameWorld(GameWorld&&) = default; + GameWorld& operator=(GameWorld&&) = default; + + bool IsAwake() const { return mAwakened; } + void Awaken(); + void Resleep(); + void Update(); + + void Draw(); + + const GameObject& GetRoot() const { return mRoot; } + GameObject& GetRoot() { return mRoot; } +}; diff --git a/source/main.cpp b/source/main.cpp index b98d62b..8f61696 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -18,6 +18,14 @@ static void GlfwErrorCallback(int error, const char* description) { fprintf(stderr, "Glfw Error %d: %s\n", error, description); } +static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { + GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard(); + if (keyboard) { + App* app = static_cast(glfwGetWindowUserPointer(window)); + app->HandleKey(keyboard, key, action); + } +} + int main() { if (!glfwInit()) { return -1; @@ -46,10 +54,18 @@ int main() { glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); #endif + App app; + GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr); if (window == nullptr) { return -2; } + + glfwSetWindowUserPointer(window, &app); + + // Window callbacks are retained by ImGui GLFW backend + glfwSetKeyCallback(window, &GlfwKeyCallback); + glfwMakeContextCurrent(window); glfwSwapInterval(1); @@ -63,7 +79,7 @@ int main() { ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); - App app; + app.Init(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -84,6 +100,7 @@ int main() { glfwSwapBuffers(window); } + app.Shutdown(); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); -- cgit v1.2.3-70-g09d2