diff options
author | rtk0c <[email protected]> | 2023-09-20 23:58:58 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2023-09-20 23:58:58 -0700 |
commit | f138311d2d2e0cc9ba0496d523bb46f2c1c9fb73 (patch) | |
tree | f96100a813a4ffb28dcd074455d3a2f8ee426430 /source/30-game/main.cpp |
Copy from the PlasticSCM repo, replace vendored glm wtih conan
Diffstat (limited to 'source/30-game/main.cpp')
-rw-r--r-- | source/30-game/main.cpp | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/source/30-game/main.cpp b/source/30-game/main.cpp new file mode 100644 index 0000000..30ba9a6 --- /dev/null +++ b/source/30-game/main.cpp @@ -0,0 +1,545 @@ +#include "App.hpp" + +#include "AppConfig.hpp" +#include "CommonVertexIndex.hpp" +#include "ImGuiGuizmo.hpp" +#include "Input.hpp" +#include "Ires.hpp" +#include "Level.hpp" +#include "Log.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 GlfwKeyboardCallback(GLFWkeyboard* keyboard, int event) { + if (InputState::instance == nullptr) { + // Called before initialization, skipping because we'll do a collect pass anyways when initializing + return; + } + + switch (event) { + case GLFW_CONNECTED: { + InputState::instance->ConnectKeyboard(keyboard); + } break; + + case GLFW_DISCONNECTED: { + InputState::instance->DisconnectKeyboard(keyboard); + } break; + } +} + +void OpenGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message); +} + +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); + } +} + +// For platform data path selection below +// 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; + +#if BRUSSEL_DEV_ENV + Log::gDefaultBuffer.messages.resize(1024); + Log::gDefaultBufferId = Log::RegisterBuffer(Log::gDefaultBuffer); +#endif + + constexpr auto kOpenGLDebug = "opengl-debug"; + constexpr auto kImGuiBackend = "imgui-backend"; + constexpr auto kGameDataDir = "game-data-dir"; + constexpr auto kGameAssetDir = "game-asset-dir"; + + cxxopts::Options options(std::string(AppConfig::kAppName), ""); + // clang-format off + options.add_options() + (kOpenGLDebug, "Enable OpenGL debugging messages.") + (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 { + AppConfig::assetDir = "."; + AppConfig::assetDirPath = fs::path("."); + } + + 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 { +#if BRUSSEL_DEV_ENV + AppConfig::dataDir = "."; + AppConfig::dataDirPath = fs::path("."); +#else +// In a regular build, use default platform data paths +# if defined(_WIN32) + fs::path dataDirPath; + + PWSTR path = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &path); + if (SUCCEEDED(hr)) { + dataDirPath = fs::path(path) / AppConfig::kAppName; + CoTaskMemFree(path); + + fs::create_directories(dataDirPath); + } 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 dataDirPath = fs::path("~/Library/Application Support/") / AppConfig::kAppName; + fs::create_directories(dataDirPath); +# elif defined(__linux__) + auto dataDirPath = GetEnvVar("XDG_DATA_HOME", "~/.local/share") / AppConfig::kAppName; + fs::create_directories(dataDirPath); +# endif + AppConfig::dataDir = dataDirPath.string(); + AppConfig::dataDirPath = dataDirPath; +#endif + } + + if (!glfwInit()) { + return -1; + } + + glfwSetErrorCallback(&GlfwErrorCallback); + glfwSetKeyboardCallback(&GlfwKeyboardCallback); + + 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, AppConfig::kAppNameC, 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); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + return -3; + } + +#if defined(BRUSSEL_DEV_ENV) + auto glVersionString = glGetString(GL_VERSION); + + int glMajorVersion; + glGetIntegerv(GL_MAJOR_VERSION, &glMajorVersion); + int glMinorVersion; + glGetIntegerv(GL_MINOR_VERSION, &glMinorVersion); + + printf("OpenGL version (via glGetString(GL_VERSION)): %s\n", glVersionString); + printf("OpenGL version (via glGetIntegerv() with GL_MAJOR_VERSION and GL_MINOR_VERSION): %d.%d\n", glMajorVersion, glMinorVersion); +#endif + + bool useOpenGLDebug = args[kOpenGLDebug].as<bool>(); + if (useOpenGLDebug) { + printf("Using OpenGL debugging\n --%s", kOpenGLDebug); + + // TODO check extension KHR_debug availability + // TODO conan glad is not including any extensions + // NOTE: KHR_debug is a core extension, which means it may be available in lower version even though the feature is added in 4.3 + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(&OpenGLDebugCallback, 0); + } + + 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(); + } + + InputState::instance = new InputState(); + { + int count; + GLFWkeyboard** list = glfwGetKeyboards(&count); + for (int i = 0; i < count; ++i) { + GLFWkeyboard* keyboard = list[i]; + InputState::instance->ConnectKeyboard(keyboard); + } + } + + 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, + }); + + gVformatLines.Attach(new VertexFormat()); + gVformatLines->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .type = VET_Float3, + .semantic = VES_Position, + }); + gVformatLines->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .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; +} |