Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-12-02 03:16:07 +02:00
commit 8d3749cc6a
14 changed files with 651 additions and 0 deletions

26
.clang-format Normal file
View File

@@ -0,0 +1,26 @@
UseTab: ForIndentation
TabWidth: 4
IndentWidth: 4
ColumnLimit: 80
AlignEscapedNewlines: DontAlign
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
BasedOnStyle: WebKit
BraceWrapping:
AfterFunction: true
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: true
BreakConstructorInitializers: BeforeComma
IndentPPDirectives: AfterHash
IndentRequiresClause: false
InsertNewlineAtEOF: true
LineEnding: LF
NamespaceIndentation: None
PointerAlignment: Right # east pointer
QualifierAlignment: Right # east const
RemoveSemicolon: true
RequiresClausePosition: WithFollowing
RequiresExpressionIndentation: OuterScope
SpaceAfterTemplateKeyword: false

5
.clangd Normal file
View File

@@ -0,0 +1,5 @@
CompileFlags:
Add: [
"-isystem", "/nix/store/9hy16b0ba5bz0gd309rvhh119mn44f4j-libcxx-21.1.2-dev/include/c++/v1",
"-isystem", "/nix/store/jkqdwnd0ifr3fkjif3lz003an2fhbh17-glibc-2.40-66-dev/include"
]

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
[Bb]uild*
result
.cache
.direnv

59
flake.lock generated Normal file
View File

@@ -0,0 +1,59 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1764517877,
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c",
"revCount": 904649,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.904649%2Brev-2d293cbfa5a793b4c50d17c05ef9e385b90edf6c/019ad7f2-e8f3-79e9-ad92-dd7a45c069d3/source.tar.gz?rev=2d293cbfa5a793b4c50d17c05ef9e385b90edf6c&revCount=904649"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.1"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

72
flake.nix Normal file
View File

@@ -0,0 +1,72 @@
{
description = "My flake";
inputs = {
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
nativeBuildInputs = with pkgs; [
vulkan-loader
vulkan-memory-allocator
vulkan-validation-layers
vk-bootstrap
openxr-loader
wayland
zlib
sdl3
];
buildInputs = with pkgs; [
meson
ninja
pkg-config
];
in
{
devShells.default =
pkgs.mkShell.override { stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.llvmPackages_21.stdenv; }
{
packages =
with pkgs;
[
llvmPackages_21.clang-tools
lldb
codespell
doxygen
gtest
cppcheck
]
++ buildInputs
++ nativeBuildInputs
++ pkgs.lib.optionals pkgs.stdenv.isLinux (
with pkgs;
[
valgrind-light
]
);
shellHook = ''
export VK_BOOTSTRAP_LIB=${pkgs.vk-bootstrap}
export VK_BOOTSTRAP_DEV=${pkgs.vk-bootstrap.dev}
cat > .clangd << 'EOF'
CompileFlags:
Add: [
"-isystem", "${pkgs.llvmPackages_21.libcxx.dev}/include/c++/v1",
"-isystem", "${pkgs.glibc.dev}/include"
]
EOF
'';
};
}
);
}

50
meson.build Normal file
View File

@@ -0,0 +1,50 @@
project('vr-compositor', 'cpp',
version: '0.1',
default_options: ['cpp_std=c++23']
)
cc = meson.get_compiler('cpp')
wayland_dep = dependency('wayland-server')
vulkan_dep = dependency('vulkan')
openxr_dep = dependency('openxr')
zlib_dep = dependency('zlib')
sdl3_dep = dependency('sdl3')
vkbootstrap_dev = get_option('vkbootstrap_dev')
vkbootstrap_lib = get_option('vkbootstrap_lib')
vkbootstrap_inc = include_directories(
join_paths(vkbootstrap_dev, 'include')
)
vkbootstrap_dep = cc.find_library(
'vk-bootstrap',
dirs: join_paths(vkbootstrap_lib, 'lib'),
required: true,
)
add_project_arguments(
'-Wall',
'-Wextra',
'-Wpedantic',
'-Werror',
language : ['c', 'cpp']
)
exe = executable('vr-compositor',
[
'src/main.cpp',
'src/Logger.cpp',
'src/Application.cpp',
],
include_directories: vkbootstrap_inc,
dependencies: [
wayland_dep,
vulkan_dep,
openxr_dep,
vkbootstrap_dep,
zlib_dep,
sdl3_dep,
]
)

2
meson_options.txt Normal file
View File

@@ -0,0 +1,2 @@
option('vkbootstrap_dev', type: 'string', description: 'vk-bootstrap dev output path')
option('vkbootstrap_lib', type: 'string', description: 'vk-bootstrap lib output path')

