initial commit

This commit is contained in:
2026-04-16 23:27:01 -03:00
commit 47f8b8598d
8 changed files with 368 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build
.cache

6
CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 4.2.0)
set(CMAKE_CXX_STANDARD 23)
project(rogue LANGUAGES CXX)
add_subdirectory(external)
add_subdirectory(source)

66
external/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,66 @@
include(FetchContent)
set(VUK_USE_SHADERC OFF CACHE BOOL "NO SHADERC" FORCE)
set(VUK_EXTRA_IMGUI OFF CACHE BOOL "WAYLAND GLFW" FORCE)
FetchContent_Declare(
vuk_fetch
GIT_REPOSITORY https://github.com/martty/vuk.git
GIT_TAG 06fac4b
)
#set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "WAYLAND GLFW" FORCE)
#FetchContent_Declare(
# glfw_fetch
# GIT_REPOSITORY https://github.com/glfw/glfw.git
# GIT_TAG 3.4
#)
FetchContent_Declare(
glm_fetch
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 1.0.2
)
FetchContent_Declare(
entt_fetch
GIT_REPOSITORY https://github.com/skypjack/entt.git
GIT_TAG b4e58bd
)
set(SLANG_ENABLE_SLANGC ON CACHE INTERNAL "" FORCE)
set(SLANG_LIB_TYPE SHARED CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_SLANGRT OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_GFX OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_REPLAYER OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_TESTS OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_EXAMPLES OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_SLANGD OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_SLANGI OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_DXIL ON CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_SLANG_GLSLANG ON CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_PREBUILT_BINARIES ON CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_FULL_IR_VALIDATION OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_ASAN OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_COVERAGE OFF CACHE INTERNAL "" FORCE)
set(SLANG_ENABLE_TIME_TRACE OFF CACHE INTERNAL "" FORCE)
set(SLANG_STANDARD_MODULE_DEVELOP_BUILD OFF CACHE INTERNAL "" FORCE)
set(SLANG_EXCLUDE_DAWN ON CACHE INTERNAL "" FORCE)
set(SLANG_EXCLUDE_TINT ON CACHE INTERNAL "" FORCE)
SET(SLANG_LIB_TYPE "STATIC" CACHE STRING "" FORCE)
FetchContent_Declare(
slang_fetch
GIT_REPOSITORY https://github.com/shader-slang/slang.git
GIT_TAG 528ddf7
)
FetchContent_Declare(
fastgltf_fetch
GIT_REPOSITORY https://github.com/spnda/fastgltf.git
GIT_TAG 35368f913b79acc112c05d67291681e7badea68b
)
FetchContent_MakeAvailable(vuk_fetch glm_fetch entt_fetch slang_fetch fastgltf_fetch)

18
source/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
add_library(rogue-lib STATIC
cli/args.cpp
cli/args.hpp
)
target_include_directories(rogue-lib PUBLIC source .)
target_compile_definitions(rogue-lib PUBLIC -DGLFW_INCLUDE_VULKAN -DGLM_ENABLE_EXPERIMENTAL)
target_link_libraries(rogue-lib PUBLIC
glm
vuk
vk-bootstrap
slang
fastgltf
)
add_executable(rogue
main.cxx
)
target_link_libraries(rogue PUBLIC rogue-lib)

103
source/cli/args.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include "args.hpp"
#include <charconv>
#include <format>
#include <stdexcept>
namespace rog::cli {
args parse(int argc, char **argv) {
args result{};
result.extent = {512, 512};
result.samples_per_pixel = 256;
if (argc < 2) {
throw std::runtime_error("no scene provided");
}
std::vector<std::string_view> args_view(argv + 1,
argv + argc); // skip program name
for (size_t i = 0; i < args_view.size(); ++i) {
std::string_view arg = args_view[i];
if (arg == "--help" || arg == "-h" || arg == "-?") {
result.help = true;
return result;
}
if (arg[0] != '-' && result.scene_path.empty()) {
result.scene_path = std::filesystem::path(arg);
continue;
}
if (arg == "--width" || arg == "-w") {
if (i + 1 >= args_view.size()) {
throw std::runtime_error("error: --width requires a value");
}
const auto val = args_view[++i];
uint32_t w = 0;
std::from_chars(val.data(), val.data() + val.size(), w);
result.extent.x = w;
continue;
}
if (arg == "--height" || (arg == "-h" && i + 1 < args_view.size() &&
args_view[i + 1][0] != '-')) {
if (i + 1 >= args_view.size()) {
throw std::runtime_error("error: --height requires a value");
}
const auto val = args_view[++i];
uint32_t h = 0;
std::from_chars(val.data(), val.data() + val.size(), h);
result.extent.y = h;
continue;
}
if (arg == "--spp") {
if (i + 1 >= args_view.size()) {
throw std::runtime_error("error: --spp requires a value");
}
const auto val = args_view[++i];
uint32_t spp = 0;
std::from_chars(val.data(), val.data() + val.size(), spp);
result.samples_per_pixel = spp;
continue;
}
if (arg == "--blender") {
if (i + 1 >= args_view.size()) {
throw std::runtime_error("error: --blender requires a path");
}
result.blender_path = std::filesystem::path(args_view[++i]);
continue;
}
if (arg[0] == '-') {
throw std::runtime_error(std::format("error: unknown option '{}'", arg));
}
}
return result;
}
std::string_view help_message() noexcept {
return R"(Usage: rogue [OPTIONS] <scene>
positional:
<scene> Path to .glb or .blend file (required)
options:
-w, --width <uint> Render width (default: 512)
-h, --height <uint> Render height (default: 512)
--spp <uint> Samples per pixel (default: 256)
--blender <path> Custom path to Blender executable
--help Show this help message
examples:
rogue scene.glb
rogue my_scene.blend --interactive
rogue scene.blend -w 1920 -h 1080 --spp 1024
)";
}
} // namespace rog::cli

