Reestructura carpetes: src->source, third_party->source/external, shaders->data/shaders

This commit is contained in:
2026-05-04 13:21:34 +02:00
parent cec347a97c
commit e51ee84167
82 changed files with 36 additions and 39 deletions

509
source/main.cpp Normal file
View File

@@ -0,0 +1,509 @@
// src/main.cpp
#include <algorithm>
#include <ctime>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <SDL3/SDL.h>
#include "audio/jail_audio.hpp"
#include "defines.hpp"
#include "rendering/shader_backend.hpp"
struct Logger {
static void info(const std::string& s) { std::cout << "[INFO] " << s << '\n'; }
static void error(const std::string& s) { std::cerr << "[ERROR] " << s << '\n'; }
};
struct VideoOptions {
bool fullscreen = false;
bool vsync = true;
} Options_video;
struct DisplayMonitor {
std::string name;
int width = 0;
int height = 0;
int refresh_rate = 0;
};
static DisplayMonitor display_monitor_;
static SDL_Window* window_ = nullptr;
static std::unique_ptr<Rendering::IShaderBackend> backend_;
struct ShaderEntry {
std::filesystem::path folder;
std::string base_name;
};
static std::vector<ShaderEntry> shader_list_;
static std::vector<std::string> shader_names_;
static std::vector<std::string> shader_authors_;
static size_t current_shader_index_ = 0;
static std::filesystem::path shaders_directory_;
static Uint32 shader_start_ticks_ = 0;
static Uint32 fps_frame_count_ = 0;
static Uint32 fps_last_update_ticks_ = 0;
static float current_fps_ = 0.0f;
static std::unique_ptr<Ja::Engine> audio_engine_;
static std::vector<Ja::Music*> music_list_;
static std::vector<std::string> music_names_;
static size_t current_music_index_ = 0;
static bool music_muted_ = false;
static std::vector<ShaderEntry> scanShaderDirectory(const std::filesystem::path& directory) {
std::vector<ShaderEntry> shaders;
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
Logger::error("Shader directory does not exist: " + directory.string());
return shaders;
}
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (!entry.is_directory()) { continue; }
const std::string folder_name = entry.path().filename().string();
if (folder_name.empty() || folder_name[0] == '_' || folder_name[0] == '.') { continue; }
const std::filesystem::path gl_source = entry.path() / (folder_name + ".gl.glsl");
if (!std::filesystem::exists(gl_source)) {
Logger::info("Skipping " + folder_name + ": missing " + gl_source.filename().string());
continue;
}
shaders.push_back(ShaderEntry{entry.path(), folder_name});
}
std::sort(shaders.begin(), shaders.end(),
[](const ShaderEntry& a, const ShaderEntry& b) { return a.base_name < b.base_name; });
Logger::info("Found " + std::to_string(shaders.size()) + " shader(s) in " + directory.string());
shader_names_.resize(shaders.size(), "");
shader_authors_.resize(shaders.size(), "");
return shaders;
}
static void preloadMusicDirectory(const std::filesystem::path& directory) {
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
Logger::info("Music directory does not exist: " + directory.string());
return;
}
std::vector<std::filesystem::path> ogg_paths;
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.is_regular_file() && entry.path().extension() == ".ogg") {
ogg_paths.push_back(entry.path());
}
}
std::sort(ogg_paths.begin(), ogg_paths.end());
for (const auto& path : ogg_paths) {
std::size_t size = 0;
void* raw = SDL_LoadFile(path.string().c_str(), &size);
if (raw == nullptr || size == 0) {
Logger::error("Failed to read music file: " + path.string());
if (raw != nullptr) { SDL_free(raw); }
continue;
}
Ja::Music* music = Ja::loadMusic(static_cast<Uint8*>(raw), static_cast<Uint32>(size),
path.filename().string().c_str());
SDL_free(raw);
if (music == nullptr) {
Logger::error("Failed to decode OGG: " + path.string());
continue;
}
music_list_.push_back(music);
music_names_.push_back(path.filename().string());
}
Logger::info("Preloaded " + std::to_string(music_list_.size()) + " music file(s) from " + directory.string());
}
static void playRandomMusic() {
if (music_list_.empty() || !audio_engine_) { return; }
current_music_index_ = static_cast<size_t>(rand()) % music_list_.size();
audio_engine_->playMusic(music_list_[current_music_index_], 0);
Logger::info("Now playing: " + music_names_[current_music_index_]);
}
static void updateWindowTitle() {
if (window_ == nullptr || shader_list_.empty()) { return; }
std::string shaderName;
if (!shader_names_.empty() && !shader_names_[current_shader_index_].empty()) {
shaderName = shader_names_[current_shader_index_];
} else {
shaderName = shader_list_[current_shader_index_].base_name;
}
if (!shader_authors_.empty() && !shader_authors_[current_shader_index_].empty()) {
shaderName += " by " + shader_authors_[current_shader_index_];
}
std::string title = WINDOW_TITLE_PREFIX;
title += " (";
title += shaderName;
if (backend_) {
title += " - ";
title += backend_->driverName();
}
if (current_fps_ > 0.0f) {
title += " - ";
title += std::to_string(static_cast<int>(current_fps_ + 0.5f)) + " FPS";
}
if (Options_video.vsync) {
title += " - VSync";
}
title += ")";
SDL_SetWindowTitle(window_, title.c_str());
}
static bool loadShaderAtIndex(size_t index) {
if (index >= shader_list_.size()) {
Logger::error("Invalid shader index: " + std::to_string(index));
return false;
}
const auto& entry = shader_list_[index];
Logger::info("Loading shader: " + entry.folder.string());
Rendering::ShaderProgramSpec spec;
spec.folder = entry.folder;
spec.base_name = entry.base_name;
const std::filesystem::path meta_path = entry.folder / "meta.txt";
if (std::filesystem::exists(meta_path)) {
spec.metadata = Rendering::parseMetaFile(meta_path);
} else {
std::string source;
const std::filesystem::path gl_source = entry.folder / (entry.base_name + ".gl.glsl");
if (Rendering::loadFileToString(gl_source, source)) {
spec.metadata = Rendering::extractShaderMetadata(source);
}
}
if (!spec.metadata.name.empty()) {
shader_names_[index] = spec.metadata.name;
Logger::info("Shader name: " + spec.metadata.name);
}
if (!spec.metadata.author.empty()) {
shader_authors_[index] = spec.metadata.author;
Logger::info("Shader author: " + spec.metadata.author);
}
return backend_->loadShader(spec);
}
void getDisplayInfo() {
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr && num_displays > 0) {
for (int i = 0; i < num_displays; ++i) {
const SDL_DisplayID instance_id = displays[i];
const char* name = SDL_GetDisplayName(instance_id);
Logger::info(std::string("Display ") + std::to_string(instance_id) + ": " + (name != nullptr ? name : "Unknown"));
}
const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(displays[0]);
const char* first_display_name = SDL_GetDisplayName(displays[0]);
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
if (dm != nullptr) {
display_monitor_.width = static_cast<int>(dm->w);
display_monitor_.height = static_cast<int>(dm->h);
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
} else {
Logger::info("SDL_GetCurrentDisplayMode returned null");
}
} else {
Logger::info("No displays found or SDL_GetDisplays failed");
}
}
void setFullscreenMode() {
if (window_ == nullptr) { return; }
if (Options_video.fullscreen) {
if (!SDL_SetWindowFullscreen(window_, true)) {
Logger::error(std::string("Failed to set fullscreen: ") + SDL_GetError());
Logger::info("Fallback to windowed mode 800x800");
SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT);
Options_video.fullscreen = false;
SDL_ShowCursor();
} else {
SDL_HideCursor();
}
} else {
SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT);
SDL_ShowCursor();
}
}
void toggleFullscreen() {
Options_video.fullscreen = !Options_video.fullscreen;
setFullscreenMode();
}
void toggleVSync() {
Options_video.vsync = !Options_video.vsync;
if (backend_) {
backend_->setVSync(Options_video.vsync);
}
}
void switchShader(int direction) {
if (shader_list_.empty()) { return; }
size_t new_index = current_shader_index_;
if (direction > 0) {
new_index = (current_shader_index_ + 1) % shader_list_.size();
} else if (direction < 0) {
new_index = (current_shader_index_ == 0) ? shader_list_.size() - 1 : current_shader_index_ - 1;
}
if (!loadShaderAtIndex(new_index)) {
Logger::error("Failed to switch shader, keeping current one");
return;
}
current_shader_index_ = new_index;
shader_start_ticks_ = SDL_GetTicks();
updateWindowTitle();
}
void handleDebugEvents(const SDL_Event& event) {
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
switch (event.key.key) {
case SDLK_F3: { toggleFullscreen(); break; }
case SDLK_F4: { toggleVSync(); break; }
case SDLK_M: {
music_muted_ = !music_muted_;
if (audio_engine_) { audio_engine_->setMusicVolume(music_muted_ ? 0.0f : 1.0f); }
Logger::info(music_muted_ ? "Music muted" : "Music unmuted");
break;
}
case SDLK_LEFT: { switchShader(-1); break; }
case SDLK_RIGHT: { switchShader(+1); break; }
default: break;
}
}
}
enum class BackendChoice { Auto, Gpu, OpenGL };
static auto createWindowForBackend(BackendChoice choice) -> SDL_Window* {
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
if (choice == BackendChoice::OpenGL) {
flags |= SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
return SDL_CreateWindow(APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, flags);
}
int main(int argc, char** argv) {
std::string shaderPath;
bool fullscreenFlag = false;
BackendChoice backend_choice = BackendChoice::Auto;
for (int i = 1; i < argc; ++i) {
const std::string a = argv[i];
if (a == "-F" || a == "--fullscreen") { fullscreenFlag = true; continue; }
if (a == "--backend=gpu") { backend_choice = BackendChoice::Gpu; continue; }
if (a == "--backend=opengl") { backend_choice = BackendChoice::OpenGL; continue; }
if (a == "--backend=auto") { backend_choice = BackendChoice::Auto; continue; }
if (shaderPath.empty()) { shaderPath = a; }
}
if (shaderPath.empty()) { shaderPath = "test"; }
Options_video.fullscreen = fullscreenFlag;
auto initResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
if constexpr (std::is_same_v<decltype(initResult), bool>) {
if (!initResult) { Logger::error(SDL_GetError()); return -1; }
} else {
if (initResult != 0) { Logger::error(SDL_GetError()); return -1; }
}
getDisplayInfo();
if (backend_choice != BackendChoice::OpenGL) {
window_ = createWindowForBackend(BackendChoice::Gpu);
if (window_ != nullptr) {
backend_ = Rendering::makeSdl3GpuBackend();
if (!backend_->init(window_)) {
Logger::info("SDL3 GPU backend init failed, falling back to OpenGL");
backend_.reset();
SDL_DestroyWindow(window_);
window_ = nullptr;
if (backend_choice == BackendChoice::Gpu) {
SDL_Quit();
return -1;
}
}
}
}
if (backend_ == nullptr) {
window_ = createWindowForBackend(BackendChoice::OpenGL);
if (window_ == nullptr) {
Logger::error(std::string("SDL_CreateWindow error: ") + SDL_GetError());
SDL_Quit();
return -1;
}
backend_ = Rendering::makeOpenGLBackend();
if (!backend_->init(window_)) {
Logger::error("Failed to initialize shader backend");
SDL_DestroyWindow(window_);
SDL_Quit();
return -1;
}
}
setFullscreenMode();
backend_->setVSync(Options_video.vsync);
audio_engine_ = std::make_unique<Ja::Engine>(48000, SDL_AUDIO_S16, 2);
audio_engine_->setOnMusicEnded([]() { playRandomMusic(); });
const std::string resources_dir = getResourcesDirectory();
srand(static_cast<unsigned int>(time(nullptr)));
const std::filesystem::path music_directory = std::filesystem::path(resources_dir) / "data" / "music";
preloadMusicDirectory(music_directory);
if (!music_list_.empty()) {
playRandomMusic();
} else {
Logger::info("No music files found in " + music_directory.string());
}
const std::filesystem::path arg_path(shaderPath);
std::filesystem::path target_folder;
if (arg_path.has_parent_path()) {
target_folder = arg_path;
shaders_directory_ = arg_path.parent_path();
} else {
shaders_directory_ = std::filesystem::path(resources_dir) / "data" / "shaders";
target_folder = shaders_directory_ / shaderPath;
}
shader_list_ = scanShaderDirectory(shaders_directory_);
if (shader_list_.empty()) {
Logger::error("No shaders found in directory: " + shaders_directory_.string());
backend_->cleanup();
SDL_DestroyWindow(window_);
SDL_Quit();
return -1;
}
size_t initial_index = 0;
bool found_shader = false;
for (size_t i = 0; i < shader_list_.size(); ++i) {
if (shader_list_[i].folder == target_folder) {
initial_index = i;
found_shader = true;
break;
}
}
if (!found_shader) {
const std::filesystem::path default_folder = std::filesystem::path(resources_dir) / "data" / "shaders" / "test";
for (size_t i = 0; i < shader_list_.size(); ++i) {
if (shader_list_[i].folder == default_folder) {
initial_index = i;
found_shader = true;
break;
}
}
}
if (!found_shader) {
Logger::info("Specified shader not found, using first shader in directory");
initial_index = 0;
}
current_shader_index_ = initial_index;
if (!loadShaderAtIndex(current_shader_index_)) {
Logger::error("Failed to load initial shader");
backend_->cleanup();
SDL_DestroyWindow(window_);
SDL_Quit();
return -1;
}
shader_start_ticks_ = SDL_GetTicks();
fps_last_update_ticks_ = SDL_GetTicks();
updateWindowTitle();
bool running = true;
while (running) {
fps_frame_count_++;
const Uint32 current_ticks = SDL_GetTicks();
if (current_ticks - fps_last_update_ticks_ >= 500) {
const float elapsed_seconds = static_cast<float>(current_ticks - fps_last_update_ticks_) / 1000.0f;
current_fps_ = static_cast<float>(fps_frame_count_) / elapsed_seconds;
fps_frame_count_ = 0;
fps_last_update_ticks_ = current_ticks;
updateWindowTitle();
}
if (audio_engine_) { audio_engine_->update(); }
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
running = false;
} else if (e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
running = false;
} else if (e.type == SDL_EVENT_KEY_DOWN) {
if (e.key.key == SDLK_ESCAPE) { running = false; }
handleDebugEvents(e);
}
}
Rendering::ShaderUniforms uniforms;
uniforms.iTime = static_cast<float>(SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
int w = 0;
int h = 0;
SDL_GetWindowSize(window_, &w, &h);
uniforms.iResolutionX = static_cast<float>(w);
uniforms.iResolutionY = static_cast<float>(h);
backend_->render(uniforms);
if (!Options_video.vsync) {
SDL_Delay(1);
}
}
backend_->cleanup();
backend_.reset();
for (Ja::Music* m : music_list_) { Ja::deleteMusic(m); }
music_list_.clear();
music_names_.clear();
audio_engine_.reset();
SDL_DestroyWindow(window_);
SDL_Quit();
return 0;
}