116
src/Application.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include "Application.h"
#include <SDL3/SDL_init.h>
#include <iostream>
#include <print>
#include <SDL3/SDL_vulkan.h>
#include <VkBootstrap.h>
#include <openxr/openxr.h>
namespace Lunar {
Application::Application()
{
vkb::InstanceBuilder instance_builder {};
instance_builder.request_validation_layers()
.set_app_name("Lunar")
.set_engine_name("Lunar")
.require_api_version(1, 0, 0)
.set_debug_callback_user_data_pointer(this)
.set_debug_callback(
[](VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
VkDebugUtilsMessengerCallbackDataEXT const *callback_data,
void *user_data) {
auto app { reinterpret_cast<Application *>(user_data) };
auto level = Logger::Level::Debug;
if (message_severity
& VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
level = Logger::Level::Error;
} else if (message_severity
& VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
level = Logger::Level::Warning;
} else if (message_severity
& VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
level = Logger::Level::Info;
}
app->m_logger.log(level,
std::format("[{}] {}",
vkb::to_string_message_type(message_type),
callback_data->pMessage));
return VK_FALSE;
});
auto const instance_builder_ret { instance_builder.build() };
if (!instance_builder_ret) {
std::println(std::cerr, "Failed to create Vulkan instance. Error: {}",
instance_builder_ret.error().message());
throw std::runtime_error("App init fail");
}
m_vkb_instance = instance_builder_ret.value();
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::println(std::cerr, "Failed to initialize SDL.");
throw std::runtime_error("App init fail");
}
m_window = SDL_CreateWindow(
"Lunar", 1280, 720, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
if (!m_window) {
m_logger.err("Failed to create SDL window");
throw std::runtime_error("App init fail");
}
if (!SDL_Vulkan_CreateSurface(
m_window, m_vkb_instance, nullptr, &m_vk_surface)) {
m_logger.err("Failed to create vulkan surface");
throw std::runtime_error("App init fail");
}
vkb::PhysicalDeviceSelector phys_device_selector { m_vkb_instance };
phys_device_selector.set_surface(m_vk_surface)
.add_required_extensions({ VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME });
auto physical_device_selector_return { phys_device_selector.select() };
if (!physical_device_selector_return) {
std::println(std::cerr, "Failed to find Vulkan device. Error: {}",
physical_device_selector_return.error().message());
throw std::runtime_error("App init fail");
}
auto phys_device = physical_device_selector_return.value();
m_logger.info("App init done!");
}
Application::~Application()
{
SDL_Vulkan_DestroySurface(m_vkb_instance, m_vk_surface, nullptr);
SDL_DestroyWindow(m_window);
SDL_Quit();
vkb::destroy_instance(m_vkb_instance);
m_logger.info("App destroy done!");
}
auto Application::run() -> void
{
SDL_Event e;
while (m_running) {
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT)
m_running = false;
}
// nothing else
}
}
} // namespace Lunar

26
src/Application.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <SDL3/SDL_video.h>
#include <VkBootstrap.h>
#include <vulkan/vulkan_core.h>
#include "src/Logger.h"
namespace Lunar {
struct Application {
Application();
~Application();
auto run() -> void;
private:
vkb::Instance m_vkb_instance;
VkSurfaceKHR m_vk_surface { nullptr };
SDL_Window *m_window { nullptr };
Logger m_logger { "Lunar" };
bool m_running { true };
};
} // namespace Lunar

205
src/Logger.cpp Normal file
View File