26
source/cli/args.hpp Normal file
View File

@@ -0,0 +1,26 @@
#ifndef ARGS_HPP
#define ARGS_HPP
#include <filesystem>
#include <glm/glm.hpp>
#include <optional>
#include <string_view>
namespace rog::cli {
struct args {
std::filesystem::path scene_path;
glm::uvec2 extent = {512, 512};
std::uint32_t samples_per_pixel = 256;
std::optional<std::filesystem::path> blender_path;
bool help = false;
};
[[nodiscard]] args parse(int argc, char **argv);
[[nodiscard]] std::string_view help_message() noexcept;
} // namespace rog::cli
#endif

29
source/main.cxx Normal file
View File

@@ -0,0 +1,29 @@
#include <cli/args.hpp>
#include <print>
#include <util/logger.hpp>
auto main(int argc, char **argv) -> int {
auto log = rog::logger(rog::log_level::debug, [](std::string_view message) {
std::println("{}", message);
});
try {
const auto args = rog::cli::parse(argc, argv);
if (args.help) {
std::println("{}", rog::cli::help_message());
return 0;
}
if (args.scene_path.empty()) {
std::println("scene path required");
return 1;
}
if (!std::filesystem::exists(args.scene_path)) {
std::println("{} does not exist", args.scene_path.generic_string());
return 2;
}
} catch (const std::exception &ex) {
log.error("{}", ex.what());
}
}

118
source/util/logger.hpp Normal file
View File

@@ -0,0 +1,118 @@
#ifndef LOGGER_HPP
#define LOGGER_HPP
#include <chrono>
#include <ctime>
#include <format>
#include <functional>
#include <string>
#include <string_view>
#include <vector>
namespace rog {
enum class log_level {
debug = 0,
info = 1,
warn = 2,
error = 3,
fatal = 4,
};
class logger {
private:
using SinkFunction = std::function<void(std::string_view)>;
std::vector<SinkFunction> sinks;
log_level current_level = log_level::info; // default level
[[nodiscard]] bool verify_level(log_level l) const noexcept {
return static_cast<int>(current_level) <= static_cast<int>(l);
}
static std::string get_timestamp() {
using namespace std::chrono;
const auto now = system_clock::now();
const auto time_t_now = system_clock::to_time_t(now);
const auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
std::tm local_tm{};
#if defined(_MSC_VER)
localtime_s(&local_tm, &time_t_now);
#else
localtime_r(&time_t_now, &local_tm); // safe on macOS / Linux
#endif
auto buf = std::array<char, 64>();
std::strftime(buf.data(), 64, "%Y-%m-%d %H:%M:%S", &local_tm);
auto buf_str = std::string(buf.data());
return std::format("{}.{:03}", buf_str, ms.count());
}
void dispatch(log_level level, std::string_view level_str,
std::string_view message) {
if (!verify_level(level)) {
return;
}
std::string timestamp = get_timestamp();
std::string formatted =
std::format("{:>5} [{}] -- {}", level_str, timestamp, message);
for (const auto &sink : sinks) {
sink(formatted);
}
}
public:
logger() = default;
template <typename... Sinks>
explicit logger(log_level level, Sinks &&...sinks) : current_level(level) {
(this->sinks.push_back(std::forward<Sinks>(sinks)), ...);
}
void set_level(log_level level) noexcept { current_level = level; }
template <typename... Args>
void debug(std::format_string<Args...> fmt, Args &&...args) {
dispatch(log_level::debug, "DEBUG",
std::format(fmt, std::forward<Args>(args)...));
}
template <typename... Args>
void info(std::format_string<Args...> fmt, Args &&...args) {
dispatch(log_level::info, "INFO",
std::format(fmt, std::forward<Args>(args)...));
}
template <typename... Args>
void warn(std::format_string<Args...> fmt, Args &&...args) {
dispatch(log_level::warn, "WARN",
std::format(fmt, std::forward<Args>(args)...));
}
template <typename... Args>
void error(std::format_string<Args...> fmt, Args &&...args) {
dispatch(log_level::error, "ERROR",
std::format(fmt, std::forward<Args>(args)...));
}
template <typename... Args>
void fatal(std::format_string<Args...> fmt, Args &&...args) {
dispatch(log_level::fatal, "FATAL",
std::format(fmt, std::forward<Args>(args)...));
}
// Add more sinks at runtime if desired
void add_sink(SinkFunction sink) { sinks.push_back(std::move(sink)); }
};
} // namespace rog
#endif