diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..032138d --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterUnion: true + BeforeCatch: false + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 0 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 0 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never diff --git a/SConstruct b/SConstruct index 7d1732c..ced104c 100644 --- a/SConstruct +++ b/SConstruct @@ -1,10 +1,14 @@ config = { - 'PROJECT_NAME': 'spp_template' + 'PROJECT_NAME': 'RAID Framework', + 'CXX_NO_EXCEPTIONS': True } env = SConscript('external/scons-plus-plus/SConscript', exports = ['config']) env.Append(CPPPATH = [Dir('private'), Dir('public')]) -# app -env = env.Module('private/spp_template/SModule') +# library +env = env.Module('private/raid/SModule') + +# test app +env = env.Module('private/raid_test/SModule') env.Finalize() diff --git a/external/scons-plus-plus b/external/scons-plus-plus index c883f3c..c994752 160000 --- a/external/scons-plus-plus +++ b/external/scons-plus-plus @@ -1 +1 @@ -Subproject commit c883f3c1c777357957e30fc44cf987e8bb62c818 +Subproject commit c994752c3244fdf9835b1d3a5238094d2a799855 diff --git a/private/raid/SModule b/private/raid/SModule new file mode 100644 index 0000000..31e1a2b --- /dev/null +++ b/private/raid/SModule @@ -0,0 +1,31 @@ + +Import('env') + +src_files = Split(""" + application.cpp +""") + +lib_raid = env.UnityStaticLibrary( + name = 'RAID', + target = env['LIB_DIR'] + '/raid', + source = src_files, + dependencies = { + 'fmt': {}, + 'mijin': {}, + 'imgui': { + 'options': { + 'docking': True, + 'backends': [ + 'sdl3', + 'opengl3' + ] + } + }, + 'SDL': { + 'min': (3,0,0) + } + } +) +env.Default(lib_raid) + +Return('env') diff --git a/private/raid/application.cpp b/private/raid/application.cpp new file mode 100644 index 0000000..11f8a05 --- /dev/null +++ b/private/raid/application.cpp @@ -0,0 +1,240 @@ + +#include "raid/raid.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace raid +{ +namespace +{ +constexpr int GL_COLOR_BUFFER_BIT = 0x00004000; +const char* IMGUI_GLSL_VERSION = "#version 130"; +} + +int Application::run(int argc, char** argv) +{ + (void) argc; + (void) argv; + + MIJIN_SCOPE_EXIT { + cleanup(); + }; + if (!initSDL()) + { + return ERR_INIT_FAILED; + } + if (!initGL()) + { + return ERR_INIT_FAILED; + } + if (!initImGui()) + { + return ERR_INIT_FAILED; + } + + while (mRunning) + { + handleSDLEvents(); + + if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MINIMIZED) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + ImGui::SetNextWindowPos({0, 0}); + ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + + ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration); + render(); + ImGui::End(); + + ImGui::Render(); + + glClearColor(0.3f, 0.3f, 0.3f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + SDL_GL_SwapWindow(mWindow); + } + + return 0; +} + +void Application::handleMessage(const Message& message) +{ + switch (message.severity) + { + case MessageSeverity::INFO: + fmt::print("INFO: {}", message.text); + break; + case MessageSeverity::WARNING: + fmt::print("WARNING: {}", message.text); + break; + case MessageSeverity::ERROR: + fmt::print(stderr, "ERROR: {}", message.text); + break; + } +} + +void Application::handleSDLEvent(const SDL_Event& event) +{ + switch (event.type) + { + case SDL_EVENT_QUIT: + mRunning = false; + return; + default: + ImGui_ImplSDL3_ProcessEvent(&event); + break; + } +} + +bool Application::initSDL() +{ + if (!SDL_Init(0)) + { + msgError("Error initializing SDL: {}.", SDL_GetError()); + return false; + } + + // GL attributes must be set before window creation + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + // TODO: not sure if these really make sense, but they are in the example + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + const SDL_WindowFlags WINDOW_FLAGS = 0 + | SDL_WINDOW_OPENGL + | SDL_WINDOW_RESIZABLE + | SDL_WINDOW_HIGH_PIXEL_DENSITY; + mWindow = SDL_CreateWindow( + /* title = */ "RAID", + /* w = */ 1280, + /* h = */ 720, + /* flags = */ WINDOW_FLAGS + ); + return true; +} + +bool Application::initGL() +{ + mGLContext = SDL_GL_CreateContext(mWindow); + if (mWindow == nullptr) + { + msgError("Error creating SDL window: {}.", SDL_GetError()); + return false; + } + SDL_GL_MakeCurrent(mWindow, mGLContext); + SDL_GL_SetSwapInterval(1); // enable vsync, at least for now + + glClear = reinterpret_cast(SDL_GL_GetProcAddress("glClear")); + glClearColor = reinterpret_cast(SDL_GL_GetProcAddress("glClearColor")); + + return true; +} + +bool Application::initImGui() +{ + IMGUI_CHECKVERSION(); // not exactly useful when using static libs, but won't hurt + + if (ImGui::CreateContext() == nullptr) + { + msgError("Error initializing ImGui context."); + return false; + } + + ImGuiIO& imguiIO = ImGui::GetIO(); + imguiIO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + imguiIO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + + // default style + ImGui::StyleColorsDark(); + + // init the backends + if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, mGLContext)) + { + msgError("Error initializing ImGui SDL3 backend."); + return false; + } + if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION)) + { + msgError("Error initializing ImGui OpenGL3 backend."); + return false; + } + + // init font + imguiIO.Fonts->AddFontDefault(); + + return true; +} + +void Application::cleanup() +{ + if (ImGui::GetCurrentContext() != nullptr) + { + const ImGuiIO& imguiIO = ImGui::GetIO(); + if (imguiIO.BackendRendererUserData != nullptr) + { + ImGui_ImplOpenGL3_Shutdown(); + } + if (imguiIO.BackendPlatformUserData != nullptr) + { + ImGui_ImplSDL3_Shutdown(); + } + ImGui::DestroyContext(); + } + if (mGLContext != nullptr) + { + SDL_GL_DestroyContext(mGLContext); + } + if (mWindow != nullptr) + { + SDL_DestroyWindow(mWindow); + } + SDL_Quit(); +} + +void Application::handleSDLEvents() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + handleSDLEvent(event); + } +} + +void QuickApp::init(QuickAppOptions options) +{ + MIJIN_ASSERT_FATAL(options.callbacks.render, "Missing render callback."); + mOptions = std::move(options); +} + +void QuickApp::render() +{ + mOptions.callbacks.render(); +} + +int runQuick(int argc, char* argv[], QuickAppOptions options) +{ + QuickApp app; + app.init(std::move(options)); + return app.run(argc, argv); +} +} diff --git a/private/spp_template/SModule b/private/raid_test/SModule similarity index 62% rename from private/spp_template/SModule rename to private/raid_test/SModule index 18935e7..8e55386 100644 --- a/private/spp_template/SModule +++ b/private/raid_test/SModule @@ -6,11 +6,12 @@ src_files = Split(""" """) prog_app = env.UnityProgram( - name = 'Template', - target = env['BIN_DIR'] + '/spp_template', + name = 'Test App', + target = env['BIN_DIR'] + '/raid_test', source = src_files, dependencies = { - 'mijin': {} + 'mijin': {}, + 'RAID': {} } ) env.Default(prog_app) diff --git a/private/raid_test/main.cpp b/private/raid_test/main.cpp new file mode 100644 index 0000000..a24bac5 --- /dev/null +++ b/private/raid_test/main.cpp @@ -0,0 +1,21 @@ + +#include "raid/raid.hpp" + +#include + +namespace +{ +void render() +{ + ImGui::Text("hi"); +} +} + +int main(int argc, char* argv[]) +{ + return raid::runQuick(argc, argv, { + .callbacks = { + .render = &render + } + }); +} diff --git a/private/spp_template/main.cpp b/private/spp_template/main.cpp deleted file mode 100644 index e9ad257..0000000 --- a/private/spp_template/main.cpp +++ /dev/null @@ -1,5 +0,0 @@ - -int main(int, char**) -{ - return 0; -} diff --git a/public/raid/raid.hpp b/public/raid/raid.hpp new file mode 100644 index 0000000..a937fda --- /dev/null +++ b/public/raid/raid.hpp @@ -0,0 +1,133 @@ + +#pragma once + +#if !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED) +#define RAID_PUBLIC_RAID_RAID_HPP_INCLUDED 1 + +#include +#include +#include +#include + +namespace raid +{ +inline constexpr int ERR_INIT_FAILED = 100; + +enum class MessageSeverity : unsigned char +{ + INFO, + WARNING, + ERROR +}; + +struct Message +{ + MessageSeverity severity; + const char* text; +}; + +class Application +{ +private: + bool mRunning = true; + SDL_Window* mWindow = nullptr; + SDL_GLContext mGLContext = nullptr; + + using glClear_fn_t = void (*)(std::uint32_t); + using glClearColor_fn_t = void (*)(float, float, float, float); + + glClear_fn_t glClear; + glClearColor_fn_t glClearColor; +public: + virtual ~Application() = default; + + [[nodiscard]] + int run(int argc, char* argv[]); + +protected: + virtual void render() = 0; + virtual void handleMessage(const Message& message); + virtual void handleSDLEvent(const SDL_Event& event); + + void msgInfo(const char* text) + { + handleMessage({ + .severity = MessageSeverity::INFO, + .text = text + }); + } + void msgWarning(const char* text) + { + handleMessage({ + .severity = MessageSeverity::WARNING, + .text = text + }); + } + void msgError(const char* text) + { + handleMessage({ + .severity = MessageSeverity::ERROR, + .text = text + }); + } + void msgInfo(const std::string& text) + { + msgInfo(text.c_str()); + } + void msgWarning(const std::string& text) + { + msgWarning(text.c_str()); + } + void msgError(const std::string& text) + { + msgError(text.c_str()); + } + template + void msgInfo(fmt::format_string format, TArg&& arg, TArgs&&... args) + { + std::string text = fmt::format(format, std::forward(arg), std::forward(args)...); + msgInfo(text); + } + template + void msgWarning(fmt::format_string format, TArg&& arg, TArgs&&... args) + { + std::string text = fmt::format(format, std::forward(arg), std::forward(args)...); + msgWarning(text); + } + template + void msgError(fmt::format_string format, TArg&& arg, TArgs&&... args) + { + std::string text = fmt::format(format, std::forward(arg), std::forward(args)...); + msgError(text); + } +private: + bool initSDL(); + bool initGL(); + bool initImGui(); + void cleanup(); + void handleSDLEvents(); +}; + +using render_cb_t = std::function; +struct QuickAppOptions +{ + struct + { + render_cb_t render; + } callbacks; +}; + +class QuickApp : public Application +{ +private: + QuickAppOptions mOptions; +public: + void init(QuickAppOptions options); + void render() override; +}; + +[[nodiscard]] +int runQuick(int argc, char* argv[], QuickAppOptions options); +} // namespace raid + +#endif // !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)