@@ -0,0 +1,205 @@
#include "Logger.h"
#include "Util.h"
#include <chrono>
#include <filesystem>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#ifdef _WIN32
# include <shlobj.h> // SHGetKnownFolderPath
# include <windows.h>
#elif defined(__APPLE__)
# include <pwd.h>
# include <sys/types.h>
# include <unistd.h>
#else
# include <pwd.h>
# include <unistd.h>
#endif
#ifndef __EMSCRIPTEN__
# include <zlib.h>
#endif
#define FG_BLUE "\033[34m"
#define FG_RED "\033[31m"
#define FG_YELLOW "\033[33m"
#define FG_GRAY "\033[90m"
#define ANSI_RESET "\033[0m"
std::filesystem::path get_log_path(std::string_view app_name)
{
#ifdef _WIN32
PWSTR path = nullptr;
SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path);
std::wstring wpath(path);
CoTaskMemFree(path);
return std::filesystem::path(wpath) / app_name / "logs";
#elif defined(__APPLE__)
const char *home = getenv("HOME");
if (!home)
home = getpwuid(getuid())->pw_dir;
return std::filesystem::path(home) / "Library" / "Logs" / app_name;
#else
auto const *home { getenv("HOME") };
if (!home)
home = getpwuid(getuid())->pw_dir;
return std::filesystem::path(home) / ".local" / "share" / app_name / "logs";
#endif
}
#ifndef __EMSCRIPTEN__
int compress_file(std::filesystem::path const &input_path,
std::filesystem::path const &output_path)
{
size_t const chunk_size = 4096;
std::ifstream in { input_path, std::ios::binary };
if (!in)
return 1;
gzFile out { gzopen(output_path.string().c_str(), "wb") };
if (!out)
return 1;
defer(gzclose(out));
std::vector<char> buffer(chunk_size);
while (in) {
in.read(buffer.data(), buffer.size());
std::streamsize bytes = in.gcount();
if (bytes > 0)
gzwrite(out, buffer.data(), static_cast<unsigned int>(bytes));
}
std::filesystem::remove(input_path);
return 0;
}
#endif
Logger::Logger(std::string_view app_name)
{
#ifndef __EMSCRIPTEN__
auto path { get_log_path(app_name) };
auto const exists { std::filesystem::exists(path) };
if (exists && !std::filesystem::is_directory(path)) {
std::filesystem::remove_all(path);
}
if (!exists) {
std::filesystem::create_directories(path);
}
int max { -1 };
std::filesystem::directory_iterator iter(path);
for (auto const &file : iter) {
if (!file.is_regular_file())
continue;
int v;
if (std::sscanf(
file.path().filename().stem().string().c_str(), "log_%d", &v)
!= 1) {
continue;
}
if (v > max)
max = v;
auto ext = file.path().filename().extension().string();
if (ext == ".txt") {
auto np = file.path();
np.replace_extension(ext + ".gz");
compress_file(file.path(), np);
}
}
max++;
path /= std::format("log_{}.txt", max);
m_fout = std::ofstream(path, std::ios::app | std::ios::out);
#endif // EMSCRIPTEN
}
auto Logger::debug(std::string_view msg) -> void
{
log(Logger::Level::Debug, msg);
}
auto Logger::info(std::string_view msg) -> void
{
log(Logger::Level::Info, msg);
}
auto Logger::warn(std::string_view msg) -> void
{
log(Logger::Level::Warning, msg);
}
auto Logger::err(std::string_view msg) -> void
{
log(Logger::Level::Error, msg);
}
std::string get_current_time_string()
{
auto now { std::chrono::system_clock::now() };
auto now_c { std::chrono::system_clock::to_time_t(now) };
std::tm tm { *std::gmtime(&now_c) };
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
return oss.str();
}
void Logger::log(Level level, std::string_view msg)
{
auto time_str = get_current_time_string();
std::string level_str;
switch (level) {
case Logger::Level::Debug:
level_str = "DEBUG";
break;
case Logger::Level::Info:
level_str = " INFO";
break;
case Logger::Level::Warning:
level_str = " WARN";
break;
case Logger::Level::Error:
level_str = "ERROR";
break;
default:
std::unreachable();
}
auto const msg_file { std::format("{} [{}] {}", time_str, level_str, msg) };
#ifdef _WIN32
auto const msg_stdout = msg_file;
#elif __EMSCRIPTEN__
auto const msg_stdout = msg_file;
#else
char const *color;
switch (level) {
case Logger::Level::Debug:
color = FG_GRAY;
break;
case Logger::Level::Info:
color = FG_BLUE;
break;
case Logger::Level::Warning:
color = FG_YELLOW;
break;
case Logger::Level::Error:
color = FG_RED;
break;
default:
std::unreachable();
}
auto const msg_stdout { std::format(
"{}{} [{}] {}" ANSI_RESET, color, time_str, level_str, msg) };
#endif
#ifndef __EMSCRIPTEN__
m_fout << msg_file << std::endl;
#endif // EMSCRIPTEN
std::println(std::cerr, "{}", msg_stdout);
}

52
src/Logger.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <format>
#include <fstream>
#include <string_view>
struct Logger {
enum class Level {
Debug,
Info,
Warning,
Error,
};
Logger(std::string_view app_name);
auto debug(std::string_view msg) -> void;
auto info(std::string_view msg) -> void;
auto warn(std::string_view msg) -> void;
auto err(std::string_view msg) -> void;
template<typename... Args>
auto debug(std::format_string<Args...> fmt, Args &&...args) -> void
{
log(Level::Debug, std::format(fmt, std::forward<Args>(args)...));
}
template<typename... Args>
auto info(std::format_string<Args...> fmt, Args &&...args) -> void
{
log(Level::Info, std::format(fmt, std::forward<Args>(args)...));
}
template<typename... Args>
auto warn(std::format_string<Args...> fmt, Args &&...args) -> void
{
log(Level::Warning, std::format(fmt, std::forward<Args>(args)...));
}
template<typename... Args>
auto err(std::format_string<Args...> fmt, Args &&...args) -> void
{
log(Level::Error, std::format(fmt, std::forward<Args>(args)...));
}
auto log(Level level, std::string_view msg) -> void;
private:
#ifndef __EMSCRIPTEN__
std::ofstream m_fout;
#endif
};

26
src/Util.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <string_view>
using namespace std::string_view_literals;
template<typename F> struct privDefer {
F f;
privDefer(F f)
: f(f)
{
}
~privDefer() { f(); }
};
template<typename F> privDefer<F> defer_func(F f) { return privDefer<F>(f); }
#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x) DEFER_2(x, __COUNTER__)
#define defer(code) auto DEFER_3(_defer_) = defer_func([&]() { code; })
#if defined(_MSC_VER)
# define ALIGN(a) __declspec(align(a))
#else
# define ALIGN(a) __attribute__((aligned(a)))
#endif

7
src/main.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include "src/Application.h"
auto main() -> int
{
Lunar::Application app {};
app.run();
}