commit 8d3749cc6af89842667de652bf175c2594e9b795 Author: Slendi Date: Tue Dec 2 03:16:07 2025 +0200 Init Signed-off-by: Slendi diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7899a42 --- /dev/null +++ b/.clang-format @@ -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 diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..8647ae2 --- /dev/null +++ b/.clangd @@ -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" + ] diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..149a11e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +[Bb]uild* +result +.cache +.direnv diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c9fc10a --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..39a1ba7 --- /dev/null +++ b/flake.nix @@ -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 + ''; + }; + } + ); +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a18194e --- /dev/null +++ b/meson.build @@ -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, + ] +) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..f31337e --- /dev/null +++ b/meson_options.txt @@ -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') diff --git a/src/Application.cpp b/src/Application.cpp new file mode 100644 index 0000000..205b019 --- /dev/null +++ b/src/Application.cpp @@ -0,0 +1,116 @@ +#include "Application.h" + +#include +#include +#include + +#include +#include +#include + +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(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 diff --git a/src/Application.h b/src/Application.h new file mode 100644 index 0000000..28e8427 --- /dev/null +++ b/src/Application.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +#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 diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..54d4289 --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,205 @@ +#include "Logger.h" + +#include "Util.h" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include // SHGetKnownFolderPath +# include +#elif defined(__APPLE__) +# include +# include +# include +#else +# include +# include +#endif + +#ifndef __EMSCRIPTEN__ +# include +#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 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(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); +} diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..7187fd5 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +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 + auto debug(std::format_string fmt, Args &&...args) -> void + { + log(Level::Debug, std::format(fmt, std::forward(args)...)); + } + + template + auto info(std::format_string fmt, Args &&...args) -> void + { + log(Level::Info, std::format(fmt, std::forward(args)...)); + } + + template + auto warn(std::format_string fmt, Args &&...args) -> void + { + log(Level::Warning, std::format(fmt, std::forward(args)...)); + } + + template + auto err(std::format_string fmt, Args &&...args) -> void + { + log(Level::Error, std::format(fmt, std::forward(args)...)); + } + + auto log(Level level, std::string_view msg) -> void; + +private: +#ifndef __EMSCRIPTEN__ + std::ofstream m_fout; +#endif +}; diff --git a/src/Util.h b/src/Util.h new file mode 100644 index 0000000..8a150f6 --- /dev/null +++ b/src/Util.h @@ -0,0 +1,26 @@ +#pragma once + +#include +using namespace std::string_view_literals; + +template struct privDefer { + F f; + privDefer(F f) + : f(f) + { + } + ~privDefer() { f(); } +}; + +template privDefer defer_func(F f) { return privDefer(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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..af3ffba --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,7 @@ +#include "src/Application.h" + +auto main() -> int +{ + Lunar::Application app {}; + app.run(); +}