Reestructura carpetes: src->source, third_party->source/external, shaders->data/shaders
This commit is contained in:
509
source/main.cpp
Normal file
509
source/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user