aboutsummaryrefslogtreecommitdiff
path: root/ProjectBrussel/Game/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ProjectBrussel/Game/main.cpp')
-rw-r--r--ProjectBrussel/Game/main.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/ProjectBrussel/Game/main.cpp b/ProjectBrussel/Game/main.cpp
new file mode 100644
index 0000000..c49fc0b
--- /dev/null
+++ b/ProjectBrussel/Game/main.cpp
@@ -0,0 +1,461 @@
+#include "App.hpp"
+
+#include "AppConfig.hpp"
+#include "CommonVertexIndex.hpp"
+#include "EditorGuizmo.hpp"
+#include "Ires.hpp"
+#include "Level.hpp"
+#include "Material.hpp"
+#include "Shader.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>
+#include <imgui_internal.h>
+#include <cstdlib>
+#include <cxxopts.hpp>
+#include <filesystem>
+#include <string>
+
+#include <tracy/Tracy.hpp>
+#include <tracy/TracyClient.cpp>
+
+namespace fs = std::filesystem;
+using namespace std::literals;
+
+struct GlfwUserData {
+ App* app = nullptr;
+};
+
+void GlfwErrorCallback(int error, const char* description) {
+ fprintf(stderr, "[GLFW] Error %d: %s\n", error, description);
+}
+
+void GlfwFramebufferResizeCallback(GLFWwindow* window, int width, int height) {
+ AppConfig::mainWindowWidth = width;
+ AppConfig::mainWindowHeight = height;
+ AppConfig::mainWindowAspectRatio = (float)width / height;
+}
+
+void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mods) {
+ if (ImGui::GetIO().WantCaptureMouse) {
+ return;
+ }
+
+ auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window));
+ auto app = userData->app;
+ app->HandleMouse(button, action);
+}
+
+void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff) {
+ if (ImGui::GetIO().WantCaptureMouse) {
+ return;
+ }
+
+ auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window));
+ auto app = userData->app;
+ app->HandleMouseMotion(xOff, yOff);
+}
+
+void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ if (ImGui::GetIO().WantCaptureKeyboard) {
+ return;
+ }
+
+ GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard();
+ if (keyboard) {
+ auto userData = static_cast<GlfwUserData*>(glfwGetWindowUserPointer(window));
+ auto app = userData->app;
+ app->HandleKey(keyboard, key, action);
+ }
+}
+
+// https://stackoverflow.com/questions/54499256/how-to-find-the-saved-games-folder-programmatically-in-c-c
+#if defined(_WIN32)
+# if defined(__MINGW32__)
+# include <ShlObj.h>
+# else
+# include <ShlObj_core.h>
+# endif
+# include <objbase.h>
+# pragma comment(lib, "shell32.lib")
+# pragma comment(lib, "ole32.lib")
+#elif defined(__linux__)
+fs::path GetEnvVar(const char* name, const char* backup) {
+ if (const char* path = std::getenv(name)) {
+ fs::path dataDir(path);
+ fs::create_directories(dataDir);
+ return dataDir;
+ } else {
+ fs::path dataDir(backup);
+ fs::create_directories(dataDir);
+ return dataDir;
+ }
+}
+#endif
+
+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;
+ if (args.count(kImGuiBackend) > 0) {
+ auto imguiBackend = args[kImGuiBackend].as<std::string>();
+ if (imguiBackend == "opengl2") {
+ imguiUseOpenGL3 = false;
+ } else if (imguiBackend == "opengl3") {
+ imguiUseOpenGL3 = true;
+ } else {
+ // TODO support more backends?
+ imguiUseOpenGL3 = true;
+ }
+ } else {
+ imguiUseOpenGL3 = true;
+ }
+
+ if (args.count(kGameAssetDir) > 0) {
+ auto assetDir = args[kGameAssetDir].as<std::string>();
+
+ fs::path assetDirPath(assetDir);
+ if (!fs::exists(assetDirPath)) {
+ fprintf(stderr, "Invalid asset directory.\n");
+ return -4;
+ }
+
+ AppConfig::assetDir = std::move(assetDir);
+ AppConfig::assetDirPath = std::move(assetDirPath);
+ } else {
+#if defined(_WIN32)
+ fs::path dataDir;
+
+ PWSTR path = nullptr;
+ HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path);
+ if (SUCCEEDED(hr)) {
+ dataDir = fs::path(path) / AppConfig::kAppName;
+ CoTaskMemFree(path);
+
+ fs::create_directories(dataDir);
+ } else {
+ std::string msg;
+ msg += "Failed to find/create the default user data directory at %APPDATA%. Error code: ";
+ msg += hr;
+ throw std::runtime_error(msg);
+ }
+#elif defined(__APPLE__)
+ // MacOS programming guide recommends apps to hardcode the path - user customization of "where data are stored" is done in Finder
+ auto dataDir = fs::path("~/Library/Application Support/") / AppConfig::kAppName;
+ fs::create_directories(dataDir);
+#elif defined(__linux__)
+ auto dataDir = GetEnvVar("XDG_DATA_HOME", "~/.local/share") / AppConfig::kAppName;
+ fs::create_directories(dataDir);
+#endif
+ }
+
+ if (args.count(kGameDataDir) > 0) {
+ auto dataDir = args[kGameDataDir].as<std::string>();
+
+ fs::path dataDirPath(dataDir);
+ fs::create_directories(dataDir);
+
+ AppConfig::dataDir = std::move(dataDir);
+ AppConfig::dataDirPath = std::move(dataDirPath);
+ } else {
+ // TODO platform default path
+ AppConfig::dataDir = ".";
+ AppConfig::dataDirPath = fs::path(".");
+ }
+
+ if (!glfwInit()) {
+ return -1;
+ }
+
+ glfwSetErrorCallback(&GlfwErrorCallback);
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
+
+#if defined(__APPLE__)
+ const char* imguiGlslVersion = "#version 150";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
+#else
+ const char* imguiGlslVersion = "#version 130";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+#endif
+
+ GlfwUserData glfwUserData;
+
+ GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr);
+ if (window == nullptr) {
+ return -2;
+ }
+
+ glfwSetWindowUserPointer(window, &glfwUserData);
+
+ // Window callbacks are retained by ImGui GLFW backend
+ glfwSetFramebufferSizeCallback(window, &GlfwFramebufferResizeCallback);
+ glfwSetKeyCallback(window, &GlfwKeyCallback);
+ glfwSetMouseButtonCallback(window, &GlfwMouseCallback);
+ glfwSetCursorPosCallback(window, &GlfwMouseMotionCallback);
+
+ {
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+ GlfwFramebufferResizeCallback(window, width, height);
+ }
+
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ // TODO setup opengl debug context
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
+ return -3;
+ }
+
+ IMGUI_CHECKVERSION();
+ auto ctx = ImGui::CreateContext();
+ auto& io = ImGui::GetIO();
+ ImGuizmo::SetImGuiContext(ctx);
+
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_Init(imguiGlslVersion);
+ } else {
+ ImGui_ImplOpenGL2_Init();
+ }
+
+ IresManager::instance = new IresManager();
+ IresManager::instance->DiscoverFilesDesignatedLocation();
+
+ LevelManager::instance = new LevelManager();
+ LevelManager::instance->DiscoverFilesDesignatedLocation();
+
+ gVformatStandard.Attach(new VertexFormat());
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float3,
+ .semantic = VES_Position,
+ });
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float2,
+ .semantic = VES_TexCoords1,
+ });
+ gVformatStandard->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Ubyte4Norm,
+ .semantic = VES_Color1,
+ });
+
+ gVformatStandardSplit.Attach(new VertexFormat());
+ gVformatStandardSplit->AddElement(VertexElementFormat{
+ .bindingIndex = 0,
+ .type = VET_Float3,
+ .semantic = VES_Position,
+ });
+ gVformatStandardSplit->AddElement(VertexElementFormat{
+ .bindingIndex = 1,
+ .type = VET_Float2,
+ .semantic = VES_TexCoords1,
+ });
+ gVformatStandardSplit->AddElement(VertexElementFormat{
+ .bindingIndex = 1,
+ .type = VET_Ubyte4Norm,
+ .semantic = VES_Color1,
+ });
+
+ // Matches gVformatStandard
+ gDefaultShader.Attach(new Shader());
+ gDefaultShader->InitFromSources(Shader::ShaderSources{
+ .vertex = R"""(
+#version 330 core
+layout(location = 0) in vec3 pos;
+layout(location = 1) in vec4 color;
+out vec4 v2fColor;
+uniform mat4 transform;
+void main() {
+ gl_Position = transform * vec4(pos, 1.0);
+ v2fColor = color;
+}
+)"""sv,
+ .fragment = R"""(
+#version 330 core
+in vec4 v2fColor;
+out vec4 fragColor;
+void main() {
+ fragColor = v2fColor;
+}
+)"""sv,
+ });
+ { // in vec3 pos;
+ ShaderMathVariable var;
+ var.scalarType = GL_FLOAT;
+ var.width = 1;
+ var.height = 3;
+ var.arrayLength = 1;
+ var.semantic = VES_Position;
+ var.location = 0;
+ gDefaultShader->GetInfo().inputs.push_back(std::move(var));
+ gDefaultShader->GetInfo().things.try_emplace(
+ "pos"s,
+ ShaderThingId{
+ .kind = ShaderThingId::KD_Input,
+ .index = (int)gDefaultShader->GetInfo().inputs.size() - 1,
+ });
+ }
+ { // in vec4 color;
+ ShaderMathVariable var;
+ var.scalarType = GL_FLOAT;
+ var.width = 1;
+ var.height = 4;
+ var.arrayLength = 1;
+ var.semantic = VES_Color1;
+ var.location = 1;
+ gDefaultShader->GetInfo().inputs.push_back(std::move(var));
+ gDefaultShader->GetInfo().things.try_emplace(
+ "color"s,
+ ShaderThingId{
+ .kind = ShaderThingId::KD_Input,
+ .index = (int)gDefaultShader->GetInfo().inputs.size() - 1,
+ });
+ }
+ { // out vec4 fragColor;
+ ShaderMathVariable var;
+ var.scalarType = GL_FLOAT;
+ var.width = 1;
+ var.height = 4;
+ var.arrayLength = 1;
+ gDefaultShader->GetInfo().outputs.push_back(std::move(var));
+ gDefaultShader->GetInfo().things.try_emplace(
+ "fragColor"s,
+ ShaderThingId{
+ .kind = ShaderThingId::KD_Output,
+ .index = (int)gDefaultShader->GetInfo().outputs.size() - 1,
+ });
+ }
+ // NOTE: autofill uniforms not recorded here
+
+ gDefaultMaterial.Attach(new Material());
+ gDefaultMaterial->SetShader(gDefaultShader.Get());
+
+ { // Main loop
+ App app;
+ glfwUserData.app = &app;
+
+ // NOTE: don't enable backface culling, because the game mainly runs in 2D and sometimes we'd like to flip sprites around
+ // it also helps with debugging layers in 3D view
+ glEnable(GL_DEPTH_TEST);
+
+ // 60 updates per second
+ constexpr double kMsPerUpdate = 1000.0 / 60;
+ constexpr double kSecondsPerUpdate = kMsPerUpdate / 1000;
+ double prevTime = glfwGetTime();
+ double accumulatedTime = 0.0;
+ while (!glfwWindowShouldClose(window)) {
+ {
+ ZoneScopedN("GameInput");
+ glfwPollEvents();
+ }
+
+ double currTime = glfwGetTime();
+ double deltaTime = prevTime - currTime;
+
+ // In seconds
+ accumulatedTime += currTime - prevTime;
+
+ // Update
+ // Play "catch up" to ensure a deterministic number of Update()'s per second
+ while (accumulatedTime >= kSecondsPerUpdate) {
+ double beg = glfwGetTime();
+ {
+ ZoneScopedN("GameUpdate");
+ app.Update();
+ }
+ double end = glfwGetTime();
+
+ // Update is taking longer than it should be, start skipping updates
+ auto diff = end - beg;
+ if (diff >= kSecondsPerUpdate) {
+ auto skippedUpdates = (int)(accumulatedTime / kSecondsPerUpdate);
+ accumulatedTime = 0.0;
+ fprintf(stderr, "Elapsed time %f, skipped %d updates.", diff, skippedUpdates);
+ } else {
+ accumulatedTime -= kSecondsPerUpdate;
+ }
+ }
+
+ int fbWidth = AppConfig::mainWindowWidth;
+ int fbHeight = AppConfig::mainWindowHeight;
+ 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 | GL_DEPTH_BUFFER_BIT);
+
+ { // Regular draw
+ ZoneScopedN("Render");
+ app.Draw(currTime, deltaTime);
+ }
+
+ { // ImGui draw
+ ZoneScopedN("ImGui");
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_NewFrame();
+ } else {
+ ImGui_ImplOpenGL2_NewFrame();
+ }
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ app.Show();
+
+ ImGui::Render();
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ } else {
+ ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
+ }
+ }
+
+ glfwSwapBuffers(window);
+ FrameMark;
+
+ prevTime = currTime;
+ }
+ }
+
+ if (imguiUseOpenGL3) {
+ ImGui_ImplOpenGL3_Shutdown();
+ } else {
+ ImGui_ImplOpenGL2_Shutdown();
+ }
+ ImGui_ImplGlfw_Shutdown();
+
+ ImGui::DestroyContext();
+
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}