Compare commits
19 Commits
v0.0.1
...
ee4ea4ae0a
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||
|
||||
# Generated files
|
||||
*.gen.*
|
||||
!*.gen.*.jinja
|
||||
|
||||
# Prerequisites
|
||||
*.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 = {
|
||||
'PROJECT_NAME': 'RAID Framework',
|
||||
'CXX_NO_EXCEPTIONS': True
|
||||
'PROJECT_NAME': 'RAID Framework'
|
||||
}
|
||||
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
|
||||
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
|
||||
env = env.Module('private/raid/SModule')
|
||||
|
||||
|
||||
3
SModule
3
SModule
@@ -3,7 +3,8 @@ Import('env')
|
||||
|
||||
public_dir = env.Dir('public')
|
||||
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 = env.Module('private/raid/SModule')
|
||||
|
||||
|
||||
1
external/scons-plus-plus
vendored
1
external/scons-plus-plus
vendored
Submodule external/scons-plus-plus deleted from c994752c32
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,18 @@ import json
|
||||
|
||||
Import('env')
|
||||
|
||||
if not hasattr(env, 'Jinja'):
|
||||
env.Error('RAID requires Jinja.')
|
||||
|
||||
src_files = Split("""
|
||||
application.cpp
|
||||
fonts.gen.cpp
|
||||
stb_image.cpp
|
||||
""")
|
||||
|
||||
env.Jinja("fonts.gen.cpp")
|
||||
env.Jinja("fonts.gen.hpp")
|
||||
|
||||
with open('../../dependencies.json', 'r') as f:
|
||||
dependencies = env.DepsFromJson(json.load(f))
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <thread>
|
||||
#include <fmt/base.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <mijin/detect.hpp>
|
||||
#include <mijin/debug/assert.hpp>
|
||||
#include <mijin/platform/folders.hpp>
|
||||
#include <mijin/util/iterators.hpp>
|
||||
@@ -12,9 +14,11 @@
|
||||
#include <mijin/virtual_filesystem/mapping.hpp>
|
||||
#include <mijin/virtual_filesystem/relative.hpp>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#include <backends/imgui_impl_sdl3.h>
|
||||
#include <stb_image.h>
|
||||
#include "./fonts.gen.hpp"
|
||||
|
||||
namespace raid
|
||||
{
|
||||
@@ -59,6 +63,12 @@ int stbiEof(void* user)
|
||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||
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)
|
||||
@@ -94,8 +104,16 @@ int Application::run(int argc, char** argv)
|
||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos);
|
||||
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
|
||||
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::PopStyleVar(static_cast<int>(mMainWindowStyles.size()));
|
||||
|
||||
render();
|
||||
|
||||
mTaskLoop.tick();
|
||||
@@ -103,15 +121,26 @@ int Application::run(int argc, char** argv)
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
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)
|
||||
{
|
||||
ImGui::UpdatePlatformWindows();
|
||||
ImGui::RenderPlatformWindowsDefault();
|
||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
||||
|
||||
if (mConfig.graphicsApi == GraphicsAPI::OPENGL) {
|
||||
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(mWindow);
|
||||
@@ -212,23 +241,60 @@ ImTextureID Application::getOrLoadTexture(fs::path path)
|
||||
}
|
||||
|
||||
// create texture
|
||||
GLuint texture = 0;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
ImTextureID texture = 0;
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
case GraphicsAPI::OPENGL:
|
||||
{
|
||||
GLuint glTexture = 0;
|
||||
gl.GenTextures(1, &glTexture);
|
||||
gl.BindTexture(GL_TEXTURE_2D, glTexture);
|
||||
|
||||
// setup texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
// setup texture
|
||||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// upload image
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
|
||||
// upload image
|
||||
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
ImGuiIO& imguiIO = ImGui::GetIO();
|
||||
@@ -241,10 +307,15 @@ void Application::configureImgui()
|
||||
std::vector<FontConfig> Application::getDefaultFonts()
|
||||
{
|
||||
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)
|
||||
{
|
||||
switch (message.severity)
|
||||
@@ -274,6 +345,11 @@ void Application::handleSDLEvent(const SDL_Event& event)
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleSDLError(const char* message)
|
||||
{
|
||||
msgError("SDL: {}", message);
|
||||
}
|
||||
|
||||
bool Application::init()
|
||||
{
|
||||
auto addConfigDir = [&](const fs::path& path)
|
||||
@@ -294,6 +370,9 @@ bool Application::init()
|
||||
);
|
||||
};
|
||||
|
||||
mMemoryFS = mFS.emplaceAdapter<mijin::MemoryFileSystemAdapter>();
|
||||
initMemoryFS();
|
||||
|
||||
addConfigDir(mijin::getKnownFolder(mijin::KnownFolder::USER_CONFIG_ROOT) / getFolderName());
|
||||
addDataDir(mijin::getKnownFolder(mijin::KnownFolder::USER_DATA_ROOT) / getFolderName());
|
||||
|
||||
@@ -305,7 +384,7 @@ bool Application::init()
|
||||
const fs::path path = std::move(*pathOpt);
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -318,7 +397,7 @@ bool Application::init()
|
||||
createUserDir("/data");
|
||||
|
||||
// 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");
|
||||
addDataDir(fs::current_path() / "data/data");
|
||||
#endif
|
||||
@@ -355,9 +434,19 @@ bool Application::init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!initGL())
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
return false;
|
||||
case GraphicsAPI::OPENGL:
|
||||
if (!initOpenGL())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
if (!initVulkan())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!initImGui())
|
||||
{
|
||||
@@ -383,14 +472,29 @@ void Application::cleanup()
|
||||
}
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
for (const auto& [path, texture] : mTextures)
|
||||
{
|
||||
const GLuint asUint = static_cast<GLuint>(texture);
|
||||
glDeleteTextures(1, &asUint);
|
||||
for (const auto& [_, texture] : mTextures) {
|
||||
destroyTexture(texture);
|
||||
}
|
||||
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.instance != nullptr)
|
||||
{
|
||||
vk.DestroyInstance(vk.instance, nullptr);
|
||||
}
|
||||
SDL_Vulkan_UnloadLibrary();
|
||||
break;
|
||||
}
|
||||
if (mWindow != nullptr)
|
||||
{
|
||||
@@ -401,54 +505,159 @@ void Application::cleanup()
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
// GL attributes must be set before window creation
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
msgInfo("SDL video driver: {}", SDL_GetCurrentVideoDriver());
|
||||
|
||||
// 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);
|
||||
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(
|
||||
/* title = */ getWindowTitle().c_str(),
|
||||
/* w = */ 1280,
|
||||
/* h = */ 720,
|
||||
/* flags = */ WINDOW_FLAGS
|
||||
/* flags = */ windowFlags
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::initGL()
|
||||
bool Application::initOpenGL()
|
||||
{
|
||||
mGLContext = SDL_GL_CreateContext(mWindow);
|
||||
gl.context = SDL_GL_CreateContext(mWindow);
|
||||
if (mWindow == nullptr)
|
||||
{
|
||||
msgError("Error creating SDL window: {}.", SDL_GetError());
|
||||
msgError("Error creating OpenGL context: {}.", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
||||
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||
SDL_GL_SetSwapInterval(1); // enable vsync, at least for now
|
||||
|
||||
glClear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
||||
glClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
||||
glGenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
||||
glBindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
||||
glTexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
||||
glPixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
||||
glTexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
||||
glDeleteTextures = reinterpret_cast<glDeleteTextures_fn_t>(SDL_GL_GetProcAddress("glDeleteTextures"));
|
||||
gl.Clear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
||||
gl.ClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
||||
gl.GenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
||||
gl.BindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
||||
gl.TexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
||||
gl.PixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
||||
gl.TexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
||||
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.CreateInstance = reinterpret_cast<vkCreateInstance_fn_t>(vk.GetInstanceProc(nullptr, "vkCreateInstance"));
|
||||
|
||||
const VkApplicationInfo applicationInfo = {
|
||||
.apiVersion = vkMakeApiVersion(0, 1, 3, 0) // TODO: probably should let the user specify this?
|
||||
};
|
||||
const VkInstanceCreateInfo instanceCreateInfo = {
|
||||
.pApplicationInfo = &applicationInfo
|
||||
};
|
||||
|
||||
if (const VkResult result = vk.CreateInstance(&instanceCreateInfo, nullptr, &vk.instance); result != VK_SUCCESS)
|
||||
{
|
||||
msgError("Error creating Vulkan instance: {: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"));
|
||||
|
||||
// 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: {: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)
|
||||
{
|
||||
if (queueFamilyProperties[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||
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_surface"
|
||||
};
|
||||
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: {: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;
|
||||
}
|
||||
@@ -457,12 +666,18 @@ bool Application::initImGui()
|
||||
{
|
||||
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.");
|
||||
return false;
|
||||
}
|
||||
|
||||
imguiContext->ErrorCallbackUserData = this;
|
||||
imguiContext->ErrorCallback = [](ImGuiContext* /* ctx */, void* userData, const char* msg) {
|
||||
static_cast<Application*>(userData)->handleSDLError(msg);
|
||||
};
|
||||
|
||||
loadImGuiConfig();
|
||||
|
||||
configureImgui();
|
||||
@@ -474,15 +689,33 @@ bool Application::initImGui()
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
|
||||
|
||||
if (imguiIO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
msgError("Error initializing ImGui OpenGL3 backend.");
|
||||
return false;
|
||||
if (!(imguiIO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports)) {
|
||||
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
|
||||
|
||||
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,8 @@ Import('env')
|
||||
|
||||
src_files = Split("""
|
||||
main.cpp
|
||||
|
||||
application.cpp
|
||||
""")
|
||||
|
||||
prog_app = env.UnityProgram(
|
||||
|
||||
57
private/raid_test/application.cpp
Normal file
57
private/raid_test/application.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
#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);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::configureImgui()
|
||||
{
|
||||
raid::Application::configureImgui();
|
||||
ImGuiIO& imguiIO = ImGui::GetIO();
|
||||
imguiIO.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
}
|
||||
|
||||
void Application::render()
|
||||
{
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("Quit"))
|
||||
{
|
||||
requestQuit();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
ImGui::Text("hi");
|
||||
|
||||
ImGui::ShowMetricsWindow();
|
||||
ImGui::Begin("Test");
|
||||
ImGui::Text("Test Content");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
std::string Application::getFolderName()
|
||||
{
|
||||
return "raid_test_app";
|
||||
}
|
||||
|
||||
std::string Application::getWindowTitle()
|
||||
{
|
||||
return "RAID Test Application";
|
||||
}
|
||||
} // namespace raid_test
|
||||
24
private/raid_test/application.hpp
Normal file
24
private/raid_test/application.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(RAID_TEST_APPLICATION_HPP_INCLUDED)
|
||||
#define RAID_TEST_APPLICATION_HPP_INCLUDED 1
|
||||
|
||||
#include "raid/raid.hpp"
|
||||
|
||||
namespace raid_test
|
||||
{
|
||||
class Application : public raid::Application
|
||||
{
|
||||
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)
|
||||
@@ -1,40 +1,7 @@
|
||||
|
||||
#include "raid/raid.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();
|
||||
}
|
||||
}
|
||||
#include "./application.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
return raid::runQuick(argc, argv, {
|
||||
.callbacks = {
|
||||
.render = &render
|
||||
},
|
||||
.folderName = "raid_test_app",
|
||||
.windowTitle = "RAID Test App",
|
||||
.mainWindowFlags = raid::DEFAULT_MAIN_WINDOW_FLAGS | ImGuiWindowFlags_MenuBar
|
||||
});
|
||||
return raid_test::gApplication.run(argc, argv);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
#if !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
||||
#define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <mijin/debug/assert.hpp>
|
||||
|
||||
namespace ImRaid
|
||||
{
|
||||
@@ -47,6 +54,106 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im
|
||||
}
|
||||
return clicked;
|
||||
}
|
||||
|
||||
struct DataTableState
|
||||
{
|
||||
std::vector<std::size_t> sortedIndices;
|
||||
std::size_t sortColumn = 0;
|
||||
bool sortDescending = false;
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
const ImGuiTableColumnSortSpecs& specs = *sortSpecs->Specs;
|
||||
state.dirty |= (specs.ColumnIndex != state.sortColumn);
|
||||
state.sortColumn = specs.ColumnIndex;
|
||||
state.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) {
|
||||
return options.columns[state.sortColumn].comparator(data[leftIdx], data[rightIdx]);
|
||||
});
|
||||
state.dirty = false;
|
||||
}
|
||||
|
||||
for (std::size_t indexIdx = 0; indexIdx < state.sortedIndices.size(); ++indexIdx)
|
||||
{
|
||||
const std::size_t dataIdx = state.sortDescending ? state.sortedIndices[state.sortedIndices.size() - indexIdx - 1]
|
||||
: state.sortedIndices[indexIdx];
|
||||
const TObject& object = data[dataIdx];
|
||||
ImGui::TableNextRow();
|
||||
|
||||
for (const DataTableColumn<TObject>& column : options.columns)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
column.renderer({
|
||||
.object = object
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
} // namespace ImRaid
|
||||
|
||||
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_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)
|
||||
176
public/raid/internal/vulkan.hpp
Normal file
176
public/raid/internal/vulkan.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
#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
|
||||
// flags
|
||||
using VkFlags = std::uint32_t;
|
||||
using VkInstanceCreateFlags = VkFlags;
|
||||
using VkQueueFlags = VkFlags;
|
||||
using VkDeviceCreateFlags = VkFlags;
|
||||
using VkDeviceQueueCreateFlags = VkFlags;
|
||||
|
||||
// enums
|
||||
enum VkResult
|
||||
{
|
||||
VK_SUCCESS = 0
|
||||
};
|
||||
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
|
||||
};
|
||||
enum VkSystemAllocationScope {};
|
||||
enum VkInternalAllocationType {};
|
||||
enum VkQueueFlagBits : VkFlags
|
||||
{
|
||||
VK_QUEUE_GRAPHICS_BIT = 0x00000001
|
||||
};
|
||||
|
||||
// handles
|
||||
using VkDevice = struct VkDevice_*;
|
||||
using VkQueue = struct VkQueue_*;
|
||||
|
||||
// structs
|
||||
struct VkExtent3D
|
||||
{
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint32_t depth = 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 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;
|
||||
};
|
||||
|
||||
// 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);
|
||||
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
|
||||
using vkGetInstanceProcAddr_fn_t = PFN_vkVoidFunction (*)(VkInstance instance, const char* pName);
|
||||
using vkCreateInstance_fn_t = VkResult (*)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);
|
||||
using vkDestroyInstance_fn_t = void (*)(VkInstance instance, const VkAllocationCallbacks* pAllocator);
|
||||
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);
|
||||
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;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkDevice device;
|
||||
VkQueue queue;
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
vkGetInstanceProcAddr_fn_t GetInstanceProc;
|
||||
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)
|
||||
@@ -6,12 +6,17 @@
|
||||
|
||||
#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
|
||||
{
|
||||
@@ -21,6 +26,7 @@ inline constexpr ImGuiWindowFlags DEFAULT_MAIN_WINDOW_FLAGS = 0
|
||||
| ImGuiWindowFlags_NoDecoration
|
||||
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
||||
| ImGuiWindowFlags_NoNav;
|
||||
inline constexpr const char* DEFAULT_FONT_PATH = "/data/fonts/NotoSans-Regular.ttf";
|
||||
|
||||
enum class MessageSeverity : unsigned char
|
||||
{
|
||||
@@ -48,49 +54,65 @@ struct FontConfig
|
||||
FontFlags flags;
|
||||
};
|
||||
|
||||
class Application
|
||||
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;
|
||||
SDL_GLContext mGLContext = 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;
|
||||
|
||||
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;
|
||||
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; }
|
||||
|
||||
@@ -98,6 +120,8 @@ public:
|
||||
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]]
|
||||
@@ -115,14 +139,18 @@ public:
|
||||
[[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);
|
||||
|
||||
void msgInfo(const char* text)
|
||||
{
|
||||
@@ -179,7 +207,8 @@ protected:
|
||||
virtual void cleanup();
|
||||
private:
|
||||
bool initSDL();
|
||||
bool initGL();
|
||||
bool initOpenGL();
|
||||
bool initVulkan();
|
||||
bool initImGui();
|
||||
void handleSDLEvents();
|
||||
void loadImGuiConfig();
|
||||
@@ -205,6 +234,8 @@ private:
|
||||
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;
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
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 license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
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
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers 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.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
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
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
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
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
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 license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
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
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers 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.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
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
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
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
|
||||
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)
|
||||
Reference in New Issue
Block a user