Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
e354e20e54 | |||
![]() |
d56918e71d | ||
![]() |
69fdae991a | ||
da8d1589b3 | |||
![]() |
9d02d97af3 | ||
![]() |
20020318e1 | ||
![]() |
9bc56a2748 | ||
5ebd609507 | |||
b38fa1b68b | |||
41258cda5b | |||
ee4ea4ae0a | |||
b35edfcf20 | |||
180f2b70fa | |||
917309e99c | |||
66668959d3 | |||
58329a2033 | |||
![]() |
0f2f5973fb | ||
![]() |
6657606e81 | ||
bc8d241592 | |||
db09eb5e3a | |||
![]() |
d3b56d3fd0 | ||
![]() |
7470878f9c | ||
d74474a042 | |||
71a268b5ad | |||
63fcb6181f | |||
6cf748b05d | |||
![]() |
d1addb438c | ||
aef6eb03d2 | |||
8d1a7bc957 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -28,6 +28,10 @@ compile_commands.json
|
|||||||
/config.py
|
/config.py
|
||||||
/config_*.py
|
/config_*.py
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.gen.*
|
||||||
|
!*.gen.*.jinja
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
*.d
|
*.d
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "scons-plus-plus"]
|
|
||||||
path = external/scons-plus-plus
|
|
||||||
url = https://git.mewin.de/mewin/scons-plus-plus.git
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Patrick Wuttke
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -1,10 +1,12 @@
|
|||||||
config = {
|
config = {
|
||||||
'PROJECT_NAME': 'RAID Framework',
|
'PROJECT_NAME': 'RAID Framework'
|
||||||
'CXX_NO_EXCEPTIONS': True
|
|
||||||
}
|
}
|
||||||
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
|
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
|
||||||
env.Append(CPPPATH = [Dir('private'), Dir('public')])
|
env.Append(CPPPATH = [Dir('private'), Dir('public')])
|
||||||
|
|
||||||
|
# add the default recipe repository
|
||||||
|
env.RecipeRepo('mewin', 'https://git.mewin.de/mewin/spp_recipes.git', 'stable')
|
||||||
|
|
||||||
# library
|
# library
|
||||||
env = env.Module('private/raid/SModule')
|
env = env.Module('private/raid/SModule')
|
||||||
|
|
||||||
|
3
SModule
3
SModule
@ -3,7 +3,8 @@ Import('env')
|
|||||||
|
|
||||||
public_dir = env.Dir('public')
|
public_dir = env.Dir('public')
|
||||||
env.Append(CPPPATH = [public_dir])
|
env.Append(CPPPATH = [public_dir])
|
||||||
if env['BUILD_TYPE'] == 'release':
|
env.Append(JINJA_FILE_SEARCHPATH = [env.Dir('.')])
|
||||||
|
if env['BUILD_TYPE'] in ('release', 'release_debug', 'profile'):
|
||||||
env.Append(CPPDEFINES = ['RAID_RELEASE'])
|
env.Append(CPPDEFINES = ['RAID_RELEASE'])
|
||||||
env = env.Module('private/raid/SModule')
|
env = env.Module('private/raid/SModule')
|
||||||
|
|
||||||
|
@ -13,5 +13,6 @@
|
|||||||
"SDL": {
|
"SDL": {
|
||||||
"min": [3,0,0]
|
"min": [3,0,0]
|
||||||
},
|
},
|
||||||
"stb": {}
|
"stb": {},
|
||||||
|
"yaml-cpp": {}
|
||||||
}
|
}
|
||||||
|
1
external/scons-plus-plus
vendored
1
external/scons-plus-plus
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit c994752c3244fdf9835b1d3a5238094d2a799855
|
|
84
external/scons-plus-plus/SConscript
vendored
Normal file
84
external/scons-plus-plus/SConscript
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
SCons++ Bootstrapper
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from SCons.Environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
Import('config')
|
||||||
|
|
||||||
|
|
||||||
|
_SPP_FOLDER_NAME = 'scons-plus-plus'
|
||||||
|
_SPP_DEFAULT_REPOSITORY = 'https://git.mewin.de/mewin/scons-plus-plus.git'
|
||||||
|
_SPP_DEFAULT_BRANCH = 'master'
|
||||||
|
|
||||||
|
|
||||||
|
spp_root: Path
|
||||||
|
spp_repository: str
|
||||||
|
spp_branch: str
|
||||||
|
|
||||||
|
def _main() -> Environment:
|
||||||
|
global spp_root, spp_repository, spp_branch
|
||||||
|
|
||||||
|
spp_root = config.get('SPP_ROOT')
|
||||||
|
if spp_root is None:
|
||||||
|
spp_root = _get_default_spp_root()
|
||||||
|
elif not isinstance(spp_root, Path):
|
||||||
|
spp_root = Path(str(spp_root))
|
||||||
|
spp_root = spp_root.absolute()
|
||||||
|
|
||||||
|
spp_repository = config.get('SPP_REPOSITORY', _SPP_DEFAULT_REPOSITORY)
|
||||||
|
spp_branch = config.get('SPP_BRANCH', _SPP_DEFAULT_BRANCH)
|
||||||
|
|
||||||
|
_printinfo(f'Using SCons++ root at: {spp_root}')
|
||||||
|
|
||||||
|
if not spp_root.exists():
|
||||||
|
_printinfo('SCons++ does not yet exist, downloading it.')
|
||||||
|
_install_spp()
|
||||||
|
|
||||||
|
spp_script = spp_root / 'SConscript'
|
||||||
|
if not spp_script.exists():
|
||||||
|
_printerr(f'SCons++ main script not found at {spp_script}!')
|
||||||
|
sys.exit(1)
|
||||||
|
return SConscript(spp_script, exports=['config'])
|
||||||
|
|
||||||
|
def _get_default_spp_root() -> Path:
|
||||||
|
if os.name == 'posix':
|
||||||
|
# follow XDG specification -> first try $XDG_DATA_HOME, then $HOME/.local/share
|
||||||
|
data_home = os.environ.get('XDG_DATA_HOME')
|
||||||
|
if data_home is not None:
|
||||||
|
return Path(data_home, _SPP_FOLDER_NAME)
|
||||||
|
home = os.environ.get('HOME')
|
||||||
|
if home is not None:
|
||||||
|
return Path(home, '.local', 'share', _SPP_FOLDER_NAME)
|
||||||
|
elif os.name == 'nt':
|
||||||
|
# just use LocalAppData, which should always be set on Windows
|
||||||
|
return Path(os.environ['LocalAppData'], _SPP_FOLDER_NAME)
|
||||||
|
_printinfo(f'Could not detect SCons++ root directory, falling back to ./{_SPP_FOLDER_NAME}.')
|
||||||
|
return Path(_SPP_FOLDER_NAME)
|
||||||
|
|
||||||
|
def _install_spp() -> None:
|
||||||
|
git_exe = shutil.which('git')
|
||||||
|
if git_exe is None:
|
||||||
|
_printerr('No git executable found, cannot install SCons++.')
|
||||||
|
sys.exit(1)
|
||||||
|
_exec_checked((git_exe, 'clone', '-b', spp_branch, '--progress', spp_repository, spp_root))
|
||||||
|
|
||||||
|
def _exec_checked(args: Sequence[str], **kwargs) -> None:
|
||||||
|
subprocess.run(args, stdout=sys.stdout, stderr=sys.stderr, check=True, **kwargs)
|
||||||
|
|
||||||
|
if not GetOption('silent'):
|
||||||
|
_printinfo = print
|
||||||
|
else:
|
||||||
|
def _printinfo(*args): ...
|
||||||
|
def _printerr(*args) -> None:
|
||||||
|
print(*args, file=sys.stderr)
|
||||||
|
|
||||||
|
env = _main()
|
||||||
|
Return('env')
|
@ -3,11 +3,19 @@ import json
|
|||||||
|
|
||||||
Import('env')
|
Import('env')
|
||||||
|
|
||||||
|
if not hasattr(env, 'Jinja'):
|
||||||
|
env.Error('RAID requires Jinja.')
|
||||||
|
|
||||||
src_files = Split("""
|
src_files = Split("""
|
||||||
application.cpp
|
application.cpp
|
||||||
|
config.cpp
|
||||||
|
fonts.gen.cpp
|
||||||
stb_image.cpp
|
stb_image.cpp
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
env.Jinja("fonts.gen.cpp")
|
||||||
|
env.Jinja("fonts.gen.hpp")
|
||||||
|
|
||||||
with open('../../dependencies.json', 'r') as f:
|
with open('../../dependencies.json', 'r') as f:
|
||||||
dependencies = env.DepsFromJson(json.load(f))
|
dependencies = env.DepsFromJson(json.load(f))
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
#include "raid/raid.hpp"
|
#include "raid/application.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <fmt/base.h>
|
#include <fmt/base.h>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_vulkan.h>
|
||||||
|
#include <mijin/detect.hpp>
|
||||||
#include <mijin/debug/assert.hpp>
|
#include <mijin/debug/assert.hpp>
|
||||||
#include <mijin/platform/folders.hpp>
|
#include <mijin/platform/folders.hpp>
|
||||||
#include <mijin/util/iterators.hpp>
|
#include <mijin/util/iterators.hpp>
|
||||||
@ -12,9 +14,11 @@
|
|||||||
#include <mijin/virtual_filesystem/mapping.hpp>
|
#include <mijin/virtual_filesystem/mapping.hpp>
|
||||||
#include <mijin/virtual_filesystem/relative.hpp>
|
#include <mijin/virtual_filesystem/relative.hpp>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <imgui_internal.h>
|
||||||
#include <backends/imgui_impl_opengl3.h>
|
#include <backends/imgui_impl_opengl3.h>
|
||||||
#include <backends/imgui_impl_sdl3.h>
|
#include <backends/imgui_impl_sdl3.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
|
#include "./fonts.gen.hpp"
|
||||||
|
|
||||||
namespace raid
|
namespace raid
|
||||||
{
|
{
|
||||||
@ -59,6 +63,12 @@ int stbiEof(void* user)
|
|||||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||||
return stream.isAtEnd();
|
return stream.isAtEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr std::uint32_t vkMakeApiVersion(std::uint32_t variant, std::uint32_t major, std::uint32_t minor, std::uint32_t patch) noexcept
|
||||||
|
{
|
||||||
|
return ((variant << 29U) | (major << 22U) | (minor << 12U) | patch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Application::run(int argc, char** argv)
|
int Application::run(int argc, char** argv)
|
||||||
@ -94,8 +104,16 @@ int Application::run(int argc, char** argv)
|
|||||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos);
|
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos);
|
||||||
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
|
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
|
||||||
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
|
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
|
||||||
|
|
||||||
|
for (const auto& [variable, value] : mMainWindowStyles) {
|
||||||
|
std::visit([&](auto val) {
|
||||||
|
ImGui::PushStyleVar(variable, val);
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Begin("##main", nullptr, mMainWindowFlags);
|
ImGui::Begin("##main", nullptr, mMainWindowFlags);
|
||||||
|
ImGui::PopStyleVar(static_cast<int>(mMainWindowStyles.size()));
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|
||||||
mTaskLoop.tick();
|
mTaskLoop.tick();
|
||||||
@ -103,15 +121,26 @@ int Application::run(int argc, char** argv)
|
|||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
||||||
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
switch (mConfig.graphicsApi)
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
{
|
||||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
case GraphicsAPI::OPENGL:
|
||||||
|
gl.ClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
||||||
|
gl.Clear(GL_COLOR_BUFFER_BIT);
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
break;
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
MIJIN_TRAP(); // TODO!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (imguiIO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
if (imguiIO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||||
{
|
{
|
||||||
ImGui::UpdatePlatformWindows();
|
ImGui::UpdatePlatformWindows();
|
||||||
ImGui::RenderPlatformWindowsDefault();
|
ImGui::RenderPlatformWindowsDefault();
|
||||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
|
||||||
|
if (mConfig.graphicsApi == GraphicsAPI::OPENGL) {
|
||||||
|
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GL_SwapWindow(mWindow);
|
SDL_GL_SwapWindow(mWindow);
|
||||||
@ -212,23 +241,60 @@ ImTextureID Application::getOrLoadTexture(fs::path path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create texture
|
// create texture
|
||||||
GLuint texture = 0;
|
ImTextureID texture = 0;
|
||||||
glGenTextures(1, &texture);
|
switch (mConfig.graphicsApi)
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
{
|
||||||
|
case GraphicsAPI::OPENGL:
|
||||||
|
{
|
||||||
|
GLuint glTexture = 0;
|
||||||
|
gl.GenTextures(1, &glTexture);
|
||||||
|
gl.BindTexture(GL_TEXTURE_2D, glTexture);
|
||||||
|
|
||||||
// setup texture
|
// setup texture
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
// upload image
|
// upload image
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
|
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
|
||||||
|
|
||||||
mTextures.emplace(std::move(path), texture);
|
texture = glTexture;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
MIJIN_TRAP(); // TODO!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture != 0) {
|
||||||
|
mTextures.emplace(std::move(path), texture);
|
||||||
|
}
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::destroyTexture(ImTextureID texture)
|
||||||
|
{
|
||||||
|
auto it = std::ranges::find_if(mTextures, [texture](const auto& entry) {
|
||||||
|
return entry.second == texture;
|
||||||
|
});
|
||||||
|
MIJIN_ASSERT(it != mTextures.end(), "Invalid texture id.");
|
||||||
|
mTextures.erase(it);
|
||||||
|
|
||||||
|
switch (mConfig.graphicsApi)
|
||||||
|
{
|
||||||
|
case GraphicsAPI::OPENGL:
|
||||||
|
{
|
||||||
|
const GLuint asUint = static_cast<GLuint>(texture);
|
||||||
|
gl.DeleteTextures(1, &asUint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
MIJIN_TRAP(); // TODO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::configureImgui()
|
void Application::configureImgui()
|
||||||
{
|
{
|
||||||
ImGuiIO& imguiIO = ImGui::GetIO();
|
ImGuiIO& imguiIO = ImGui::GetIO();
|
||||||
@ -241,10 +307,15 @@ void Application::configureImgui()
|
|||||||
std::vector<FontConfig> Application::getDefaultFonts()
|
std::vector<FontConfig> Application::getDefaultFonts()
|
||||||
{
|
{
|
||||||
return {{
|
return {{
|
||||||
.path = "/data/fonts/NotoSans-Regular.ttf"
|
.path = DEFAULT_FONT_PATH
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::initMemoryFS()
|
||||||
|
{
|
||||||
|
mMemoryFS->addFile(DEFAULT_FONT_PATH, NOTO_SANS_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::handleMessage(const Message& message)
|
void Application::handleMessage(const Message& message)
|
||||||
{
|
{
|
||||||
switch (message.severity)
|
switch (message.severity)
|
||||||
@ -268,12 +339,29 @@ void Application::handleSDLEvent(const SDL_Event& event)
|
|||||||
case SDL_EVENT_QUIT:
|
case SDL_EVENT_QUIT:
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
return;
|
return;
|
||||||
|
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||||
|
if (SDL_GetWindowFromID(event.window.windowID) == mWindow) {
|
||||||
|
handleCloseRequested();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ImGui_ImplSDL3_ProcessEvent(&event);
|
ImGui_ImplSDL3_ProcessEvent(&event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::handleSDLError(const char* message)
|
||||||
|
{
|
||||||
|
msgError("SDL: {}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::handleCloseRequested()
|
||||||
|
{
|
||||||
|
SDL_Event quitEvent;
|
||||||
|
quitEvent.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&quitEvent);
|
||||||
|
}
|
||||||
|
|
||||||
bool Application::init()
|
bool Application::init()
|
||||||
{
|
{
|
||||||
auto addConfigDir = [&](const fs::path& path)
|
auto addConfigDir = [&](const fs::path& path)
|
||||||
@ -294,6 +382,9 @@ bool Application::init()
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mMemoryFS = mFS.emplaceAdapter<mijin::MemoryFileSystemAdapter>();
|
||||||
|
initMemoryFS();
|
||||||
|
|
||||||
addConfigDir(mijin::getKnownFolder(mijin::KnownFolder::USER_CONFIG_ROOT) / getFolderName());
|
addConfigDir(mijin::getKnownFolder(mijin::KnownFolder::USER_CONFIG_ROOT) / getFolderName());
|
||||||
addDataDir(mijin::getKnownFolder(mijin::KnownFolder::USER_DATA_ROOT) / getFolderName());
|
addDataDir(mijin::getKnownFolder(mijin::KnownFolder::USER_DATA_ROOT) / getFolderName());
|
||||||
|
|
||||||
@ -305,7 +396,7 @@ bool Application::init()
|
|||||||
const fs::path path = std::move(*pathOpt);
|
const fs::path path = std::move(*pathOpt);
|
||||||
if (!fs::exists(path))
|
if (!fs::exists(path))
|
||||||
{
|
{
|
||||||
const bool result = fs::create_directories(path);
|
[[maybe_unused]] const bool result = fs::create_directories(path);
|
||||||
MIJIN_ASSERT(result, "Error creating user folder.");
|
MIJIN_ASSERT(result, "Error creating user folder.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,7 +409,7 @@ bool Application::init()
|
|||||||
createUserDir("/data");
|
createUserDir("/data");
|
||||||
|
|
||||||
// in development builds, also add the development folders
|
// in development builds, also add the development folders
|
||||||
#if !defined(RAID_RELEASE)
|
#if !defined(RAID_RELEASE) || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS)
|
||||||
addConfigDir(fs::current_path() / "data/config");
|
addConfigDir(fs::current_path() / "data/config");
|
||||||
addDataDir(fs::current_path() / "data/data");
|
addDataDir(fs::current_path() / "data/data");
|
||||||
#endif
|
#endif
|
||||||
@ -355,9 +446,20 @@ bool Application::init()
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!initGL())
|
switch (mConfig.graphicsApi)
|
||||||
{
|
{
|
||||||
return false;
|
case GraphicsAPI::OPENGL:
|
||||||
|
if (!initOpenGL())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
if (!initVulkan())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!initImGui())
|
if (!initImGui())
|
||||||
{
|
{
|
||||||
@ -383,14 +485,33 @@ void Application::cleanup()
|
|||||||
}
|
}
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
}
|
}
|
||||||
for (const auto& [path, texture] : mTextures)
|
for (const auto& [_, texture] : mTextures) {
|
||||||
{
|
destroyTexture(texture);
|
||||||
const GLuint asUint = static_cast<GLuint>(texture);
|
|
||||||
glDeleteTextures(1, &asUint);
|
|
||||||
}
|
}
|
||||||
if (mGLContext != nullptr)
|
switch (mConfig.graphicsApi)
|
||||||
{
|
{
|
||||||
SDL_GL_DestroyContext(mGLContext);
|
case GraphicsAPI::OPENGL:
|
||||||
|
if (gl.context != nullptr)
|
||||||
|
{
|
||||||
|
SDL_GL_DestroyContext(gl.context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
if (vk.device != nullptr)
|
||||||
|
{
|
||||||
|
vk.DeviceWaitIdle(vk.device);
|
||||||
|
vk.DestroyDevice(vk.device, nullptr);
|
||||||
|
}
|
||||||
|
if (vk.debugUtilsMessenger != nullptr)
|
||||||
|
{
|
||||||
|
vk.DestroyDebugUtilsMessengerEXT(vk.instance, vk.debugUtilsMessenger, /* pAllocator = */ nullptr);
|
||||||
|
}
|
||||||
|
if (vk.instance != nullptr)
|
||||||
|
{
|
||||||
|
vk.DestroyInstance(vk.instance, nullptr);
|
||||||
|
}
|
||||||
|
SDL_Vulkan_UnloadLibrary();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (mWindow != nullptr)
|
if (mWindow != nullptr)
|
||||||
{
|
{
|
||||||
@ -401,54 +522,260 @@ void Application::cleanup()
|
|||||||
|
|
||||||
bool Application::initSDL()
|
bool Application::initSDL()
|
||||||
{
|
{
|
||||||
if (!SDL_Init(0))
|
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||||
|
if (mConfig.flags.x11OnWayland) {
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||||
{
|
{
|
||||||
msgError("Error initializing SDL: {}.", SDL_GetError());
|
msgError("Error initializing SDL: {}.", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GL attributes must be set before window creation
|
SDL_SetHint(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "0"); // let us handle ourselves
|
||||||
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
|
msgInfo("SDL video driver: {}", SDL_GetCurrentVideoDriver());
|
||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
SDL_WindowFlags windowFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||||
|
switch (mConfig.graphicsApi)
|
||||||
|
{
|
||||||
|
case GraphicsAPI::OPENGL:
|
||||||
|
// 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);
|
||||||
|
windowFlags |= SDL_WINDOW_OPENGL;
|
||||||
|
break;
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
if (!SDL_Vulkan_LoadLibrary(nullptr))
|
||||||
|
{
|
||||||
|
msgError("Error loading Vulkan library: {}.", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
windowFlags |= SDL_WINDOW_VULKAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const SDL_WindowFlags WINDOW_FLAGS = 0
|
|
||||||
| SDL_WINDOW_OPENGL
|
|
||||||
| SDL_WINDOW_RESIZABLE
|
|
||||||
| SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
|
||||||
mWindow = SDL_CreateWindow(
|
mWindow = SDL_CreateWindow(
|
||||||
/* title = */ getWindowTitle().c_str(),
|
/* title = */ getWindowTitle().c_str(),
|
||||||
/* w = */ 1280,
|
/* w = */ 1280,
|
||||||
/* h = */ 720,
|
/* h = */ 720,
|
||||||
/* flags = */ WINDOW_FLAGS
|
/* flags = */ windowFlags
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::initGL()
|
bool Application::initOpenGL()
|
||||||
{
|
{
|
||||||
mGLContext = SDL_GL_CreateContext(mWindow);
|
gl.context = SDL_GL_CreateContext(mWindow);
|
||||||
if (mWindow == nullptr)
|
if (mWindow == nullptr)
|
||||||
{
|
{
|
||||||
msgError("Error creating SDL window: {}.", SDL_GetError());
|
msgError("Error creating OpenGL context: {}.", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||||
SDL_GL_SetSwapInterval(1); // enable vsync, at least for now
|
SDL_GL_SetSwapInterval(1); // enable vsync, at least for now
|
||||||
|
|
||||||
glClear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
gl.Clear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
||||||
glClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
gl.ClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
||||||
glGenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
gl.GenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
||||||
glBindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
gl.BindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
||||||
glTexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
gl.TexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
||||||
glPixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
gl.PixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
||||||
glTexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
gl.TexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
||||||
glDeleteTextures = reinterpret_cast<glDeleteTextures_fn_t>(SDL_GL_GetProcAddress("glDeleteTextures"));
|
gl.DeleteTextures = reinterpret_cast<glDeleteTextures_fn_t>(SDL_GL_GetProcAddress("glDeleteTextures"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::initVulkan()
|
||||||
|
{
|
||||||
|
vk.GetInstanceProc = reinterpret_cast<vkGetInstanceProcAddr_fn_t>(SDL_Vulkan_GetVkGetInstanceProcAddr());
|
||||||
|
vk.EnumerateInstanceLayerProperties = reinterpret_cast<vkEnumerateInstanceLayerProperties_fn_t>(vk.GetInstanceProc(nullptr, "vkEnumerateInstanceLayerProperties"));
|
||||||
|
vk.EnumerateInstanceExtensionProperties = reinterpret_cast<vkEnumerateInstanceExtensionProperties_fn_t>(vk.GetInstanceProc(nullptr, "vkEnumerateInstanceExtensionProperties"));
|
||||||
|
vk.CreateInstance = reinterpret_cast<vkCreateInstance_fn_t>(vk.GetInstanceProc(nullptr, "vkCreateInstance"));
|
||||||
|
|
||||||
|
std::uint32_t numInstanceLayers = 0;
|
||||||
|
if (const VkResult result = vk.EnumerateInstanceLayerProperties(&numInstanceLayers, nullptr); result != VK_SUCCESS && result != VK_INCOMPLETE)
|
||||||
|
{
|
||||||
|
msgError("Error enumerating instance layers: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<VkLayerProperties> instanceLayers;
|
||||||
|
instanceLayers.resize(numInstanceLayers);
|
||||||
|
if (const VkResult result = vk.EnumerateInstanceLayerProperties(&numInstanceLayers, instanceLayers.data()); result != VK_SUCCESS && result != VK_INCOMPLETE)
|
||||||
|
{
|
||||||
|
msgError("Error enumerating instance layers: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const char*> enabledInstanceLayers;
|
||||||
|
for (const VkLayerProperties& props : instanceLayers) {
|
||||||
|
if (std::strcmp(props.layerName, "VK_LAYER_KHRONOS_validation") == 0) {
|
||||||
|
enabledInstanceLayers.push_back("VK_LAYER_KHRONOS_validation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t numInstanceExtensions = 0;
|
||||||
|
if (const VkResult result = vk.EnumerateInstanceExtensionProperties(nullptr, &numInstanceExtensions, nullptr); result != VK_SUCCESS && result != VK_INCOMPLETE)
|
||||||
|
{
|
||||||
|
msgError("Error enumerating instance extensions: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<VkExtensionProperties> instanceExtensions;
|
||||||
|
instanceExtensions.resize(numInstanceExtensions);
|
||||||
|
if (const VkResult result = vk.EnumerateInstanceExtensionProperties(nullptr, &numInstanceExtensions, instanceExtensions.data()); result != VK_SUCCESS && result != VK_INCOMPLETE)
|
||||||
|
{
|
||||||
|
msgError("Error enumerating instance extensions: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t numSdlExtensions = 0;
|
||||||
|
const char* const* sdlExtensions = SDL_Vulkan_GetInstanceExtensions(&numSdlExtensions);
|
||||||
|
std::uint32_t numSupportedSdlExtensions = 0;
|
||||||
|
|
||||||
|
bool hasDebugUtils = false;
|
||||||
|
std::vector<const char*> enabledInstanceExtensions;
|
||||||
|
for (const VkExtensionProperties& props : instanceExtensions) {
|
||||||
|
if (std::strcmp(props.extensionName, "VK_EXT_debug_utils") == 0)
|
||||||
|
{
|
||||||
|
enabledInstanceExtensions.push_back("VK_EXT_debug_utils");
|
||||||
|
hasDebugUtils = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (std::uint32_t idx = 0; idx < numSdlExtensions; ++idx)
|
||||||
|
{
|
||||||
|
if (std::strncmp(props.extensionName, sdlExtensions[idx], VK_MAX_EXTENSION_NAME_SIZE) == 0)
|
||||||
|
{
|
||||||
|
enabledInstanceExtensions.push_back(sdlExtensions[idx]);
|
||||||
|
++numSupportedSdlExtensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numSupportedSdlExtensions != numSdlExtensions)
|
||||||
|
{
|
||||||
|
msgError("Cannot create Vulkan device, not all required instance extensions are supported.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkApplicationInfo applicationInfo = {
|
||||||
|
.apiVersion = vkMakeApiVersion(0, 1, 3, 0) // TODO: probably should let the user specify this?
|
||||||
|
};
|
||||||
|
const VkInstanceCreateInfo instanceCreateInfo = {
|
||||||
|
.pApplicationInfo = &applicationInfo,
|
||||||
|
.enabledLayerCount = static_cast<std::uint32_t>(enabledInstanceLayers.size()),
|
||||||
|
.ppEnabledLayerNames = enabledInstanceLayers.data(),
|
||||||
|
.enabledExtensionCount = static_cast<std::uint32_t>(enabledInstanceExtensions.size()),
|
||||||
|
.ppEnabledExtensionNames = enabledInstanceExtensions.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const VkResult result = vk.CreateInstance(&instanceCreateInfo, nullptr, &vk.instance); result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
msgError("Error creating Vulkan instance: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk.DestroyInstance = reinterpret_cast<vkDestroyInstance_fn_t>(vk.GetInstanceProc(vk.instance, "vkDestroyInstance"));
|
||||||
|
vk.EnumeratePhysicalDevices = reinterpret_cast<vkEnumeratePhysicalDevices_fn_t>(vk.GetInstanceProc(vk.instance, "vkEnumeratePhysicalDevices"));
|
||||||
|
vk.GetPhysicalDeviceQueueFamilyProperties2 = reinterpret_cast<vkGetPhysicalDeviceQueueFamilyProperties2_fn_t>(vk.GetInstanceProc(vk.instance, "vkGetPhysicalDeviceQueueFamilyProperties2"));
|
||||||
|
vk.CreateDevice = reinterpret_cast<vkCreateDevice_fn_t>(vk.GetInstanceProc(vk.instance, "vkCreateDevice"));
|
||||||
|
vk.DestroyDevice = reinterpret_cast<vkDestroyDevice_fn_t>(vk.GetInstanceProc(vk.instance, "vkDestroyDevice"));
|
||||||
|
vk.DeviceWaitIdle = reinterpret_cast<vkDeviceWaitIdle_fn_t>(vk.GetInstanceProc(vk.instance, "vkDeviceWaitIdle"));
|
||||||
|
vk.GetDeviceQueue = reinterpret_cast<vkGetDeviceQueue_fn_t>(vk.GetInstanceProc(vk.instance, "vkGetDeviceQueue"));
|
||||||
|
|
||||||
|
if (hasDebugUtils)
|
||||||
|
{
|
||||||
|
vk.CreateDebugUtilsMessengerEXT = reinterpret_cast<vkCreateDebugUtilsMessengerEXT_fn_t>(vk.GetInstanceProc(vk.instance, "vkCreateDebugUtilsMessengerEXT"));
|
||||||
|
vk.DestroyDebugUtilsMessengerEXT = reinterpret_cast<vkDestroyDebugUtilsMessengerEXT_fn_t>(vk.GetInstanceProc(vk.instance, "vkDestroyDebugUtilsMessengerEXT"));
|
||||||
|
|
||||||
|
const VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {
|
||||||
|
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||||||
|
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||||||
|
.pfnUserCallback = [](VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
|
||||||
|
return static_cast<Application*>(pUserData)->handleDebugUtilsMessage(messageSeverity, messageTypes, pCallbackData);
|
||||||
|
},
|
||||||
|
.pUserData = this
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const VkResult result = vk.CreateDebugUtilsMessengerEXT(vk.instance, &messengerCreateInfo, /* pAllocator = */ nullptr, &vk.debugUtilsMessenger); result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
msgWarning("Error creating Vulkan debug utils messenger: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
vk.debugUtilsMessenger = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vk.debugUtilsMessenger = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is really cheap... (but should be sufficient in most cases)
|
||||||
|
std::uint32_t physicalDeviceCount = 1;
|
||||||
|
if (const VkResult result = vk.EnumeratePhysicalDevices(vk.instance, &physicalDeviceCount, &vk.physicalDevice); result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
msgError("Error enumerating Vulkan physical devices: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t queueFamilyPropertyCount = 0;
|
||||||
|
vk.GetPhysicalDeviceQueueFamilyProperties2(vk.physicalDevice, &queueFamilyPropertyCount, nullptr);
|
||||||
|
|
||||||
|
std::vector<VkQueueFamilyProperties2> queueFamilyProperties;
|
||||||
|
queueFamilyProperties.resize(queueFamilyPropertyCount);
|
||||||
|
vk.GetPhysicalDeviceQueueFamilyProperties2(vk.physicalDevice, &queueFamilyPropertyCount, queueFamilyProperties.data());
|
||||||
|
|
||||||
|
// TODO: this should also check for surface support (but who cares?)
|
||||||
|
std::uint32_t queueFamilyIndex = 0;
|
||||||
|
for (; queueFamilyIndex < queueFamilyPropertyCount; ++queueFamilyIndex)
|
||||||
|
{
|
||||||
|
const bool supportsGraphics = queueFamilyProperties[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT;
|
||||||
|
const bool supportsPresent = SDL_Vulkan_GetPresentationSupport(vk.instance, vk.physicalDevice, queueFamilyIndex);
|
||||||
|
|
||||||
|
if (supportsGraphics && supportsPresent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queueFamilyIndex == queueFamilyPropertyCount)
|
||||||
|
{
|
||||||
|
msgError("No suitable Vulkan queue family found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vk.queueFamilyIndex = queueFamilyIndex;
|
||||||
|
|
||||||
|
static const float queuePriority = 1.0f;
|
||||||
|
const VkDeviceQueueCreateInfo queueCreateInfo = {
|
||||||
|
.queueFamilyIndex = vk.queueFamilyIndex,
|
||||||
|
.queueCount = 1,
|
||||||
|
.pQueuePriorities = &queuePriority
|
||||||
|
};
|
||||||
|
static const std::array DEVICE_EXTENSIONS = {
|
||||||
|
"VK_KHR_swapchain"
|
||||||
|
};
|
||||||
|
const VkDeviceCreateInfo deviceCreateInfo = {
|
||||||
|
.queueCreateInfoCount = 1,
|
||||||
|
.pQueueCreateInfos = &queueCreateInfo,
|
||||||
|
.enabledExtensionCount = static_cast<std::uint32_t>(DEVICE_EXTENSIONS.size()),
|
||||||
|
.ppEnabledExtensionNames = DEVICE_EXTENSIONS.data()
|
||||||
|
};
|
||||||
|
if (const VkResult result = vk.CreateDevice(vk.physicalDevice, &deviceCreateInfo, nullptr, &vk.device); result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
msgError("Error creating Vulkan device: 0x{:x}.", static_cast<unsigned>(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vk.GetDeviceQueue(vk.device, /* queueFamilyIndex = */ 0, /* queueIndex = */ 0, &vk.queue);
|
||||||
|
|
||||||
|
if (!SDL_Vulkan_CreateSurface(mWindow, vk.instance, nullptr, &vk.surface))
|
||||||
|
{
|
||||||
|
msgError("Error creating SDL Vulkan surface: {}.", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -457,12 +784,18 @@ bool Application::initImGui()
|
|||||||
{
|
{
|
||||||
IMGUI_CHECKVERSION(); // not exactly useful when using static libs, but won't hurt
|
IMGUI_CHECKVERSION(); // not exactly useful when using static libs, but won't hurt
|
||||||
|
|
||||||
if (ImGui::CreateContext() == nullptr)
|
ImGuiContext* imguiContext = ImGui::CreateContext();
|
||||||
|
if (imguiContext == nullptr)
|
||||||
{
|
{
|
||||||
msgError("Error initializing ImGui context.");
|
msgError("Error initializing ImGui context.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imguiContext->ErrorCallbackUserData = this;
|
||||||
|
imguiContext->ErrorCallback = [](ImGuiContext* /* ctx */, void* userData, const char* msg) {
|
||||||
|
static_cast<Application*>(userData)->handleSDLError(msg);
|
||||||
|
};
|
||||||
|
|
||||||
loadImGuiConfig();
|
loadImGuiConfig();
|
||||||
|
|
||||||
configureImgui();
|
configureImgui();
|
||||||
@ -474,15 +807,33 @@ bool Application::initImGui()
|
|||||||
ImGui::StyleColorsDark();
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
// init the backends
|
// init the backends
|
||||||
if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, mGLContext))
|
switch (mConfig.graphicsApi)
|
||||||
{
|
{
|
||||||
msgError("Error initializing ImGui SDL3 backend.");
|
case GraphicsAPI::OPENGL:
|
||||||
|
if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, gl.context))
|
||||||
|
{
|
||||||
|
msgError("Error initializing ImGui SDL3 backend.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
|
||||||
|
{
|
||||||
|
msgError("Error initializing ImGui OpenGL3 backend.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GraphicsAPI::VULKAN:
|
||||||
|
MIJIN_TRAP(); // TODO!
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
|
|
||||||
|
if (imguiIO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||||
{
|
{
|
||||||
msgError("Error initializing ImGui OpenGL3 backend.");
|
if (!(imguiIO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports)) {
|
||||||
return false;
|
msgWarning("ImGUI viewports enabled, but platform doesn't support them.");
|
||||||
|
}
|
||||||
|
if (!(imguiIO.BackendFlags & ImGuiBackendFlags_RendererHasViewports)) {
|
||||||
|
msgWarning("ImGUI viewports enabled, but renderer doesn't support them.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// init font
|
// init font
|
||||||
@ -541,6 +892,25 @@ void Application::saveImGuiConfig()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Application::handleDebugUtilsMessage(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT /* messageTypes */,
|
||||||
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) -> VkBool32
|
||||||
|
{
|
||||||
|
switch (messageSeverity)
|
||||||
|
{
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
||||||
|
msgError("Vulkan debug error message: {}.", pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
||||||
|
msgWarning("Vulkan debug warning message: {}.", pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msgInfo("Vulkan debug message: {}.", pCallbackData->pMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void QuickApp::preInit(QuickAppOptions options)
|
void QuickApp::preInit(QuickAppOptions options)
|
||||||
{
|
{
|
||||||
MIJIN_ASSERT_FATAL(options.callbacks.render, "Missing render callback.");
|
MIJIN_ASSERT_FATAL(options.callbacks.render, "Missing render callback.");
|
||||||
|
367
private/raid/config.cpp
Normal file
367
private/raid/config.cpp
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
|
||||||
|
#include "raid/config.hpp"
|
||||||
|
|
||||||
|
#include <mijin/async/coroutine_sleep.hpp>
|
||||||
|
#include <mijin/io/stlstream.hpp>
|
||||||
|
#include <mijin/util/string.hpp>
|
||||||
|
#include <mijin/util/variant.hpp>
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct YAML::convert<raid::ConfigSection>
|
||||||
|
{
|
||||||
|
static Node encode(const raid::ConfigSection& section)
|
||||||
|
{
|
||||||
|
Node node;
|
||||||
|
for (const auto& [key, value] : section.getValues()) {
|
||||||
|
node[key] = value;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode(const Node& node, raid::ConfigSection& section)
|
||||||
|
{
|
||||||
|
if (!node.IsMap()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mijin::VectorMap<std::string, raid::ConfigValue> values;
|
||||||
|
for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
|
||||||
|
values.emplace(it->first.as<std::string>(), it->second.as<raid::ConfigValue>());
|
||||||
|
}
|
||||||
|
section = raid::ConfigSection(std::move(values));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template<>
|
||||||
|
struct YAML::convert<raid::ConfigArray>
|
||||||
|
{
|
||||||
|
static Node encode(const raid::ConfigArray& array)
|
||||||
|
{
|
||||||
|
Node node;
|
||||||
|
for (const raid::ConfigValue& value : array.getValues()) {
|
||||||
|
node.push_back(value);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode(const Node& node, raid::ConfigArray& array)
|
||||||
|
{
|
||||||
|
if (!node.IsSequence()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<raid::ConfigValue> values;
|
||||||
|
for (const YAML::Node& value : node) {
|
||||||
|
values.push_back(value.as<raid::ConfigValue>());
|
||||||
|
}
|
||||||
|
array = raid::ConfigArray(std::move(values));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template<>
|
||||||
|
struct YAML::convert<raid::ConfigValue>
|
||||||
|
{
|
||||||
|
static Node encode(const raid::ConfigValue& value)
|
||||||
|
{
|
||||||
|
Node node;
|
||||||
|
value.visit([&]<typename T>(const T& content) {
|
||||||
|
if constexpr (!std::is_same_v<T, std::nullptr_t>) {
|
||||||
|
node = content;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode(const Node& node, raid::ConfigValue& value)
|
||||||
|
{
|
||||||
|
switch (node.Type())
|
||||||
|
{
|
||||||
|
case YAML::NodeType::Null:
|
||||||
|
case YAML::NodeType::Undefined:
|
||||||
|
value = {};
|
||||||
|
break;
|
||||||
|
case YAML::NodeType::Sequence:
|
||||||
|
value = node.as<raid::ConfigArray>();
|
||||||
|
break;
|
||||||
|
case YAML::NodeType::Map:
|
||||||
|
value = node.as<raid::ConfigSection>();
|
||||||
|
break;
|
||||||
|
case YAML::NodeType::Scalar:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = node.as<raid::config_int_t>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch(const YAML::Exception&) {} // NOLINT(bugprone-empty-catch)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = node.as<double>();
|
||||||
|
}
|
||||||
|
catch(const YAML::Exception&) {} // NOLINT(bugprone-empty-catch)
|
||||||
|
value = node.as<std::string>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const ConfigValue EMPTY_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigValue& ConfigSection::operator[](std::string_view key) const noexcept
|
||||||
|
{
|
||||||
|
auto it = mValues.find(key);
|
||||||
|
if (it == mValues.end()) {
|
||||||
|
return EMPTY_VALUE;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigArray::append(ConfigValue value)
|
||||||
|
{
|
||||||
|
mValues.push_back(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigArray::setAt(std::size_t idx, ConfigValue value)
|
||||||
|
{
|
||||||
|
mValues[idx] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigArray::removeAt(std::size_t idx, std::size_t count)
|
||||||
|
{
|
||||||
|
auto itStart = mValues.begin() + static_cast<std::ptrdiff_t>(idx);
|
||||||
|
auto itEnd = itStart + static_cast<std::ptrdiff_t>(count);
|
||||||
|
mValues.erase(itStart, itEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ConfigValue& ConfigSection::getOrAdd(std::string_view key)
|
||||||
|
{
|
||||||
|
return mValues[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigSection::set(std::string_view key, ConfigValue value)
|
||||||
|
{
|
||||||
|
mValues[key] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigValue::asBool() const noexcept
|
||||||
|
{
|
||||||
|
return visit(mijin::Visitor{
|
||||||
|
[](std::nullptr_t) { return false; },
|
||||||
|
[](bool boolValue) { return boolValue; },
|
||||||
|
[](config_int_t intValue) { return intValue != 0; },
|
||||||
|
[](double doubleValue) { return doubleValue != 0.0; },
|
||||||
|
[](const std::string& stringValue) { return stringValue == "true"; },
|
||||||
|
[](const ConfigArray&) { return false; },
|
||||||
|
[](const ConfigSection&) { return false; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
config_int_t ConfigValue::asInt() const noexcept
|
||||||
|
{
|
||||||
|
return visit(mijin::Visitor{
|
||||||
|
[](std::nullptr_t) { return config_int_t(0); },
|
||||||
|
[](bool boolValue) { return config_int_t(boolValue); },
|
||||||
|
[](config_int_t intValue) { return intValue; },
|
||||||
|
[](double doubleValue) { return static_cast<config_int_t>(doubleValue); },
|
||||||
|
[](const std::string& stringValue) {
|
||||||
|
config_int_t intValue = 0;
|
||||||
|
if (mijin::toNumber(stringValue, intValue)) {
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
return config_int_t(0);
|
||||||
|
},
|
||||||
|
[](const ConfigArray&) { return config_int_t(0); },
|
||||||
|
[](const ConfigSection&) { return config_int_t(0); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
double ConfigValue::asDouble() const noexcept
|
||||||
|
{
|
||||||
|
return visit(mijin::Visitor{
|
||||||
|
[](std::nullptr_t) { return 0.0; },
|
||||||
|
[](bool boolValue) { return double(boolValue); },
|
||||||
|
[](config_int_t intValue) { return static_cast<double>(intValue); },
|
||||||
|
[](double doubleValue) { return doubleValue; },
|
||||||
|
[](const std::string& stringValue) {
|
||||||
|
double doubleValue = 0;
|
||||||
|
if (mijin::toNumber(stringValue, doubleValue)) {
|
||||||
|
return doubleValue;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
},
|
||||||
|
[](const ConfigArray&) { return 0.0; },
|
||||||
|
[](const ConfigSection&) { return 0.0; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ConfigValue::asString() const noexcept
|
||||||
|
{
|
||||||
|
static const std::string TRUE = "true";
|
||||||
|
static const std::string FALSE = "false";
|
||||||
|
static const std::string EMPTY{};
|
||||||
|
static thread_local std::string convertBuffer;
|
||||||
|
|
||||||
|
return visit(mijin::Visitor{
|
||||||
|
[](std::nullptr_t) -> const std::string& { return EMPTY; },
|
||||||
|
[](bool boolValue) -> const std::string& { return boolValue ? TRUE : FALSE; },
|
||||||
|
[](config_int_t intValue) -> const std::string& {
|
||||||
|
convertBuffer = std::to_string(intValue);
|
||||||
|
return convertBuffer;
|
||||||
|
},
|
||||||
|
[](double doubleValue) -> const std::string& {
|
||||||
|
convertBuffer = std::to_string(doubleValue);
|
||||||
|
return convertBuffer;
|
||||||
|
},
|
||||||
|
[](const std::string& stringValue) -> const std::string& {
|
||||||
|
return stringValue; // NOLINT(bugprone-return-const-ref-from-parameter)
|
||||||
|
},
|
||||||
|
[](const ConfigArray&) -> const std::string& { return EMPTY; },
|
||||||
|
[](const ConfigSection&) -> const std::string& { return EMPTY; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigArray& ConfigValue::asArray() const noexcept
|
||||||
|
{
|
||||||
|
static const ConfigArray EMPTY;
|
||||||
|
if (isArray()) {
|
||||||
|
return std::get<ConfigArray>(mContent);
|
||||||
|
}
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSection& ConfigValue::asMutableSection() noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(isSection(), "Cannot call this on non-section values!");
|
||||||
|
return std::get<ConfigSection>(mContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigSection& ConfigValue::asSection() const noexcept
|
||||||
|
{
|
||||||
|
static const ConfigSection EMPTY;
|
||||||
|
if (isSection()) {
|
||||||
|
return std::get<ConfigSection>(mContent);
|
||||||
|
}
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigValue& FileConfig::getValue(std::string_view path) const noexcept
|
||||||
|
{
|
||||||
|
const ConfigSection* section = &mRoot;
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!path.empty(), "Invalid config value path.");
|
||||||
|
|
||||||
|
const std::string_view::size_type pos = path.find('/');
|
||||||
|
if (pos == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
section = &(*section)[path.substr(0, pos)].asSection();
|
||||||
|
path = path.substr(pos + 1);
|
||||||
|
}
|
||||||
|
return (*section)[path];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileConfig::setValue(std::string_view path, ConfigValue value) noexcept
|
||||||
|
{
|
||||||
|
ConfigSection* section = &mRoot;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!path.empty(), "Invalid config value path.");
|
||||||
|
|
||||||
|
const std::string_view::size_type pos = path.find('/');
|
||||||
|
if (pos == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigValue& existing = section->getOrAdd(path.substr(0, pos));
|
||||||
|
if (existing.isUndefined()) {
|
||||||
|
existing = ConfigSection();
|
||||||
|
}
|
||||||
|
else if (!existing.isSection())
|
||||||
|
{
|
||||||
|
MIJIN_ERROR("Value already exists, but is not a section.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
section = &existing.asMutableSection();
|
||||||
|
path = path.substr(pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
section->set(path, std::move(value));
|
||||||
|
mDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mijin::Result<> FileConfig::init(mijin::PathReference path)
|
||||||
|
{
|
||||||
|
mPath = std::move(path);
|
||||||
|
|
||||||
|
if (mPath.getInfo().exists) {
|
||||||
|
return load();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mijin::Result<> FileConfig::load()
|
||||||
|
{
|
||||||
|
std::unique_ptr<mijin::Stream> stream;
|
||||||
|
if (const mijin::StreamError result = mPath.open(mijin::FileOpenMode::READ, stream); result != mijin::StreamError::SUCCESS) {
|
||||||
|
return mijin::ResultError(mijin::errorName(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
mijin::IOStreamAdapter streamAdapter(*stream);
|
||||||
|
YAML::Node root;
|
||||||
|
|
||||||
|
try {
|
||||||
|
root = YAML::Load(streamAdapter);
|
||||||
|
}
|
||||||
|
catch(const YAML::Exception& exc) {
|
||||||
|
return mijin::ResultError(exc.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.IsMap()) {
|
||||||
|
return mijin::ResultError("invalid config file, expected a map");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mRoot = root.as<ConfigSection>();
|
||||||
|
}
|
||||||
|
catch(const YAML::Exception& exc) {
|
||||||
|
return mijin::ResultError(exc.what());
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mijin::Result<> FileConfig::save(bool force)
|
||||||
|
{
|
||||||
|
if (!force && !mDirty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mDirty = false;
|
||||||
|
|
||||||
|
std::unique_ptr<mijin::Stream> stream;
|
||||||
|
if (const mijin::StreamError result = mPath.open(mijin::FileOpenMode::WRITE, stream); result != mijin::StreamError::SUCCESS) {
|
||||||
|
return mijin::ResultError(mijin::errorName(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
YAML::Emitter emitter;
|
||||||
|
emitter << YAML::Node(mRoot);
|
||||||
|
if (const mijin::StreamError result = stream->writeText(emitter.c_str()); result != mijin::StreamError::SUCCESS) {
|
||||||
|
return mijin::ResultError(mijin::errorName(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
7
private/raid/fonts.gen.cpp.jinja
Normal file
7
private/raid/fonts.gen.cpp.jinja
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
#include "./fonts.gen.hpp"
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
extern const std::array<std::uint8_t, NOTO_SANS_SIZE> NOTO_SANS_DATA = { {{ file_content_hex('res/fonts/NotoSans-Regular.ttf') }} };
|
||||||
|
}
|
16
private/raid/fonts.gen.hpp.jinja
Normal file
16
private/raid/fonts.gen.hpp.jinja
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED)
|
||||||
|
#define RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
inline constexpr std::size_t NOTO_SANS_SIZE = {{ file_size('res/fonts/NotoSans-Regular.ttf' )}};
|
||||||
|
extern const std::array<std::uint8_t, NOTO_SANS_SIZE> NOTO_SANS_DATA;
|
||||||
|
} // namespace raid
|
||||||
|
|
||||||
|
#endif // !defined(RAID_PRIVATE_RAID_FONTS_GEN_HPP_INCLUDED)
|
@ -3,6 +3,10 @@ Import('env')
|
|||||||
|
|
||||||
src_files = Split("""
|
src_files = Split("""
|
||||||
main.cpp
|
main.cpp
|
||||||
|
|
||||||
|
application.cpp
|
||||||
|
frames/config.cpp
|
||||||
|
frames/data_table.cpp
|
||||||
""")
|
""")
|
||||||
|
|
||||||
prog_app = env.UnityProgram(
|
prog_app = env.UnityProgram(
|
||||||
|
87
private/raid_test/application.cpp
Normal file
87
private/raid_test/application.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
#include "raid_test/application.hpp"
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
Application gApplication;
|
||||||
|
|
||||||
|
bool Application::init()
|
||||||
|
{
|
||||||
|
if (!raid::Application::init()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setMainWindowFlags(raid::DEFAULT_MAIN_WINDOW_FLAGS | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking);
|
||||||
|
setMainWindowStyle(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||||
|
setMainWindowStyle(ImGuiStyleVar_WindowBorderSize, 0.f);
|
||||||
|
std::ranges::fill(mFrameOpen, true);
|
||||||
|
|
||||||
|
if (const mijin::Result<> result = mConfig.init(getFS().getPath("/config/persistent.yml")); !result.isSuccess()) {
|
||||||
|
msgError("Error initializing config: {}.", result.getError().message);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::configureImgui()
|
||||||
|
{
|
||||||
|
raid::Application::configureImgui();
|
||||||
|
ImGuiIO& imguiIO = ImGui::GetIO();
|
||||||
|
imguiIO.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::render()
|
||||||
|
{
|
||||||
|
if (const mijin::Result<> result = mConfig.save(); !result.isSuccess()) {
|
||||||
|
msgError("Error while saving config: {}.", result.getError().message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenuBar())
|
||||||
|
{
|
||||||
|
if (ImGui::BeginMenu("File"))
|
||||||
|
{
|
||||||
|
if (ImGui::MenuItem("Quit"))
|
||||||
|
{
|
||||||
|
requestQuit();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("Frames"))
|
||||||
|
{
|
||||||
|
for (std::size_t idx = 0; idx < NUM_FRAMES; ++idx) {
|
||||||
|
ImGui::MenuItem(FRAMES[idx].title, nullptr, &mFrameOpen[idx]);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("Debug"))
|
||||||
|
{
|
||||||
|
ImGui::MenuItem("ImGui Metrics", nullptr, &mShowMetrics);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImGuiID dockID = ImGui::GetID(this);
|
||||||
|
ImGui::DockSpace(dockID);
|
||||||
|
|
||||||
|
if (mShowMetrics) {
|
||||||
|
ImGui::ShowMetricsWindow(&mShowMetrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t idx = 0; idx < NUM_FRAMES; ++idx)
|
||||||
|
{
|
||||||
|
ImGui::SetNextWindowDockID(dockID, ImGuiCond_FirstUseEver);
|
||||||
|
if (mFrameOpen[idx]) {
|
||||||
|
FRAMES[idx].render(mFrameOpen[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Application::getFolderName()
|
||||||
|
{
|
||||||
|
return "raid_test_app";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Application::getWindowTitle()
|
||||||
|
{
|
||||||
|
return "RAID Test Application";
|
||||||
|
}
|
||||||
|
} // namespace raid_test
|
46
private/raid_test/application.hpp
Normal file
46
private/raid_test/application.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_TEST_APPLICATION_HPP_INCLUDED)
|
||||||
|
#define RAID_TEST_APPLICATION_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include "raid/raid.hpp"
|
||||||
|
#include "./frames/config.hpp"
|
||||||
|
#include "./frames/data_table.hpp"
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
class Application : public raid::Application
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct Frame
|
||||||
|
{
|
||||||
|
using render_fn_t = void (*)(bool& open);
|
||||||
|
const char* title;
|
||||||
|
render_fn_t render;
|
||||||
|
};
|
||||||
|
static constexpr Frame FRAMES[] = {
|
||||||
|
{.title = CONFIG_TITLE, .render = &renderConfig},
|
||||||
|
{.title = DATA_TABLE_TITLE, .render = &renderDataTable}
|
||||||
|
};
|
||||||
|
static constexpr std::size_t NUM_FRAMES = sizeof(FRAMES) / sizeof(FRAMES[0]);
|
||||||
|
|
||||||
|
raid::FileConfig mConfig;
|
||||||
|
bool mShowMetrics = false;
|
||||||
|
std::array<bool, NUM_FRAMES> mFrameOpen{};
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
raid::FileConfig& getConfig() noexcept { return mConfig; }
|
||||||
|
protected:
|
||||||
|
bool init() override;
|
||||||
|
void configureImgui() override;
|
||||||
|
void render() override;
|
||||||
|
std::string getFolderName() override;
|
||||||
|
std::string getWindowTitle() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Application gApplication;
|
||||||
|
} // namespace raid_test
|
||||||
|
|
||||||
|
#endif // !defined(RAID_TEST_APPLICATION_HPP_INCLUDED)
|
26
private/raid_test/frames/config.cpp
Normal file
26
private/raid_test/frames/config.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
#include "raid_test/frames/config.hpp"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "raid/config.hpp"
|
||||||
|
#include "raid_test/application.hpp"
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
void renderConfig(bool& open)
|
||||||
|
{
|
||||||
|
if (!ImGui::Begin(CONFIG_TITLE, &open))
|
||||||
|
{
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const char* TEST_BOOL_PATH = "test/section/bool";
|
||||||
|
bool testBool = gApplication.getConfig().getValue(TEST_BOOL_PATH).asBool();
|
||||||
|
if (ImGui::Checkbox("Test Bool", &testBool)) {
|
||||||
|
gApplication.getConfig().setValue(TEST_BOOL_PATH, testBool);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
14
private/raid_test/frames/config.hpp
Normal file
14
private/raid_test/frames/config.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED)
|
||||||
|
#define RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
inline constexpr const char* CONFIG_TITLE = "Config";
|
||||||
|
|
||||||
|
void renderConfig(bool& open);
|
||||||
|
} // namespace raid_test
|
||||||
|
|
||||||
|
#endif // !defined(RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED)
|
90
private/raid_test/frames/data_table.cpp
Normal file
90
private/raid_test/frames/data_table.cpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
#include "raid_test/frames/data_table.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "raid/imraid.hpp"
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const std::array FIRST_NAMES = {
|
||||||
|
"Emma", "Hannah", "Mia", "Leonie", "Lina", "Marie", "Sophia", "Charlotte", "Paula", "Greta", "Frieda", "Ella", "Freia",
|
||||||
|
"Leon", "Paul", "Maximilian", "Ben", "Lukas", "Finn", "Fiete", "Felix", "Moritz", "Jakob", "Tim", "Emil", "Theo",
|
||||||
|
"James", "Mary", "Michael", "Patricia", "John", "Jennifer", "Robert", "Linda", "David", "Elizabeth", "William",
|
||||||
|
"Barbara", "Richard", "Susan", "Joseph", "Jessica", "Thomas", "Karen", "Christopher", "Sarah"
|
||||||
|
};
|
||||||
|
const std::array LAST_NAMES = {
|
||||||
|
"Müller", "Schmidt", "Schneider", "Fischer", "Meyer", "Weber", "Hofmann", "Wagner", "Becker", "Schulz", "Schäfer",
|
||||||
|
"Koch", "Bauer", "Richter", "Klein", "Schröder", "Wolf", "Neumann", "Schwarz", "Schmitz", "Smith", "Johnson",
|
||||||
|
"Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodríguez", "Martínez", "Hernández", "López", "Gonzalez",
|
||||||
|
"Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin"
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int MIN_AGE = 18;
|
||||||
|
constexpr int MAX_AGE = 120;
|
||||||
|
constexpr double HEIGHT_MEAN = 1.75;
|
||||||
|
constexpr double HEIGHT_STDDEV = 0.1;
|
||||||
|
constexpr std::size_t NUM_PEOPLE = 5000;
|
||||||
|
|
||||||
|
struct Person
|
||||||
|
{
|
||||||
|
const char* firstName;
|
||||||
|
const char* lastName;
|
||||||
|
int age;
|
||||||
|
double height;
|
||||||
|
};
|
||||||
|
|
||||||
|
using rand_int_t = std::mt19937::result_type;
|
||||||
|
|
||||||
|
std::mt19937 gRandom(std::random_device{}());
|
||||||
|
std::uniform_int_distribution<rand_int_t> gFirstNameDistribution(0, static_cast<rand_int_t>(FIRST_NAMES.size()-1));
|
||||||
|
std::uniform_int_distribution<rand_int_t> gLastNameDistribution(0, static_cast<rand_int_t>(LAST_NAMES.size()-1));
|
||||||
|
std::uniform_int_distribution<rand_int_t> gAgeDistribution(static_cast<rand_int_t>(MIN_AGE), static_cast<rand_int_t>(MAX_AGE));
|
||||||
|
std::normal_distribution gHeightDistribution(HEIGHT_MEAN, HEIGHT_STDDEV);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
Person randomPerson()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.firstName = FIRST_NAMES[gFirstNameDistribution(gRandom)],
|
||||||
|
.lastName = LAST_NAMES[gLastNameDistribution(gRandom)],
|
||||||
|
.age = static_cast<int>(gAgeDistribution(gRandom)),
|
||||||
|
.height = gHeightDistribution(gRandom)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array<Person, NUM_PEOPLE> PEOPLE = []()
|
||||||
|
{
|
||||||
|
std::array<Person, NUM_PEOPLE> result;
|
||||||
|
std::ranges::generate(result, randomPerson);
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
|
||||||
|
const std::array DATA_TABLE_COLUMNS = {
|
||||||
|
ImRaid::MakeStringColumn("First Name", &Person::firstName),
|
||||||
|
ImRaid::MakeStringColumn("Last Name", &Person::lastName),
|
||||||
|
ImRaid::MakeColumn("Age", "%d years", &Person::age),
|
||||||
|
ImRaid::MakeColumn("Height", "%.2f m", &Person::height)
|
||||||
|
};
|
||||||
|
const ImRaid::DataTableOptions<Person> DATA_TABLE_OPTIONS = {
|
||||||
|
.columns = DATA_TABLE_COLUMNS
|
||||||
|
};
|
||||||
|
ImRaid::DataTableState gDataTableState;
|
||||||
|
}
|
||||||
|
void renderDataTable(bool& open)
|
||||||
|
{
|
||||||
|
if (!ImGui::Begin(DATA_TABLE_TITLE, &open))
|
||||||
|
{
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImRaid::DataTable("people", DATA_TABLE_OPTIONS, PEOPLE, gDataTableState);
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
} // namespace raid_test
|
14
private/raid_test/frames/data_table.hpp
Normal file
14
private/raid_test/frames/data_table.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_TEST_FRAMES_DATA_TABLE_HPP_INCLUDED)
|
||||||
|
#define RAID_TEST_FRAMES_DATA_TABLE_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
namespace raid_test
|
||||||
|
{
|
||||||
|
inline constexpr const char* DATA_TABLE_TITLE = "Data Table";
|
||||||
|
|
||||||
|
void renderDataTable(bool& open);
|
||||||
|
} // namespace raid_test
|
||||||
|
|
||||||
|
#endif // !defined(RAID_TEST_FRAMES_DATA_TABLE_HPP_INCLUDED)
|
@ -1,40 +1,7 @@
|
|||||||
|
|
||||||
#include "raid/raid.hpp"
|
#include "./application.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void render()
|
|
||||||
{
|
|
||||||
if (ImGui::BeginMenuBar())
|
|
||||||
{
|
|
||||||
if (ImGui::BeginMenu("File"))
|
|
||||||
{
|
|
||||||
if (ImGui::MenuItem("Quit"))
|
|
||||||
{
|
|
||||||
raid::QuickApp::get().requestQuit();
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMenuBar();
|
|
||||||
}
|
|
||||||
ImGui::Text("hi");
|
|
||||||
|
|
||||||
ImGui::Begin("Test");
|
|
||||||
ImGui::Text("Test Content");
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
return raid::runQuick(argc, argv, {
|
return raid_test::gApplication.run(argc, argv);
|
||||||
.callbacks = {
|
|
||||||
.render = &render
|
|
||||||
},
|
|
||||||
.folderName = "raid_test_app",
|
|
||||||
.windowTitle = "RAID Test App",
|
|
||||||
.mainWindowFlags = raid::DEFAULT_MAIN_WINDOW_FLAGS | ImGuiWindowFlags_MenuBar
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
257
public/raid/application.hpp
Normal file
257
public/raid/application.hpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_APPLICATION_HPP_INCLUDED)
|
||||||
|
#define RAID_APPLICATION_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <variant>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <mijin/async/coroutine.hpp>
|
||||||
|
#include <mijin/util/bitflags.hpp>
|
||||||
|
#include <mijin/virtual_filesystem/memory.hpp>
|
||||||
|
#include <mijin/virtual_filesystem/stacked.hpp>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_vulkan.h>
|
||||||
|
#include "./internal/opengl.hpp"
|
||||||
|
#include "./internal/vulkan.hpp"
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
inline constexpr int ERR_INIT_FAILED = 100;
|
||||||
|
inline constexpr ImGuiWindowFlags DEFAULT_MAIN_WINDOW_FLAGS = 0
|
||||||
|
| ImGuiWindowFlags_NoBackground
|
||||||
|
| ImGuiWindowFlags_NoDecoration
|
||||||
|
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
||||||
|
| ImGuiWindowFlags_NoNav;
|
||||||
|
inline constexpr const char* DEFAULT_FONT_PATH = "/data/fonts/NotoSans-Regular.ttf";
|
||||||
|
|
||||||
|
enum class MessageSeverity : unsigned char
|
||||||
|
{
|
||||||
|
INFO,
|
||||||
|
WARNING,
|
||||||
|
ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
MessageSeverity severity;
|
||||||
|
const char* text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FontFlags : mijin::BitFlags<FontFlags>
|
||||||
|
{
|
||||||
|
bool pixelSnapH : 1 = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FontConfig
|
||||||
|
{
|
||||||
|
fs::path path;
|
||||||
|
std::vector<std::pair<ImWchar, ImWchar>> glyphRanges;
|
||||||
|
float size = 20.f;
|
||||||
|
FontFlags flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApplicationFlags : mijin::BitFlags<ApplicationFlags>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* (Linux only) prefer X11 even when on Wayland. Required for multi-viewport support, but currently really buggy.
|
||||||
|
* \see https://github.com/ocornut/imgui/issues/8609
|
||||||
|
* \see https://github.com/ocornut/imgui/issues/8587
|
||||||
|
*/
|
||||||
|
bool x11OnWayland : 1 = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GraphicsAPI : std::uint8_t
|
||||||
|
{
|
||||||
|
OPENGL,
|
||||||
|
VULKAN
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApplicationConfig
|
||||||
|
{
|
||||||
|
ApplicationFlags flags = {};
|
||||||
|
GraphicsAPI graphicsApi = GraphicsAPI::OPENGL;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Application : private MixinOpenGLApplication, MixinVulkanApplication
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SDL_Window* mWindow = nullptr;
|
||||||
|
|
||||||
|
mijin::StackedFileSystemAdapter mFS;
|
||||||
|
mijin::MemoryFileSystemAdapter* mMemoryFS = nullptr;
|
||||||
|
mijin::SimpleTaskLoop mTaskLoop;
|
||||||
|
std::unordered_map<fs::path, ImTextureID> mTextures;
|
||||||
|
|
||||||
|
bool mRunning = true;
|
||||||
|
ImGuiWindowFlags mMainWindowFlags = DEFAULT_MAIN_WINDOW_FLAGS;
|
||||||
|
std::unordered_map<ImGuiStyleVar, std::variant<float, ImVec2>> mMainWindowStyles;
|
||||||
|
const ApplicationConfig mConfig;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
OpenGLData gl;
|
||||||
|
VulkanData vk;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
explicit Application(ApplicationConfig config = {}) noexcept : mConfig(config) {}
|
||||||
|
virtual ~Application() = default;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const ApplicationConfig& getConfig() const noexcept { return mConfig; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::StackedFileSystemAdapter& getFS() { return mFS; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::MemoryFileSystemAdapter& getMemoryFS()
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT_FATAL(mMemoryFS != nullptr, "Memory FS has not been initialized yet.");
|
||||||
|
return *mMemoryFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::SimpleTaskLoop& getLoop() { return mTaskLoop; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ImGuiWindowFlags getMainWindowFlags() const { return mMainWindowFlags; }
|
||||||
|
|
||||||
|
void setMainWindowFlags(ImGuiWindowFlags flags) { mMainWindowFlags = flags; }
|
||||||
|
void setMainWindowStyle(ImGuiStyleVar variable, std::variant<float, ImVec2> value) { mMainWindowStyles.emplace(variable, value); }
|
||||||
|
void unsetMainWindowStyle(ImGuiStyleVar variable) { mMainWindowStyles.erase(variable); }
|
||||||
|
void requestQuit() { mRunning = false; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int run(int argc, char* argv[]);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool loadFonts(std::span<const FontConfig> fonts);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool loadFont(const FontConfig& font)
|
||||||
|
{
|
||||||
|
return loadFonts({&font, 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ImTextureID getOrLoadTexture(fs::path path);
|
||||||
|
|
||||||
|
void destroyTexture(ImTextureID texture);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void render() = 0;
|
||||||
|
virtual std::string getFolderName() = 0;
|
||||||
|
virtual std::string getWindowTitle() = 0;
|
||||||
|
virtual void configureImgui();
|
||||||
|
virtual std::vector<FontConfig> getDefaultFonts();
|
||||||
|
virtual void initMemoryFS();
|
||||||
|
virtual void handleMessage(const Message& message);
|
||||||
|
virtual void handleSDLEvent(const SDL_Event& event);
|
||||||
|
virtual void handleSDLError(const char* message);
|
||||||
|
|
||||||
|
virtual void handleCloseRequested();
|
||||||
|
|
||||||
|
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<typename TArg, typename... TArgs>
|
||||||
|
void msgInfo(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
||||||
|
{
|
||||||
|
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
||||||
|
msgInfo(text);
|
||||||
|
}
|
||||||
|
template<typename TArg, typename... TArgs>
|
||||||
|
void msgWarning(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
||||||
|
{
|
||||||
|
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
||||||
|
msgWarning(text);
|
||||||
|
}
|
||||||
|
template<typename TArg, typename... TArgs>
|
||||||
|
void msgError(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
||||||
|
{
|
||||||
|
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
||||||
|
msgError(text);
|
||||||
|
}
|
||||||
|
virtual bool init();
|
||||||
|
virtual void cleanup();
|
||||||
|
private:
|
||||||
|
bool initSDL();
|
||||||
|
bool initOpenGL();
|
||||||
|
bool initVulkan();
|
||||||
|
bool initImGui();
|
||||||
|
void handleSDLEvents();
|
||||||
|
void loadImGuiConfig();
|
||||||
|
void saveImGuiConfig();
|
||||||
|
|
||||||
|
VkBool32 handleDebugUtilsMessage(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||||
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData);
|
||||||
|
};
|
||||||
|
|
||||||
|
using render_cb_t = std::function<void()>;
|
||||||
|
struct QuickAppOptions
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
render_cb_t render;
|
||||||
|
} callbacks;
|
||||||
|
std::string folderName = "raid";
|
||||||
|
std::string windowTitle = "RAID";
|
||||||
|
ImGuiWindowFlags mainWindowFlags = DEFAULT_MAIN_WINDOW_FLAGS;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QuickApp : public Application
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
render_cb_t mRenderCallback;
|
||||||
|
std::string mFolderName;
|
||||||
|
std::string mWindowTitle;
|
||||||
|
public:
|
||||||
|
explicit QuickApp(ApplicationConfig config = {}) noexcept : Application(config) {}
|
||||||
|
|
||||||
|
void preInit(QuickAppOptions options);
|
||||||
|
void render() override;
|
||||||
|
std::string getFolderName() override;
|
||||||
|
std::string getWindowTitle() override;
|
||||||
|
|
||||||
|
static QuickApp& get();
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int runQuick(int argc, char* argv[], QuickAppOptions options);
|
||||||
|
} // namespace raid
|
||||||
|
|
||||||
|
#endif // !defined(RAID_APPLICATION_HPP_INCLUDED)
|
189
public/raid/config.hpp
Normal file
189
public/raid/config.hpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED)
|
||||||
|
#define RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mijin/async/coroutine.hpp>
|
||||||
|
#include <mijin/container/vector_map.hpp>
|
||||||
|
#include <mijin/memory/dynamic_pointer.hpp>
|
||||||
|
#include <mijin/types/result.hpp>
|
||||||
|
#include <mijin/virtual_filesystem/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
class ConfigValue;
|
||||||
|
|
||||||
|
class ConfigArray
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator = std::vector<ConfigValue>::iterator;
|
||||||
|
using const_iterator = std::vector<ConfigValue>::const_iterator;
|
||||||
|
private:
|
||||||
|
std::vector<ConfigValue> mValues;
|
||||||
|
public:
|
||||||
|
ConfigArray() noexcept = default;
|
||||||
|
ConfigArray(const ConfigArray&) = default;
|
||||||
|
ConfigArray(ConfigArray&&) noexcept = default;
|
||||||
|
explicit ConfigArray(std::vector<ConfigValue> values) noexcept : mValues(std::move(values)) {}
|
||||||
|
|
||||||
|
ConfigArray& operator=(const ConfigArray&) = default;
|
||||||
|
ConfigArray& operator=(ConfigArray&&) noexcept = default;
|
||||||
|
|
||||||
|
const ConfigValue& operator[](std::size_t idx) const noexcept { return mValues[idx]; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const std::vector<ConfigValue>& getValues() const noexcept { return mValues; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::size_t getSize() const noexcept { return mValues.size(); }
|
||||||
|
|
||||||
|
void append(ConfigValue value);
|
||||||
|
void setAt(std::size_t idx, ConfigValue value);
|
||||||
|
void removeAt(std::size_t idx, std::size_t count = 1);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isEmpty() const noexcept { return mValues.empty(); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
iterator begin() noexcept { return mValues.begin(); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
iterator end() noexcept { return mValues.end(); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const_iterator begin() const noexcept { return mValues.begin(); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const_iterator end() const noexcept { return mValues.end(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigSection
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
mijin::VectorMap<std::string, ConfigValue> mValues;
|
||||||
|
public:
|
||||||
|
ConfigSection() noexcept = default;
|
||||||
|
ConfigSection(const ConfigSection&) = default;
|
||||||
|
ConfigSection(ConfigSection&&) noexcept = default;
|
||||||
|
explicit ConfigSection(mijin::VectorMap<std::string, ConfigValue> values) noexcept : mValues(std::move(values)) {}
|
||||||
|
|
||||||
|
ConfigSection& operator=(const ConfigSection&) = default;
|
||||||
|
ConfigSection& operator=(ConfigSection&&) noexcept = default;
|
||||||
|
|
||||||
|
const ConfigValue& operator[](std::string_view key) const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ConfigValue& getOrAdd(std::string_view key);
|
||||||
|
|
||||||
|
void set(std::string_view key, ConfigValue value);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::VectorMap<std::string, ConfigValue>& getValues() noexcept { return mValues; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const mijin::VectorMap<std::string, ConfigValue>& getValues() const noexcept { return mValues; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using config_int_t = std::int64_t;
|
||||||
|
class ConfigValue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::variant<std::nullptr_t, bool, config_int_t, double, std::string, ConfigArray, ConfigSection> mContent;
|
||||||
|
public:
|
||||||
|
ConfigValue() noexcept : mContent(nullptr) {}
|
||||||
|
ConfigValue(const ConfigValue&) = default;
|
||||||
|
ConfigValue(ConfigValue&&) noexcept = default;
|
||||||
|
ConfigValue(bool content) noexcept : mContent(content) {}
|
||||||
|
ConfigValue(config_int_t content) noexcept : mContent(content) {}
|
||||||
|
ConfigValue(double content) noexcept : mContent(content) {}
|
||||||
|
ConfigValue(std::string content) noexcept : mContent(std::move(content)) {}
|
||||||
|
ConfigValue(const char* content) noexcept : mContent(std::string(content)) {}
|
||||||
|
ConfigValue(ConfigArray content) noexcept : mContent(std::move(content)) {}
|
||||||
|
ConfigValue(ConfigSection content) noexcept : mContent(std::move(content)) {}
|
||||||
|
|
||||||
|
ConfigValue& operator=(const ConfigValue&) = default;
|
||||||
|
ConfigValue& operator=(ConfigValue&&) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isUndefined() const noexcept { return std::holds_alternative<std::nullptr_t>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isBool() const noexcept { return std::holds_alternative<bool>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isInt() const noexcept { return std::holds_alternative<config_int_t>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isDouble() const noexcept { return std::holds_alternative<double>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isString() const noexcept { return std::holds_alternative<std::string>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isArray() const noexcept { return std::holds_alternative<ConfigArray>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isSection() const noexcept { return std::holds_alternative<ConfigSection>(mContent); }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool asBool() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
config_int_t asInt() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
double asDouble() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const std::string& asString() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const ConfigArray& asArray() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ConfigSection& asMutableSection() noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const ConfigSection& asSection() const noexcept;
|
||||||
|
|
||||||
|
template<typename TFunc>
|
||||||
|
decltype(auto) visit(TFunc&& func) const noexcept
|
||||||
|
{
|
||||||
|
return std::visit(std::forward<TFunc>(func), mContent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileConfig
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ConfigSection mRoot;
|
||||||
|
mijin::PathReference mPath;
|
||||||
|
bool mDirty = false;
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
const ConfigSection& getRoot() const noexcept { return mRoot; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
const ConfigValue& getValue(std::string_view path) const noexcept;
|
||||||
|
|
||||||
|
void setValue(std::string_view path, ConfigValue value) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::Result<> init(mijin::PathReference path);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::Result<> load();
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
mijin::Result<> save(bool force = false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !defined(RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED)
|
@ -4,7 +4,17 @@
|
|||||||
#if !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
#if !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
||||||
#define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1
|
#define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <mijin/async/coroutine.hpp>
|
||||||
|
#include <mijin/debug/assert.hpp>
|
||||||
|
|
||||||
namespace ImRaid
|
namespace ImRaid
|
||||||
{
|
{
|
||||||
@ -47,6 +57,268 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im
|
|||||||
}
|
}
|
||||||
return clicked;
|
return clicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool BeginPopupButton(const char* label)
|
||||||
|
{
|
||||||
|
char popupId[128] = {"popup##"};
|
||||||
|
std::strcat(popupId, label);
|
||||||
|
|
||||||
|
const float popupX = ImGui::GetCursorScreenPos().x;
|
||||||
|
if (ImGui::Button(label)) {
|
||||||
|
ImGui::OpenPopup(popupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float popupY = ImGui::GetCursorScreenPos().y;
|
||||||
|
ImGui::SetNextWindowPos({popupX, popupY});
|
||||||
|
return ImGui::BeginPopup(popupId, ImGuiWindowFlags_NoNav);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DataTableState
|
||||||
|
{
|
||||||
|
std::vector<std::size_t> sortedIndices;
|
||||||
|
|
||||||
|
struct SortColumn
|
||||||
|
{
|
||||||
|
std::size_t index;
|
||||||
|
bool sortDescending = false;
|
||||||
|
};
|
||||||
|
std::vector<SortColumn> sortColumns;
|
||||||
|
bool dirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TObject>
|
||||||
|
struct DataTableColumn
|
||||||
|
{
|
||||||
|
struct CellRendererArgs
|
||||||
|
{
|
||||||
|
const TObject& object;
|
||||||
|
};
|
||||||
|
using renderer_t = std::function<void(const CellRendererArgs&)>;
|
||||||
|
using comparator_t = std::function<bool(const TObject&, const TObject&)>;
|
||||||
|
|
||||||
|
const char* header;
|
||||||
|
renderer_t renderer;
|
||||||
|
comparator_t comparator;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TObject>
|
||||||
|
struct DataTableOptions
|
||||||
|
{
|
||||||
|
std::span<const DataTableColumn<TObject>> columns;
|
||||||
|
ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable
|
||||||
|
| ImGuiTableFlags_ScrollY | ImGuiTableFlags_SortMulti;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TObject,typename TData>
|
||||||
|
inline void DataTable(const char* strId, const DataTableOptions<TObject>& options, const TData& data, DataTableState& state, ImVec2 outerSize = {})
|
||||||
|
{
|
||||||
|
if (outerSize.y <= 0.f) {
|
||||||
|
outerSize.y = ImGui::GetContentRegionAvail().y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ImGui::BeginTable(strId, static_cast<int>(options.columns.size()), options.tableFlags, outerSize)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const DataTableColumn<TObject>& column : options.columns)
|
||||||
|
{
|
||||||
|
ImGuiTableColumnFlags flags = 0;
|
||||||
|
MIJIN_ASSERT(column.renderer, "Missing column renderer.");
|
||||||
|
if (!column.comparator) {
|
||||||
|
flags |= ImGuiTableColumnFlags_NoSort;
|
||||||
|
}
|
||||||
|
ImGui::TableSetupColumn(column.header, flags);
|
||||||
|
}
|
||||||
|
ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
|
||||||
|
if (sortSpecs != nullptr && sortSpecs->SpecsDirty)
|
||||||
|
{
|
||||||
|
sortSpecs->SpecsDirty = false;
|
||||||
|
|
||||||
|
if (state.sortColumns.size() != static_cast<std::size_t>(sortSpecs->SpecsCount))
|
||||||
|
{
|
||||||
|
state.dirty = true;
|
||||||
|
state.sortColumns.resize(sortSpecs->SpecsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int idx = 0; idx < sortSpecs->SpecsCount; ++idx)
|
||||||
|
{
|
||||||
|
const ImGuiTableColumnSortSpecs& specs = sortSpecs->Specs[idx];
|
||||||
|
DataTableState::SortColumn& column = state.sortColumns[idx];
|
||||||
|
state.dirty |= (static_cast<std::size_t>(specs.ColumnIndex) != column.index);
|
||||||
|
state.dirty |= column.sortDescending != (specs.SortDirection == ImGuiSortDirection_Descending);
|
||||||
|
column = {
|
||||||
|
.index = static_cast<std::size_t>(specs.ColumnIndex),
|
||||||
|
.sortDescending = (specs.SortDirection == ImGuiSortDirection_Descending)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.sortedIndices.size() != data.size())
|
||||||
|
{
|
||||||
|
state.dirty = true;
|
||||||
|
state.sortedIndices.resize(data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.dirty)
|
||||||
|
{
|
||||||
|
for (std::size_t idx = 0; idx < data.size(); ++idx) {
|
||||||
|
state.sortedIndices[idx] = idx;
|
||||||
|
}
|
||||||
|
std::ranges::sort(state.sortedIndices, [&](std::size_t leftIdx, std::size_t rightIdx)
|
||||||
|
{
|
||||||
|
for (const DataTableState::SortColumn& column : state.sortColumns)
|
||||||
|
{
|
||||||
|
const bool less = options.columns[column.index].comparator(data[leftIdx], data[rightIdx]);
|
||||||
|
if (less)
|
||||||
|
{ // left < right
|
||||||
|
return !column.sortDescending;
|
||||||
|
}
|
||||||
|
if (options.columns[column.index].comparator(data[rightIdx], data[leftIdx]))
|
||||||
|
{ // left > right
|
||||||
|
return column.sortDescending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// left == right
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
state.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::size_t dataIdx : state.sortedIndices)
|
||||||
|
{
|
||||||
|
const TObject& object = data[dataIdx];
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
for (const DataTableColumn<TObject>& column : options.columns)
|
||||||
|
{
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
column.renderer({
|
||||||
|
.object = object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TObject>
|
||||||
|
inline DataTableColumn<TObject> MakeStringColumn(const char* header, const char* TObject::* member)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.header = header,
|
||||||
|
.renderer = [member](const auto& args) { ImGui::TextUnformatted(args.object.*member); },
|
||||||
|
.comparator = [member](const TObject& left, const TObject& right) { return std::string_view(left.*member) < std::string_view(right.*member); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TObject>
|
||||||
|
inline DataTableColumn<TObject> MakeStringColumn(const char* header, std::string TObject::* member)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.header = header,
|
||||||
|
.renderer = [member](const auto& args) { ImGui::TextUnformatted((args.object.*member).c_str()); },
|
||||||
|
.comparator = [member](const TObject& left, const TObject& right) { return left.*member < right.*member; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TObject, typename TMember>
|
||||||
|
inline DataTableColumn<TObject> MakeColumn(const char* header, const char* fmt, TMember TObject::* member)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.header = header,
|
||||||
|
.renderer = [fmt, member](const auto& args) { ImGui::Text(fmt, args.object.*member); },
|
||||||
|
.comparator = [member](const TObject& left, const TObject& right) { return left.*member < right.*member; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TFunc>
|
||||||
|
mijin::Task<> c_MessageBox(const char* titleId, TFunc&& renderFunc)
|
||||||
|
{
|
||||||
|
ImGui::OpenPopup(titleId);
|
||||||
|
|
||||||
|
bool open = true;
|
||||||
|
while (ImGui::BeginPopupModal(titleId, &open, ImGuiWindowFlags_NoResize))
|
||||||
|
{
|
||||||
|
std::invoke(std::forward<TFunc>(renderFunc));
|
||||||
|
ImGui::EndPopup();
|
||||||
|
co_await mijin::c_suspend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... TFormatArgs>
|
||||||
|
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char*> buttons, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||||
|
{
|
||||||
|
const std::string message = std::format(format, std::forward<TFormatArgs>(formatArgs)...);
|
||||||
|
int buttonIdx = -1;
|
||||||
|
|
||||||
|
const ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
float buttonsWidth = 0.f;
|
||||||
|
for (const char* button : buttons) {
|
||||||
|
buttonsWidth += ImGui::CalcTextSize(button).x;
|
||||||
|
}
|
||||||
|
buttonsWidth += static_cast<float>(buttons.size()) * 2.f * style.FramePadding.x;;
|
||||||
|
buttonsWidth += static_cast<float>(buttons.size() - 1) * style.ItemSpacing.x;
|
||||||
|
|
||||||
|
co_await c_MessageBox(titleId, [&]()
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(message.c_str());
|
||||||
|
|
||||||
|
const float offset = 0.5f * (ImGui::GetWindowWidth() - buttonsWidth);
|
||||||
|
ImGui::SetCursorPosX(offset);
|
||||||
|
for (int idx = 0; idx < static_cast<int>(buttons.size()); ++idx)
|
||||||
|
{
|
||||||
|
if (idx != 0) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
if (ImGui::Button(buttons[idx]))
|
||||||
|
{
|
||||||
|
buttonIdx = idx;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
co_return buttonIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct YesNo
|
||||||
|
{
|
||||||
|
enum Value
|
||||||
|
{
|
||||||
|
YES,
|
||||||
|
NO
|
||||||
|
};
|
||||||
|
Value value = Value::NO;
|
||||||
|
|
||||||
|
constexpr YesNo() noexcept = default;
|
||||||
|
constexpr YesNo(const YesNo&) noexcept = default;
|
||||||
|
constexpr YesNo(Value value_) noexcept : value(value_) {}
|
||||||
|
|
||||||
|
constexpr YesNo& operator=(const YesNo&) noexcept = default;
|
||||||
|
|
||||||
|
constexpr auto operator<=>(const YesNo&) const noexcept = default;
|
||||||
|
|
||||||
|
constexpr operator bool() const noexcept { return value == YES; }
|
||||||
|
constexpr bool operator!() const noexcept { return value == NO; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<YesNo::Value DefaultResult = YesNo::NO, typename... TFormatArgs>
|
||||||
|
mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||||
|
{
|
||||||
|
static std::array<const char*, 2> BUTTONS = {"Yes", "No"};
|
||||||
|
const int idx = co_await c_MessageBox(titleId, BUTTONS, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||||
|
switch (idx)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
co_return YesNo::YES;
|
||||||
|
case 1:
|
||||||
|
co_return YesNo::NO;
|
||||||
|
default:
|
||||||
|
co_return DefaultResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace ImRaid
|
} // namespace ImRaid
|
||||||
|
|
||||||
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
||||||
|
60
public/raid/imraid_plot.hpp
Normal file
60
public/raid/imraid_plot.hpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_PUBLIC_RAID_IMRAID_PLOT_HPP_INCLUDED)
|
||||||
|
#define RAID_PUBLIC_RAID_IMRAID_PLOT_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <implot.h>
|
||||||
|
#include <implot_internal.h>
|
||||||
|
|
||||||
|
namespace ImRaid::Plot
|
||||||
|
{
|
||||||
|
inline void AddVerticalLine(double xValue, ImU32 color, float thickness = 1.f, ImAxis dataAxis = ImAxis_X1)
|
||||||
|
{
|
||||||
|
const ImVec2 plotPos = ImPlot::GetPlotPos();
|
||||||
|
const ImVec2 plotSize = ImPlot::GetPlotSize();
|
||||||
|
const float xScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(xValue);
|
||||||
|
const ImVec2 v0 = ImVec2(xScaled, plotPos.y);
|
||||||
|
const ImVec2 v1 = ImVec2(xScaled, plotPos.y + plotSize.y);
|
||||||
|
|
||||||
|
ImPlot::GetPlotDrawList()->AddLine(v0, v1, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddHorizontalLine(double yValue, ImU32 color, float thickness = 1.f, ImAxis dataAxis = ImAxis_Y1)
|
||||||
|
{
|
||||||
|
const ImVec2 plotPos = ImPlot::GetPlotPos();
|
||||||
|
const ImVec2 plotSize = ImPlot::GetPlotSize();
|
||||||
|
const float yScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(yValue);
|
||||||
|
const ImVec2 v0 = ImVec2(plotPos.y, yScaled);
|
||||||
|
const ImVec2 v1 = ImVec2(plotPos.y + plotSize.y, yScaled);
|
||||||
|
|
||||||
|
ImPlot::GetPlotDrawList()->AddLine(v0, v1, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddVerticalRect(double xStart, double xEnd, ImU32 color, ImAxis dataAxis = ImAxis_X1)
|
||||||
|
{
|
||||||
|
const ImVec2 plotPos = ImPlot::GetPlotPos();
|
||||||
|
const ImVec2 plotSize = ImPlot::GetPlotSize();
|
||||||
|
const float xStartScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(xStart);
|
||||||
|
const float xEndScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(xEnd);
|
||||||
|
const ImVec2 v0 = ImVec2(xStartScaled, plotPos.y);
|
||||||
|
const ImVec2 v1 = ImVec2(xEndScaled, plotPos.y + plotSize.y);
|
||||||
|
|
||||||
|
ImPlot::GetPlotDrawList()->AddRectFilled(v0, v1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AddHorizontalRect(double yStart, double yEnd, ImU32 color, ImAxis dataAxis = ImAxis_Y1)
|
||||||
|
{
|
||||||
|
const ImVec2 plotPos = ImPlot::GetPlotPos();
|
||||||
|
const ImVec2 plotSize = ImPlot::GetPlotSize();
|
||||||
|
const float yStartScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(yStart);
|
||||||
|
const float yEndScaled = ImPlot::GetCurrentPlot()->Axes[dataAxis].PlotToPixels(yEnd);
|
||||||
|
const ImVec2 v0 = ImVec2(plotPos.x, yStartScaled);
|
||||||
|
const ImVec2 v1 = ImVec2(plotPos.x + plotSize.x, yEndScaled);
|
||||||
|
|
||||||
|
ImPlot::GetPlotDrawList()->AddRectFilled(v0, v1, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_PLOT_HPP_INCLUDED)
|
45
public/raid/internal/opengl.hpp
Normal file
45
public/raid/internal/opengl.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_INTERNAL_OPENGL_HPP_INCLUDED)
|
||||||
|
#define RAID_INTERNAL_OPENGL_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
struct MixinOpenGLApplication
|
||||||
|
{
|
||||||
|
using GLbitfield = std::uint32_t;
|
||||||
|
using GLint = std::int32_t;
|
||||||
|
using GLuint = std::uint32_t;
|
||||||
|
using GLsizei = std::int32_t;
|
||||||
|
using GLenum = std::uint32_t;
|
||||||
|
using GLfloat = float;
|
||||||
|
|
||||||
|
using glClear_fn_t = void (*)(GLbitfield);
|
||||||
|
using glClearColor_fn_t = void (*)(GLfloat, GLfloat, GLfloat, GLfloat);
|
||||||
|
using glGenTextures_fn_t = void (*)(GLsizei, GLuint*);
|
||||||
|
using glBindTexture_fn_t = void (*)(GLenum, GLuint);
|
||||||
|
using glTexParameteri_fn_t = void (*)(GLenum, GLenum, GLint);
|
||||||
|
using glPixelStorei_fn_t = void (*)(GLenum, GLint);
|
||||||
|
using glTexImage2D_fn_t = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*);
|
||||||
|
using glDeleteTextures_fn_t = void (*)(GLsizei, const GLuint*);
|
||||||
|
|
||||||
|
struct OpenGLData
|
||||||
|
{
|
||||||
|
SDL_GLContext context;
|
||||||
|
|
||||||
|
glClear_fn_t Clear;
|
||||||
|
glClearColor_fn_t ClearColor;
|
||||||
|
glGenTextures_fn_t GenTextures;
|
||||||
|
glBindTexture_fn_t BindTexture;
|
||||||
|
glTexParameteri_fn_t TexParameteri;
|
||||||
|
glPixelStorei_fn_t PixelStorei;
|
||||||
|
glTexImage2D_fn_t TexImage2D;
|
||||||
|
glDeleteTextures_fn_t DeleteTextures;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace raid
|
||||||
|
|
||||||
|
#endif // !defined(RAID_INTERNAL_OPENGL_HPP_INCLUDED)
|
283
public/raid/internal/vulkan.hpp
Normal file
283
public/raid/internal/vulkan.hpp
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(RAID_INTERNAL_VULKAN_HPP_INCLUDED)
|
||||||
|
#define RAID_INTERNAL_VULKAN_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if !defined(RAID_USE_VULKAN_H)
|
||||||
|
#define RAID_USE_VULKAN_H 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if RAID_USE_VULKAN_H
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace raid
|
||||||
|
{
|
||||||
|
struct MixinVulkanApplication
|
||||||
|
{
|
||||||
|
#if !RAID_USE_VULKAN_H
|
||||||
|
// basic types
|
||||||
|
using VkBool32 = std::uint32_t;
|
||||||
|
|
||||||
|
// flags
|
||||||
|
using VkFlags = std::uint32_t;
|
||||||
|
using VkInstanceCreateFlags = VkFlags;
|
||||||
|
using VkDebugUtilsMessengerCreateFlagsEXT = VkFlags;
|
||||||
|
using VkDebugUtilsMessageSeverityFlagsEXT = VkFlags;
|
||||||
|
using VkDebugUtilsMessageTypeFlagsEXT = VkFlags;
|
||||||
|
using VkDebugUtilsMessengerCallbackDataFlagsEXT = VkFlags;
|
||||||
|
using VkQueueFlags = VkFlags;
|
||||||
|
using VkDeviceCreateFlags = VkFlags;
|
||||||
|
using VkDeviceQueueCreateFlags = VkFlags;
|
||||||
|
|
||||||
|
// constants
|
||||||
|
static constexpr std::uint32_t VK_MAX_EXTENSION_NAME_SIZE = 256;
|
||||||
|
static constexpr std::uint32_t VK_MAX_DESCRIPTION_SIZE = 256;
|
||||||
|
|
||||||
|
// enums
|
||||||
|
enum VkResult
|
||||||
|
{
|
||||||
|
VK_SUCCESS = 0,
|
||||||
|
VK_INCOMPLETE = 5
|
||||||
|
};
|
||||||
|
enum VkStructureType
|
||||||
|
{
|
||||||
|
VK_STRUCTURE_TYPE_APPLICATION_INFO = 0,
|
||||||
|
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1,
|
||||||
|
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2,
|
||||||
|
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3,
|
||||||
|
VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2 = 1000059005,
|
||||||
|
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT = 1000128000,
|
||||||
|
VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT = 1000128002,
|
||||||
|
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT = 1000128003,
|
||||||
|
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004
|
||||||
|
};
|
||||||
|
enum VkSystemAllocationScope {};
|
||||||
|
enum VkInternalAllocationType {};
|
||||||
|
enum VkObjectType {};
|
||||||
|
enum VkDebugUtilsMessageSeverityFlagBitsEXT : VkFlags
|
||||||
|
{
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001,
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010,
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100,
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000
|
||||||
|
};
|
||||||
|
enum VkDebugUtilsMessageTypeFlagBitsEXT : VkFlags
|
||||||
|
{
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001,
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002,
|
||||||
|
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004
|
||||||
|
};
|
||||||
|
enum VkQueueFlagBits : VkFlags
|
||||||
|
{
|
||||||
|
VK_QUEUE_GRAPHICS_BIT = 0x00000001
|
||||||
|
};
|
||||||
|
|
||||||
|
// handles
|
||||||
|
using VkDevice = struct VkDevice_*;
|
||||||
|
using VkQueue = struct VkQueue_*;
|
||||||
|
using VkDebugUtilsMessengerEXT = struct VkDebugUtilsMessengerEXT_*;
|
||||||
|
|
||||||
|
struct VkDebugUtilsMessengerCallbackDataEXT;
|
||||||
|
|
||||||
|
// Vulkan function pointer types
|
||||||
|
using PFN_vkVoidFunction = void (*)();
|
||||||
|
// TODO: VKAPI_PTR?
|
||||||
|
using PFN_vkAllocationFunction = void* (*)(void* pUserData, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope);
|
||||||
|
using PFN_vkReallocationFunction = void* (*)(void* pUserData, void* pOriginal, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope);
|
||||||
|
using PFN_vkFreeFunction = void (*)(void* pUserData, void* pMemory);
|
||||||
|
using PFN_vkInternalAllocationNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope);
|
||||||
|
using PFN_vkInternalFreeNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope);
|
||||||
|
using PFN_vkDebugUtilsMessengerCallbackEXT = VkBool32 (*)(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData);
|
||||||
|
|
||||||
|
// structs
|
||||||
|
struct VkExtent3D
|
||||||
|
{
|
||||||
|
std::uint32_t width = 0;
|
||||||
|
std::uint32_t height = 0;
|
||||||
|
std::uint32_t depth = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkLayerProperties
|
||||||
|
{
|
||||||
|
char layerName[VK_MAX_EXTENSION_NAME_SIZE] = {0};
|
||||||
|
std::uint32_t specVersion = 0;
|
||||||
|
std::uint32_t implementationVersion = 0;
|
||||||
|
char description[VK_MAX_DESCRIPTION_SIZE] = {0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkExtensionProperties
|
||||||
|
{
|
||||||
|
char extensionName[VK_MAX_EXTENSION_NAME_SIZE] = {0};
|
||||||
|
std::uint32_t specVersion = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkApplicationInfo
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
const char* pApplicationNane = nullptr;
|
||||||
|
std::uint32_t applicationVersion = 0;
|
||||||
|
const char* pEngineName = nullptr;
|
||||||
|
std::uint32_t engineVersion = 0;
|
||||||
|
std::uint32_t apiVersion = 0;
|
||||||
|
};
|
||||||
|
struct VkInstanceCreateInfo
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkInstanceCreateFlags flags = 0;
|
||||||
|
const VkApplicationInfo* pApplicationInfo = nullptr;
|
||||||
|
std::uint32_t enabledLayerCount = 0;
|
||||||
|
const char* const* ppEnabledLayerNames = nullptr;
|
||||||
|
std::uint32_t enabledExtensionCount = 0;
|
||||||
|
const char* const* ppEnabledExtensionNames = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkDebugUtilsMessengerCreateInfoEXT
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkDebugUtilsMessengerCreateFlagsEXT flags = 0;
|
||||||
|
VkDebugUtilsMessageSeverityFlagsEXT messageSeverity = 0;
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT messageType = 0;
|
||||||
|
PFN_vkDebugUtilsMessengerCallbackEXT pfnUserCallback;
|
||||||
|
void* pUserData = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkDebugUtilsLabelEXT
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
const char* pLabelName = nullptr;
|
||||||
|
float color[4] = {0.f, 0.f, 0.f, 0.f};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkDebugUtilsObjectNameInfoEXT
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkObjectType objectType = static_cast<VkObjectType>(0);
|
||||||
|
std::uint64_t objectHandle = 0;
|
||||||
|
const char* pObjectName = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkDebugUtilsMessengerCallbackDataEXT
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkDebugUtilsMessengerCallbackDataFlagsEXT flags = 0;
|
||||||
|
const char* pMessageIdName;
|
||||||
|
std::int32_t messageIdNumber;
|
||||||
|
const char* pMessage;
|
||||||
|
std::uint32_t queueLabelCount;
|
||||||
|
const VkDebugUtilsLabelEXT* pQueueLabels;
|
||||||
|
std::uint32_t cmdBufLabelCount;
|
||||||
|
const VkDebugUtilsLabelEXT* pCmdBufLabels;
|
||||||
|
std::uint32_t objectCount;
|
||||||
|
const VkDebugUtilsObjectNameInfoEXT* pObjects;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkQueueFamilyProperties
|
||||||
|
{
|
||||||
|
VkQueueFlags queueFlags = 0;
|
||||||
|
std::uint32_t queueCount = 0;
|
||||||
|
std::uint32_t timestampValidBits = 0;
|
||||||
|
VkExtent3D minImageTransferGranularity = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkQueueFamilyProperties2
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
|
||||||
|
void* pNext = nullptr;
|
||||||
|
VkQueueFamilyProperties queueFamilyProperties = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkDeviceQueueCreateInfo
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkDeviceQueueCreateFlags flags = 0;
|
||||||
|
std::uint32_t queueFamilyIndex = 0;
|
||||||
|
std::uint32_t queueCount = 0;
|
||||||
|
const float* pQueuePriorities = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkPhysicalDeviceFeatures;
|
||||||
|
struct VkDeviceCreateInfo
|
||||||
|
{
|
||||||
|
VkStructureType sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||||
|
const void* pNext = nullptr;
|
||||||
|
VkDeviceCreateFlags flags;
|
||||||
|
std::uint32_t queueCreateInfoCount = 0;
|
||||||
|
const VkDeviceQueueCreateInfo* pQueueCreateInfos = nullptr;
|
||||||
|
std::uint32_t enabledLayerCount = 0;
|
||||||
|
const char* const* ppEnabledLayerNames = nullptr;
|
||||||
|
uint32_t enabledExtensionCount = 0;
|
||||||
|
const char* const* ppEnabledExtensionNames = nullptr;
|
||||||
|
const VkPhysicalDeviceFeatures* pEnabledFeatures = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VkAllocationCallbacks
|
||||||
|
{
|
||||||
|
void* pUserData = nullptr;
|
||||||
|
PFN_vkAllocationFunction pfnAllocation = nullptr;
|
||||||
|
PFN_vkReallocationFunction pfnReallocation = nullptr;
|
||||||
|
PFN_vkFreeFunction pfnFree = nullptr;
|
||||||
|
PFN_vkInternalAllocationNotification pfnInternalAllocation = nullptr;
|
||||||
|
PFN_vkInternalFreeNotification pfnInternalFree = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// -- function pointers
|
||||||
|
// instance creation
|
||||||
|
using vkGetInstanceProcAddr_fn_t = PFN_vkVoidFunction (*)(VkInstance instance, const char* pName);
|
||||||
|
using vkEnumerateInstanceLayerProperties_fn_t = VkResult (*)(std::uint32_t* pPropertyCount, VkLayerProperties* pProperties);
|
||||||
|
using vkEnumerateInstanceExtensionProperties_fn_t = VkResult (*)(const char* pLayerName, std::uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
|
||||||
|
using vkCreateDebugUtilsMessengerEXT_fn_t = VkResult (*)(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger);
|
||||||
|
using vkDestroyDebugUtilsMessengerEXT_fn_t = void (*)(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator);
|
||||||
|
using vkCreateInstance_fn_t = VkResult (*)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);
|
||||||
|
using vkDestroyInstance_fn_t = void (*)(VkInstance instance, const VkAllocationCallbacks* pAllocator);
|
||||||
|
|
||||||
|
// device creation
|
||||||
|
using vkEnumeratePhysicalDevices_fn_t = VkResult (*)(VkInstance instance, std::uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
|
||||||
|
using vkGetPhysicalDeviceQueueFamilyProperties2_fn_t = void (*)(VkPhysicalDevice physicalDevice, std::uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
|
||||||
|
using vkCreateDevice_fn_t = VkResult (*)(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);
|
||||||
|
using vkDestroyDevice_fn_t = void (*)(VkDevice device, const VkAllocationCallbacks* pAllocator);
|
||||||
|
using vkDeviceWaitIdle_fn_t = VkResult (*)(VkDevice device);
|
||||||
|
|
||||||
|
// other creation
|
||||||
|
using vkGetDeviceQueue_fn_t = void (*)(VkDevice device, std::uint32_t queueFamilyIndex, std::uint32_t queueIndex, VkQueue* pQueue);
|
||||||
|
#endif // !RAID_USE_VULKAN_H
|
||||||
|
|
||||||
|
struct VulkanData
|
||||||
|
{
|
||||||
|
VkInstance instance;
|
||||||
|
VkDebugUtilsMessengerEXT debugUtilsMessenger;
|
||||||
|
VkPhysicalDevice physicalDevice;
|
||||||
|
VkDevice device;
|
||||||
|
VkQueue queue;
|
||||||
|
VkSurfaceKHR surface;
|
||||||
|
|
||||||
|
vkGetInstanceProcAddr_fn_t GetInstanceProc;
|
||||||
|
vkEnumerateInstanceLayerProperties_fn_t EnumerateInstanceLayerProperties;
|
||||||
|
vkEnumerateInstanceExtensionProperties_fn_t EnumerateInstanceExtensionProperties;
|
||||||
|
vkCreateDebugUtilsMessengerEXT_fn_t CreateDebugUtilsMessengerEXT;
|
||||||
|
vkDestroyDebugUtilsMessengerEXT_fn_t DestroyDebugUtilsMessengerEXT;
|
||||||
|
vkCreateInstance_fn_t CreateInstance;
|
||||||
|
vkDestroyInstance_fn_t DestroyInstance;
|
||||||
|
vkEnumeratePhysicalDevices_fn_t EnumeratePhysicalDevices;
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties2_fn_t GetPhysicalDeviceQueueFamilyProperties2;
|
||||||
|
vkCreateDevice_fn_t CreateDevice;
|
||||||
|
vkDestroyDevice_fn_t DestroyDevice;
|
||||||
|
vkDeviceWaitIdle_fn_t DeviceWaitIdle;
|
||||||
|
vkGetDeviceQueue_fn_t GetDeviceQueue;
|
||||||
|
|
||||||
|
std::uint32_t queueFamilyIndex;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace raid
|
||||||
|
|
||||||
|
#endif // !defined(RAID_INTERNAL_VULKAN_HPP_INCLUDED)
|
@ -4,217 +4,7 @@
|
|||||||
#if !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
|
#if !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
|
||||||
#define RAID_PUBLIC_RAID_RAID_HPP_INCLUDED 1
|
#define RAID_PUBLIC_RAID_RAID_HPP_INCLUDED 1
|
||||||
|
|
||||||
#include <cstdint>
|
#include "./application.hpp"
|
||||||
#include <functional>
|
#include "./config.hpp"
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <mijin/async/coroutine.hpp>
|
|
||||||
#include <mijin/util/bitflags.hpp>
|
|
||||||
#include <mijin/virtual_filesystem/stacked.hpp>
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
namespace raid
|
|
||||||
{
|
|
||||||
inline constexpr int ERR_INIT_FAILED = 100;
|
|
||||||
inline constexpr ImGuiWindowFlags DEFAULT_MAIN_WINDOW_FLAGS = 0
|
|
||||||
| ImGuiWindowFlags_NoBackground
|
|
||||||
| ImGuiWindowFlags_NoDecoration
|
|
||||||
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
|
||||||
| ImGuiWindowFlags_NoNav;
|
|
||||||
|
|
||||||
enum class MessageSeverity : unsigned char
|
|
||||||
{
|
|
||||||
INFO,
|
|
||||||
WARNING,
|
|
||||||
ERROR
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Message
|
|
||||||
{
|
|
||||||
MessageSeverity severity;
|
|
||||||
const char* text;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FontFlags : mijin::BitFlags<FontFlags>
|
|
||||||
{
|
|
||||||
bool pixelSnapH : 1 = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FontConfig
|
|
||||||
{
|
|
||||||
fs::path path;
|
|
||||||
std::vector<std::pair<ImWchar, ImWchar>> glyphRanges;
|
|
||||||
float size = 20.f;
|
|
||||||
FontFlags flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Application
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
SDL_Window* mWindow = nullptr;
|
|
||||||
SDL_GLContext mGLContext = nullptr;
|
|
||||||
|
|
||||||
mijin::StackedFileSystemAdapter mFS;
|
|
||||||
mijin::SimpleTaskLoop mTaskLoop;
|
|
||||||
std::unordered_map<fs::path, ImTextureID> mTextures;
|
|
||||||
|
|
||||||
bool mRunning = true;
|
|
||||||
ImGuiWindowFlags mMainWindowFlags = DEFAULT_MAIN_WINDOW_FLAGS;
|
|
||||||
|
|
||||||
using GLbitfield = std::uint32_t;
|
|
||||||
using GLint = std::int32_t;
|
|
||||||
using GLuint = std::uint32_t;
|
|
||||||
using GLsizei = std::int32_t;
|
|
||||||
using GLenum = std::uint32_t;
|
|
||||||
using GLfloat = float;
|
|
||||||
|
|
||||||
using glClear_fn_t = void (*)(GLbitfield);
|
|
||||||
using glClearColor_fn_t = void (*)(GLfloat, GLfloat, GLfloat, GLfloat);
|
|
||||||
using glGenTextures_fn_t = void (*)(GLsizei, GLuint*);
|
|
||||||
using glBindTexture_fn_t = void (*)(GLenum, GLuint);
|
|
||||||
using glTexParameteri_fn_t = void (*)(GLenum, GLenum, GLint);
|
|
||||||
using glPixelStorei_fn_t = void (*)(GLenum, GLint);
|
|
||||||
using glTexImage2D_fn_t = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*);
|
|
||||||
using glDeleteTextures_fn_t = void (*)(GLsizei, const GLuint*);
|
|
||||||
|
|
||||||
glClear_fn_t glClear;
|
|
||||||
glClearColor_fn_t glClearColor;
|
|
||||||
glGenTextures_fn_t glGenTextures;
|
|
||||||
glBindTexture_fn_t glBindTexture;
|
|
||||||
glTexParameteri_fn_t glTexParameteri;
|
|
||||||
glPixelStorei_fn_t glPixelStorei;
|
|
||||||
glTexImage2D_fn_t glTexImage2D;
|
|
||||||
glDeleteTextures_fn_t glDeleteTextures;
|
|
||||||
public:
|
|
||||||
virtual ~Application() = default;
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
mijin::StackedFileSystemAdapter& getFS() { return mFS; }
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
mijin::SimpleTaskLoop& getLoop() { return mTaskLoop; }
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
ImGuiWindowFlags getMainWindowFlags() const { return mMainWindowFlags; }
|
|
||||||
|
|
||||||
void setMainWindowFlags(ImGuiWindowFlags flags) { mMainWindowFlags = flags; }
|
|
||||||
void requestQuit() { mRunning = false; }
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int run(int argc, char* argv[]);
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
bool loadFonts(std::span<const FontConfig> fonts);
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
bool loadFont(const FontConfig& font)
|
|
||||||
{
|
|
||||||
return loadFonts({&font, 1});
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
ImTextureID getOrLoadTexture(fs::path path);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void render() = 0;
|
|
||||||
virtual std::string getFolderName() = 0;
|
|
||||||
virtual std::string getWindowTitle() = 0;
|
|
||||||
virtual void configureImgui();
|
|
||||||
virtual std::vector<FontConfig> getDefaultFonts();
|
|
||||||
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<typename TArg, typename... TArgs>
|
|
||||||
void msgInfo(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
|
||||||
{
|
|
||||||
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
|
||||||
msgInfo(text);
|
|
||||||
}
|
|
||||||
template<typename TArg, typename... TArgs>
|
|
||||||
void msgWarning(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
|
||||||
{
|
|
||||||
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
|
||||||
msgWarning(text);
|
|
||||||
}
|
|
||||||
template<typename TArg, typename... TArgs>
|
|
||||||
void msgError(fmt::format_string<TArg, TArgs...> format, TArg&& arg, TArgs&&... args)
|
|
||||||
{
|
|
||||||
std::string text = fmt::format(format, std::forward<TArg>(arg), std::forward<TArgs>(args)...);
|
|
||||||
msgError(text);
|
|
||||||
}
|
|
||||||
virtual bool init();
|
|
||||||
virtual void cleanup();
|
|
||||||
private:
|
|
||||||
bool initSDL();
|
|
||||||
bool initGL();
|
|
||||||
bool initImGui();
|
|
||||||
void handleSDLEvents();
|
|
||||||
void loadImGuiConfig();
|
|
||||||
void saveImGuiConfig();
|
|
||||||
};
|
|
||||||
|
|
||||||
using render_cb_t = std::function<void()>;
|
|
||||||
struct QuickAppOptions
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
render_cb_t render;
|
|
||||||
} callbacks;
|
|
||||||
std::string folderName = "raid";
|
|
||||||
std::string windowTitle = "RAID";
|
|
||||||
ImGuiWindowFlags mainWindowFlags = DEFAULT_MAIN_WINDOW_FLAGS;
|
|
||||||
};
|
|
||||||
|
|
||||||
class QuickApp : public Application
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
render_cb_t mRenderCallback;
|
|
||||||
std::string mFolderName;
|
|
||||||
std::string mWindowTitle;
|
|
||||||
public:
|
|
||||||
void preInit(QuickAppOptions options);
|
|
||||||
void render() override;
|
|
||||||
std::string getFolderName() override;
|
|
||||||
std::string getWindowTitle() override;
|
|
||||||
|
|
||||||
static QuickApp& get();
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int runQuick(int argc, char* argv[], QuickAppOptions options);
|
|
||||||
} // namespace raid
|
|
||||||
|
|
||||||
#endif // !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
|
#endif // !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
|
||||||
|
@ -1,93 +1,93 @@
|
|||||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
This license is copied below, and is also available with a FAQ at:
|
This license is copied below, and is also available with a FAQ at:
|
||||||
http://scripts.sil.org/OFL
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
|
||||||
PREAMBLE
|
PREAMBLE
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
development of collaborative font projects, to support the font creation
|
development of collaborative font projects, to support the font creation
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
open framework in which fonts may be shared and improved in partnership
|
open framework in which fonts may be shared and improved in partnership
|
||||||
with others.
|
with others.
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
redistributed and/or sold with any software provided that any reserved
|
redistributed and/or sold with any software provided that any reserved
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
however, cannot be released under any other type of license. The
|
however, cannot be released under any other type of license. The
|
||||||
requirement for fonts to remain under this license does not apply
|
requirement for fonts to remain under this license does not apply
|
||||||
to any document created using the fonts or their derivatives.
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
DEFINITIONS
|
DEFINITIONS
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
include source files, build scripts and documentation.
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
copyright statement(s).
|
copyright statement(s).
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
"Original Version" refers to the collection of Font Software components as
|
||||||
distributed by the Copyright Holder(s).
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
new environment.
|
new environment.
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
writer or other person who contributed to the Font Software.
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
PERMISSION & CONDITIONS
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
Software, subject to the following conditions:
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
1) Neither the Font Software nor any of its individual components,
|
||||||
in Original or Modified Versions, may be sold by itself.
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
redistributed and/or sold with any software, provided that each copy
|
redistributed and/or sold with any software, provided that each copy
|
||||||
contains the above copyright notice and this license. These can be
|
contains the above copyright notice and this license. These can be
|
||||||
included either as stand-alone text files, human-readable headers or
|
included either as stand-alone text files, human-readable headers or
|
||||||
in the appropriate machine-readable metadata fields within text or
|
in the appropriate machine-readable metadata fields within text or
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
presented to the users.
|
presented to the users.
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
Software shall not be used to promote, endorse or advertise any
|
Software shall not be used to promote, endorse or advertise any
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
permission.
|
permission.
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
must be distributed entirely under this license, and must not be
|
must be distributed entirely under this license, and must not be
|
||||||
distributed under any other license. The requirement for fonts to
|
distributed under any other license. The requirement for fonts to
|
||||||
remain under this license does not apply to any document created
|
remain under this license does not apply to any document created
|
||||||
using the Font Software.
|
using the Font Software.
|
||||||
|
|
||||||
TERMINATION
|
TERMINATION
|
||||||
This license becomes null and void if any of the above conditions are
|
This license becomes null and void if any of the above conditions are
|
||||||
not met.
|
not met.
|
||||||
|
|
||||||
DISCLAIMER
|
DISCLAIMER
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
194
site_scons/site_tools/jinja.py
Normal file
194
site_scons/site_tools/jinja.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# source: https://github.com/hgomersall/scons-jinja
|
||||||
|
|
||||||
|
# 2025-03-28: Added JINJA_GLOBALS options.
|
||||||
|
# 2023-12-02: Added JINJA_FILTERS option.
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 Henry Gomersall <heng@kedevelopments.co.uk>
|
||||||
|
# Copyright (c) 2025 Patrick Wuttke <mewin@mewin.de>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
# a copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
# the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||||
|
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import SCons.Builder
|
||||||
|
import SCons.Tool
|
||||||
|
from SCons.Errors import StopError
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
from jinja2 import FileSystemLoader
|
||||||
|
from jinja2.utils import open_if_exists
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
class FileSystemLoaderRecorder(FileSystemLoader):
|
||||||
|
''' A wrapper around FileSystemLoader that records files as they are
|
||||||
|
loaded. These are contained within loaded_filenames set attribute.
|
||||||
|
'''
|
||||||
|
def __init__(self, searchpath, encoding='utf-8'):
|
||||||
|
|
||||||
|
self.loaded_filenames = set()
|
||||||
|
super(FileSystemLoaderRecorder, self).__init__(searchpath, encoding)
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
'''Overwritten FileSystemLoader.get_source method that extracts the
|
||||||
|
filename that is used to load each filename and adds it to
|
||||||
|
self.loaded_filenames.
|
||||||
|
'''
|
||||||
|
for searchpath in self.searchpath:
|
||||||
|
filename = os.path.join(searchpath, template)
|
||||||
|
f = open_if_exists(filename)
|
||||||
|
if f is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
contents = f.read().decode(self.encoding)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
self.loaded_filenames.add(filename)
|
||||||
|
|
||||||
|
return super(FileSystemLoaderRecorder, self).get_source(
|
||||||
|
environment, template)
|
||||||
|
|
||||||
|
# If the template isn't found, then we have to drop out.
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def jinja_scanner(node, env, path):
|
||||||
|
|
||||||
|
# Instantiate the file as necessary
|
||||||
|
node.get_text_contents()
|
||||||
|
|
||||||
|
node_dir = os.path.dirname(str(node))
|
||||||
|
|
||||||
|
template_dir, filename = os.path.split(str(node))
|
||||||
|
|
||||||
|
template_search_path = ([template_dir] +
|
||||||
|
env.subst(env['JINJA_TEMPLATE_SEARCHPATH']))
|
||||||
|
template_loader = FileSystemLoaderRecorder(template_search_path)
|
||||||
|
|
||||||
|
jinja_env = jinja2.Environment(loader=template_loader,
|
||||||
|
extensions=['jinja2.ext.do'], **env['JINJA_ENVIRONMENT_VARS'])
|
||||||
|
jinja_env.filters.update(env['JINJA_FILTERS'])
|
||||||
|
jinja_env.globals.update(env['JINJA_GLOBALS'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
template = jinja_env.get_template(filename)
|
||||||
|
except TemplateNotFound as e:
|
||||||
|
raise StopError('Missing template: ' +
|
||||||
|
os.path.join(template_dir, str(e)))
|
||||||
|
|
||||||
|
# We need to render the template to do all the necessary loading.
|
||||||
|
#
|
||||||
|
# It's necessary to respond to missing templates by grabbing
|
||||||
|
# the content as the exception is raised. This makes sure of the
|
||||||
|
# existence of the file upon which the current scanned node depends.
|
||||||
|
#
|
||||||
|
# I suspect that this is pretty inefficient, but it does
|
||||||
|
# work reliably.
|
||||||
|
context = env['JINJA_CONTEXT']
|
||||||
|
|
||||||
|
last_missing_file = ''
|
||||||
|
while True:
|
||||||
|
|
||||||
|
try:
|
||||||
|
template.render(**context)
|
||||||
|
except TemplateNotFound as e:
|
||||||
|
if last_missing_file == str(e):
|
||||||
|
# We've already been round once for this file,
|
||||||
|
# so need to raise
|
||||||
|
raise StopError('Missing template: ' +
|
||||||
|
os.path.join(template_dir, str(e)))
|
||||||
|
|
||||||
|
last_missing_file = str(e)
|
||||||
|
# Find where the template came from (using the same ordering
|
||||||
|
# as Jinja uses).
|
||||||
|
for searchpath in template_search_path:
|
||||||
|
filename = os.path.join(searchpath, last_missing_file)
|
||||||
|
if os.path.exists(filename):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
env.File(filename).get_text_contents()
|
||||||
|
continue
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# Get all the files that were loaded. The set includes the current node,
|
||||||
|
# so we remove that.
|
||||||
|
found_nodes_names = list(template_loader.loaded_filenames)
|
||||||
|
try:
|
||||||
|
found_nodes_names.remove(str(node))
|
||||||
|
except ValueError as e:
|
||||||
|
raise StopError('Missing template node: ' + str(node))
|
||||||
|
|
||||||
|
return [env.File(f) for f in found_nodes_names]
|
||||||
|
|
||||||
|
def render_jinja_template(target, source, env):
|
||||||
|
|
||||||
|
output_str = ''
|
||||||
|
|
||||||
|
if not source:
|
||||||
|
source = [f'{target}.jinja']
|
||||||
|
|
||||||
|
for template_file in source:
|
||||||
|
|
||||||
|
template_dir, filename = os.path.split(str(template_file))
|
||||||
|
|
||||||
|
template_search_path = ([template_dir] +
|
||||||
|
env.subst(env['JINJA_TEMPLATE_SEARCHPATH']))
|
||||||
|
template_loader = FileSystemLoaderRecorder(template_search_path)
|
||||||
|
|
||||||
|
jinja_env = jinja2.Environment(loader=template_loader,
|
||||||
|
extensions=['jinja2.ext.do'], **env['JINJA_ENVIRONMENT_VARS'])
|
||||||
|
|
||||||
|
jinja_env.filters.update(env['JINJA_FILTERS'])
|
||||||
|
jinja_env.globals.update(env['JINJA_GLOBALS'])
|
||||||
|
template = jinja_env.get_template(filename)
|
||||||
|
|
||||||
|
context = env['JINJA_CONTEXT']
|
||||||
|
template.render(**context)
|
||||||
|
|
||||||
|
output_str += template.render(**context)
|
||||||
|
|
||||||
|
with open(str(target[0]), 'w') as target_file:
|
||||||
|
target_file.write(output_str)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generate(env):
|
||||||
|
|
||||||
|
env.SetDefault(JINJA_CONTEXT={})
|
||||||
|
env.SetDefault(JINJA_ENVIRONMENT_VARS={})
|
||||||
|
env.SetDefault(JINJA_FILTERS={})
|
||||||
|
env.SetDefault(JINJA_GLOBALS={})
|
||||||
|
env.SetDefault(JINJA_TEMPLATE_SEARCHPATH=[])
|
||||||
|
|
||||||
|
env['BUILDERS']['Jinja'] = SCons.Builder.Builder(
|
||||||
|
action=render_jinja_template)
|
||||||
|
|
||||||
|
scanner = env.Scanner(function=jinja_scanner,
|
||||||
|
skeys=['.jinja'])
|
||||||
|
|
||||||
|
env.Append(SCANNERS=scanner)
|
||||||
|
|
||||||
|
def exists(env):
|
||||||
|
try:
|
||||||
|
import jinja2
|
||||||
|
except ImportError as e:
|
||||||
|
raise StopError(ImportError, e.message)
|
Loading…
x
Reference in New Issue
Block a user