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

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();
}