diff --git a/.gitignore b/.gitignore index e257658..6206104 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +build/ +esqueleto +.cache +.claude + # ---> C++ # Prerequisites *.d diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..29df127 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,49 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${default}", + "${workspaceFolder}/source/**" + ], + "defines": [ + "_DEBUG", + "MACOS_BUILD" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++20", + "intelliSenseMode": "macos-clang-arm64" + }, + { + "name": "Linux", + "includePath": [ + "${default}", + "${workspaceFolder}/source/**" + ], + "defines": [ + "_DEBUG", + "LINUX_BUILD" + ], + "compilerPath": "/usr/bin/g++", + "cStandard": "c17", + "cppStandard": "c++20", + "intelliSenseMode": "linux-gcc-x64" + }, + { + "name": "Win32", + "includePath": [ + "${default}", + "${workspaceFolder}/source/**" + ], + "defines": [ + "_DEBUG", + "WINDOWS_BUILD" + ], + "cStandard": "c17", + "cppStandard": "c++20", + "intelliSenseMode": "windows-msvc-x64" + } + ], + "version": 4 +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b3b1271 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,164 @@ +# CMakeLists.txt — esqueleto SDL3 + +cmake_minimum_required(VERSION 3.10) +project(esqueleto VERSION 1.00) + +# Estándar de C++ +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Exportar comandos de compilación para herramientas de análisis +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# --- 1. LISTA DE FUENTES --- +set(APP_SOURCES + # Core - Audio + source/core/audio/audio.cpp + + # Core - Input + source/core/input/input.cpp + source/core/input/input_types.cpp + source/core/input/mouse.cpp + + # Core - Rendering + source/core/rendering/gif.cpp + source/core/rendering/screen.cpp + source/core/rendering/surface.cpp + source/core/rendering/sprites/animated_sprite.cpp + source/core/rendering/sprites/dissolve_sprite.cpp + source/core/rendering/sprites/moving_sprite.cpp + source/core/rendering/sprites/sprite.cpp + source/core/rendering/text.cpp + + # Utils + source/utils/delta_timer.cpp + source/utils/utils.cpp + + # Main + source/main.cpp +) + +# Fuentes del sistema de renderizado SDL3 GPU +set(RENDERING_SOURCES + source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +) + +# --- SDL3 --- +find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) +message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}") + +# --- SHADER COMPILATION (Linux/Windows — macOS usa Metal) --- +if(NOT APPLE) + find_program(GLSLC_EXE NAMES glslc) + + set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert") + set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag") + set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_vert_spv.h") + set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_frag_spv.h") + + if(GLSLC_EXE) + add_custom_command( + OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}" + COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh" + DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "Compilando shaders SPIR-V..." + ) + add_custom_target(shaders DEPENDS "${SHADER_VERT_H}" "${SHADER_FRAG_H}") + message(STATUS "glslc encontrado: shaders se compilarán automáticamente") + else() + if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}") + message(FATAL_ERROR + "glslc no encontrado y headers SPIR-V no existen.\n" + " Instala glslc: sudo apt install glslang-tools (Linux)\n" + " choco install vulkan-sdk (Windows)\n" + " O genera los headers manualmente con: tools/shaders/compile_spirv.sh" + ) + else() + message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados") + endif() + endif() +else() + message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)") +endif() + +# --- 2. EJECUTABLE --- +add_executable(${PROJECT_NAME} ${APP_SOURCES} ${RENDERING_SOURCES}) + +if(NOT APPLE AND GLSLC_EXE) + add_dependencies(${PROJECT_NAME} shaders) +endif() + +# --- 3. DIRECTORIOS DE INCLUSIÓN --- +target_include_directories(${PROJECT_NAME} PUBLIC + "${CMAKE_SOURCE_DIR}/source" + "${CMAKE_BINARY_DIR}" +) + +# --- 4. ENLACE --- +target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3) + +# --- 5. FLAGS DE COMPILACIÓN --- +target_compile_options(${PROJECT_NAME} PRIVATE -Wall) +target_compile_options(${PROJECT_NAME} PRIVATE $<$:-Os -ffunction-sections -fdata-sections>) + +target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:_DEBUG>) +target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:RELEASE_BUILD>) + +if(WIN32) + target_compile_definitions(${PROJECT_NAME} PRIVATE WINDOWS_BUILD) + target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 mingw32) +elseif(APPLE) + target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD) + target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated) + set(CMAKE_OSX_ARCHITECTURES "arm64") +elseif(UNIX AND NOT APPLE) + target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD) +endif() + +set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# --- 6. STATIC ANALYSIS --- +find_program(CLANG_TIDY_EXE NAMES clang-tidy) +find_program(CLANG_FORMAT_EXE NAMES clang-format) + +file(GLOB_RECURSE ALL_SOURCE_FILES + "${CMAKE_SOURCE_DIR}/source/*.cpp" + "${CMAKE_SOURCE_DIR}/source/*.hpp" + "${CMAKE_SOURCE_DIR}/source/*.h" +) +list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX ".*/external/.*") + +set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES}) +list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$") +list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*_spv\\.h$") + +if(CLANG_TIDY_EXE) + add_custom_target(tidy + COMMAND ${CLANG_TIDY_EXE} -p ${CMAKE_BINARY_DIR} ${CLANG_TIDY_SOURCES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running clang-tidy..." + ) + add_custom_target(tidy-fix + COMMAND ${CLANG_TIDY_EXE} -p ${CMAKE_BINARY_DIR} --fix ${CLANG_TIDY_SOURCES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running clang-tidy with fixes..." + ) +else() + message(STATUS "clang-tidy no encontrado") +endif() + +if(CLANG_FORMAT_EXE) + add_custom_target(format + COMMAND ${CLANG_FORMAT_EXE} -i ${ALL_SOURCE_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running clang-format..." + ) + add_custom_target(format-check + COMMAND ${CLANG_FORMAT_EXE} --dry-run --Werror ${ALL_SOURCE_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Checking clang-format..." + ) +else() + message(STATUS "clang-format no encontrado") +endif() diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp new file mode 100644 index 0000000..69d8cbc --- /dev/null +++ b/source/core/audio/audio.cpp @@ -0,0 +1,190 @@ +#include "audio.hpp" + +#include + +#include // Para clamp +#include + +// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp) +// clang-format off +#undef STB_VORBIS_HEADER_ONLY +#include "external/stb_vorbis.h" +// clang-format on + +#include "core/audio/jail_audio.hpp" // Para JA_* +#include "core/options.hpp" // Para Options::audio + +// Singleton +Audio* Audio::instance = nullptr; + +void Audio::init() { Audio::instance = new Audio(); } +void Audio::destroy() { delete Audio::instance; } +auto Audio::get() -> Audio* { return Audio::instance; } + +// Constructor +Audio::Audio() { initSDLAudio(); } + +// Destructor +Audio::~Audio() { + // Liberar recursos de música cargados + for (auto& [name, music] : music_cache_) { + if (music) { + JA_StopMusic(); + delete music; + } + } + // Liberar recursos de sonido cargados + for (auto& [name, sound] : sound_cache_) { + if (sound) { + SDL_free(sound->buffer); + delete sound; + } + } + JA_Quit(); +} + +void Audio::update() { + JA_Update(); +} + +// Reproduce una pista de música a partir de su ruta de fichero +void Audio::playMusic(const std::string& path, const int loop) { + bool new_loop = (loop != 0); + + if (music_.state == MusicState::PLAYING && music_.name == path && music_.loop == new_loop) { + return; + } + + // Cargar si no está en caché + auto it = music_cache_.find(path); + JA_Music_t* resource = nullptr; + if (it != music_cache_.end()) { + resource = it->second; + } else { + resource = JA_LoadMusic(path.c_str()); + if (resource != nullptr) { + music_cache_[path] = resource; + } + } + + if (resource == nullptr) { + std::cerr << "Audio: no se pudo cargar la música: " << path << '\n'; + return; + } + + if (music_.state == MusicState::PLAYING) { + JA_StopMusic(); + } + + JA_PlayMusic(resource, loop); + music_.name = path; + music_.loop = new_loop; + music_.state = MusicState::PLAYING; +} + +void Audio::pauseMusic() { + if (music_enabled_ && music_.state == MusicState::PLAYING) { + JA_PauseMusic(); + music_.state = MusicState::PAUSED; + } +} + +void Audio::resumeMusic() { + if (music_enabled_ && music_.state == MusicState::PAUSED) { + JA_ResumeMusic(); + music_.state = MusicState::PLAYING; + } +} + +void Audio::stopMusic() { + if (music_enabled_) { + JA_StopMusic(); + music_.state = MusicState::STOPPED; + } +} + +// Reproduce un efecto de sonido a partir de su ruta de fichero +void Audio::playSound(const std::string& path, Group group) const { + if (!sound_enabled_) return; + + auto it = sound_cache_.find(path); + JA_Sound_t* resource = nullptr; + if (it != sound_cache_.end()) { + resource = it->second; + } else { + resource = JA_LoadSound(path.c_str()); + if (resource != nullptr) { + sound_cache_[path] = resource; + } + } + + if (resource == nullptr) { + std::cerr << "Audio: no se pudo cargar el sonido: " << path << '\n'; + return; + } + + JA_PlaySound(resource, 0, static_cast(group)); +} + +// Reproduce un sonido por puntero directo (para reutilizar recursos ya cargados) +void Audio::playSound(JA_Sound_t* sound, Group group) const { + if (sound_enabled_) { + JA_PlaySound(sound, 0, static_cast(group)); + } +} + +void Audio::stopAllSounds() const { + if (sound_enabled_) { + JA_StopChannel(-1); + } +} + +void Audio::fadeOutMusic(int milliseconds) const { + if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) { + JA_FadeOutMusic(milliseconds); + } +} + +auto Audio::getRealMusicState() -> MusicState { + JA_Music_state ja_state = JA_GetMusicState(); + switch (ja_state) { + case JA_MUSIC_PLAYING: return MusicState::PLAYING; + case JA_MUSIC_PAUSED: return MusicState::PAUSED; + default: return MusicState::STOPPED; + } +} + +void Audio::setSoundVolume(float sound_volume, Group group) const { + if (sound_enabled_) { + sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); + JA_SetSoundVolume(sound_volume * Options::audio.volume, static_cast(group)); + } +} + +void Audio::setMusicVolume(float music_volume) const { + if (music_enabled_) { + music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); + JA_SetMusicVolume(music_volume * Options::audio.volume); + } +} + +void Audio::applySettings() { + enable(Options::audio.enabled); +} + +void Audio::enable(bool value) { + enabled_ = value; + setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME); + setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME); +} + +void Audio::initSDLAudio() { + if (!SDL_Init(SDL_INIT_AUDIO)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError()); + } else { + JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2); + enable(Options::audio.enabled); + std::cout << "\n** AUDIO SYSTEM **\n"; + std::cout << "Audio system initialized successfully\n"; + } +} diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp new file mode 100644 index 0000000..729cea4 --- /dev/null +++ b/source/core/audio/audio.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +struct JA_Sound_t; +struct JA_Music_t; + +// --- Clase Audio: gestor de audio (singleton) --- +class Audio { + public: + // --- Enums --- + enum class Group : int { + ALL = -1, + GAME = 0, + INTERFACE = 1 + }; + + enum class MusicState { + PLAYING, + PAUSED, + STOPPED, + }; + + // --- Constantes --- + static constexpr float MAX_VOLUME = 1.0F; + static constexpr float MIN_VOLUME = 0.0F; + static constexpr int FREQUENCY = 48000; + + // --- Singleton --- + static void init(); + static void destroy(); + static auto get() -> Audio*; + Audio(const Audio&) = delete; + auto operator=(const Audio&) -> Audio& = delete; + + static void update(); + + // --- Control de música --- + // 'path' es la ruta al fichero de música (.ogg). Se cachea automáticamente. + void playMusic(const std::string& path, int loop = -1); + void pauseMusic(); + void resumeMusic(); + void stopMusic(); + void fadeOutMusic(int milliseconds) const; + + // --- Control de sonidos --- + // 'path' es la ruta al fichero WAV. Se cachea automáticamente. + void playSound(const std::string& path, Group group = Group::GAME) const; + void playSound(JA_Sound_t* sound, Group group = Group::GAME) const; + void stopAllSounds() const; + + // --- Control de volumen --- + void setSoundVolume(float volume, Group group = Group::ALL) const; + void setMusicVolume(float volume) const; + + // --- Configuración general --- + void enable(bool value); + void toggleEnabled() { enabled_ = !enabled_; } + void applySettings(); + + // --- Configuración de sonidos --- + void enableSound() { sound_enabled_ = true; } + void disableSound() { sound_enabled_ = false; } + void enableSound(bool value) { sound_enabled_ = value; } + void toggleSound() { sound_enabled_ = !sound_enabled_; } + + // --- Configuración de música --- + void enableMusic() { music_enabled_ = true; } + void disableMusic() { music_enabled_ = false; } + void enableMusic(bool value) { music_enabled_ = value; } + void toggleMusic() { music_enabled_ = !music_enabled_; } + + // --- Consultas de estado --- + [[nodiscard]] auto isEnabled() const -> bool { return enabled_; } + [[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; } + [[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; } + [[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; } + [[nodiscard]] static auto getRealMusicState() -> MusicState; + [[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; } + + private: + struct Music { + MusicState state{MusicState::STOPPED}; + std::string name; + bool loop{false}; + }; + + Audio(); + ~Audio(); + void initSDLAudio(); + + static Audio* instance; + + Music music_; + bool enabled_{true}; + bool sound_enabled_{true}; + bool music_enabled_{true}; + + // Caché de recursos cargados (ruta → recurso) + mutable std::unordered_map music_cache_; + mutable std::unordered_map sound_cache_; +}; diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp new file mode 100644 index 0000000..3ef686c --- /dev/null +++ b/source/core/audio/jail_audio.hpp @@ -0,0 +1,482 @@ +#pragma once + +// --- Includes --- +#include +#include // Para uint32_t, uint8_t +#include // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET +#include // Para free, malloc +#include // Para strcpy, strlen + +#define STB_VORBIS_HEADER_ONLY +#include "external/stb_vorbis.h" // Para stb_vorbis_decode_memory + +// --- Public Enums --- +enum JA_Channel_state { JA_CHANNEL_INVALID, + JA_CHANNEL_FREE, + JA_CHANNEL_PLAYING, + JA_CHANNEL_PAUSED, + JA_SOUND_DISABLED }; +enum JA_Music_state { JA_MUSIC_INVALID, + JA_MUSIC_PLAYING, + JA_MUSIC_PAUSED, + JA_MUSIC_STOPPED, + JA_MUSIC_DISABLED }; + +// --- Struct Definitions --- +#define JA_MAX_SIMULTANEOUS_CHANNELS 20 +#define JA_MAX_GROUPS 2 + +struct JA_Sound_t { + SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + Uint32 length{0}; + Uint8* buffer{NULL}; +}; + +struct JA_Channel_t { + JA_Sound_t* sound{nullptr}; + int pos{0}; + int times{0}; + int group{0}; + SDL_AudioStream* stream{nullptr}; + JA_Channel_state state{JA_CHANNEL_FREE}; +}; + +struct JA_Music_t { + SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + Uint32 length{0}; + Uint8* buffer{nullptr}; + char* filename{nullptr}; + + int pos{0}; + int times{0}; + SDL_AudioStream* stream{nullptr}; + JA_Music_state state{JA_MUSIC_INVALID}; +}; + +// --- Internal Global State --- +// Marcado 'inline' (C++17) para asegurar una única instancia. + +inline JA_Music_t* current_music{nullptr}; +inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; + +inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000}; +inline float JA_musicVolume{1.0f}; +inline float JA_soundVolume[JA_MAX_GROUPS]; +inline bool JA_musicEnabled{true}; +inline bool JA_soundEnabled{true}; +inline SDL_AudioDeviceID sdlAudioDevice{0}; + +inline bool fading{false}; +inline int fade_start_time{0}; +inline int fade_duration{0}; +inline float fade_initial_volume{0.0f}; // Corregido de 'int' a 'float' + +// --- Forward Declarations --- +inline void JA_StopMusic(); +inline void JA_StopChannel(const int channel); +inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0); + +// --- Core Functions --- + +inline void JA_Update() { + if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) { + if (fading) { + int time = SDL_GetTicks(); + if (time > (fade_start_time + fade_duration)) { + fading = false; + JA_StopMusic(); + return; + } else { + const int time_passed = time - fade_start_time; + const float percent = (float)time_passed / (float)fade_duration; + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent)); + } + } + + if (current_music->times != 0) { + if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length / 2)) { + SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length); + } + if (current_music->times > 0) current_music->times--; + } else { + if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic(); + } + } + + if (JA_soundEnabled) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) + if (channels[i].state == JA_CHANNEL_PLAYING) { + if (channels[i].times != 0) { + if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) { + SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length); + if (channels[i].times > 0) channels[i].times--; + } + } else { + if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i); + } + } + } +} + +inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { +#ifdef _DEBUG + SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG); +#endif + + JA_audioSpec = {format, num_channels, freq}; + if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice + sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); + if (sdlAudioDevice == 0) SDL_Log("Failed to initialize SDL audio!"); + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE; + for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f; +} + +inline void JA_Quit() { + if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice + sdlAudioDevice = 0; +} + +// --- Music Functions --- + +inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { + JA_Music_t* music = new JA_Music_t(); + + int chan, samplerate; + short* output; + music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2; + + music->spec.channels = chan; + music->spec.freq = samplerate; + music->spec.format = SDL_AUDIO_S16; + music->buffer = static_cast(SDL_malloc(music->length)); + SDL_memcpy(music->buffer, output, music->length); + free(output); + music->pos = 0; + music->state = JA_MUSIC_STOPPED; + + return music; +} + +inline JA_Music_t* JA_LoadMusic(const char* filename) { + // [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid. + FILE* f = fopen(filename, "rb"); + if (!f) return NULL; // Añadida comprobación de apertura + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + auto* buffer = static_cast(malloc(fsize + 1)); + if (!buffer) { // Añadida comprobación de malloc + fclose(f); + return NULL; + } + if (fread(buffer, fsize, 1, f) != 1) { + fclose(f); + free(buffer); + return NULL; + } + fclose(f); + + JA_Music_t* music = JA_LoadMusic(buffer, fsize); + if (music) { // Comprobar que JA_LoadMusic tuvo éxito + music->filename = static_cast(malloc(strlen(filename) + 1)); + if (music->filename) { + strcpy(music->filename, filename); + } + } + + free(buffer); + + return music; +} + +inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) { + if (!JA_musicEnabled || !music) return; // Añadida comprobación de music + + JA_StopMusic(); + + current_music = music; + current_music->pos = 0; + current_music->state = JA_MUSIC_PLAYING; + current_music->times = loop; + + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); + if (!current_music->stream) { // Comprobar creación de stream + SDL_Log("Failed to create audio stream!"); + current_music->state = JA_MUSIC_STOPPED; + return; + } + if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n"); + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); + if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n"); +} + +inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) { + if (!music) music = current_music; + if (!music) return nullptr; // Añadida comprobación + return music->filename; +} + +inline void JA_PauseMusic() { + if (!JA_musicEnabled) return; + if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; // Comprobación mejorada + + current_music->state = JA_MUSIC_PAUSED; + SDL_UnbindAudioStream(current_music->stream); +} + +inline void JA_ResumeMusic() { + if (!JA_musicEnabled) return; + if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; // Comprobación mejorada + + current_music->state = JA_MUSIC_PLAYING; + SDL_BindAudioStream(sdlAudioDevice, current_music->stream); +} + +inline void JA_StopMusic() { + if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return; + + current_music->pos = 0; + current_music->state = JA_MUSIC_STOPPED; + if (current_music->stream) { + SDL_DestroyAudioStream(current_music->stream); + current_music->stream = nullptr; + } + // No liberamos filename aquí, se debería liberar en JA_DeleteMusic +} + +inline void JA_FadeOutMusic(const int milliseconds) { + if (!JA_musicEnabled) return; + if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return; + + fading = true; + fade_start_time = SDL_GetTicks(); + fade_duration = milliseconds; + fade_initial_volume = JA_musicVolume; +} + +inline JA_Music_state JA_GetMusicState() { + if (!JA_musicEnabled) return JA_MUSIC_DISABLED; + if (!current_music) return JA_MUSIC_INVALID; + + return current_music->state; +} + +inline void JA_DeleteMusic(JA_Music_t* music) { + if (!music) return; + if (current_music == music) { + JA_StopMusic(); + current_music = nullptr; + } + SDL_free(music->buffer); + if (music->stream) SDL_DestroyAudioStream(music->stream); + free(music->filename); // filename se libera aquí + delete music; +} + +inline float JA_SetMusicVolume(float volume) { + JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f); + if (current_music && current_music->stream) { + SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); + } + return JA_musicVolume; +} + +inline void JA_SetMusicPosition(float value) { + if (!current_music) return; + current_music->pos = value * current_music->spec.freq; + // Nota: Esta implementación de 'pos' no parece usarse en JA_Update para + // el streaming. El streaming siempre parece empezar desde el principio. +} + +inline float JA_GetMusicPosition() { + if (!current_music) return 0; + return float(current_music->pos) / float(current_music->spec.freq); + // Nota: Ver `JA_SetMusicPosition` +} + +inline void JA_EnableMusic(const bool value) { + if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic(); + + JA_musicEnabled = value; +} + +// --- Sound Functions --- + +inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) { + JA_Sound_t* sound = new JA_Sound_t(); + sound->buffer = buffer; + sound->length = length; + // Nota: spec se queda con los valores por defecto. + return sound; +} + +inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) { + JA_Sound_t* sound = new JA_Sound_t(); + if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) { + SDL_Log("Failed to load WAV from memory: %s", SDL_GetError()); + delete sound; + return nullptr; + } + return sound; +} + +inline JA_Sound_t* JA_LoadSound(const char* filename) { + JA_Sound_t* sound = new JA_Sound_t(); + if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) { + SDL_Log("Failed to load WAV file: %s", SDL_GetError()); + delete sound; + return nullptr; + } + return sound; +} + +inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) { + if (!JA_soundEnabled || !sound) return -1; + + int channel = 0; + while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; } + if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) { + // No hay canal libre, reemplazamos el primero + channel = 0; + } + + return JA_PlaySoundOnChannel(sound, channel, loop, group); +} + +inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) { + if (!JA_soundEnabled || !sound) return -1; + if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1; + + JA_StopChannel(channel); // Detiene y limpia el canal si estaba en uso + + channels[channel].sound = sound; + channels[channel].times = loop; + channels[channel].pos = 0; + channels[channel].group = group; // Asignar grupo + channels[channel].state = JA_CHANNEL_PLAYING; + channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); + + if (!channels[channel].stream) { + SDL_Log("Failed to create audio stream for sound!"); + channels[channel].state = JA_CHANNEL_FREE; + return -1; + } + + SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length); + SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); + SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); + + return channel; +} + +inline void JA_DeleteSound(JA_Sound_t* sound) { + if (!sound) return; + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].sound == sound) JA_StopChannel(i); + } + SDL_free(sound->buffer); + delete sound; +} + +inline void JA_PauseChannel(const int channel) { + if (!JA_soundEnabled) return; + + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) + if (channels[i].state == JA_CHANNEL_PLAYING) { + channels[i].state = JA_CHANNEL_PAUSED; + SDL_UnbindAudioStream(channels[i].stream); + } + } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == JA_CHANNEL_PLAYING) { + channels[channel].state = JA_CHANNEL_PAUSED; + SDL_UnbindAudioStream(channels[channel].stream); + } + } +} + +inline void JA_ResumeChannel(const int channel) { + if (!JA_soundEnabled) return; + + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) + if (channels[i].state == JA_CHANNEL_PAUSED) { + channels[i].state = JA_CHANNEL_PLAYING; + SDL_BindAudioStream(sdlAudioDevice, channels[i].stream); + } + } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == JA_CHANNEL_PAUSED) { + channels[channel].state = JA_CHANNEL_PLAYING; + SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); + } + } +} + +inline void JA_StopChannel(const int channel) { + if (channel == -1) { + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].state != JA_CHANNEL_FREE) { + if (channels[i].stream) SDL_DestroyAudioStream(channels[i].stream); + channels[i].stream = nullptr; + channels[i].state = JA_CHANNEL_FREE; + channels[i].pos = 0; + channels[i].sound = NULL; + } + } + } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state != JA_CHANNEL_FREE) { + if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream); + channels[channel].stream = nullptr; + channels[channel].state = JA_CHANNEL_FREE; + channels[channel].pos = 0; + channels[channel].sound = NULL; + } + } +} + +inline JA_Channel_state JA_GetChannelState(const int channel) { + if (!JA_soundEnabled) return JA_SOUND_DISABLED; + if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID; + + return channels[channel].state; +} + +inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para todos los grupos +{ + const float v = SDL_clamp(volume, 0.0f, 1.0f); + + if (group == -1) { + for (int i = 0; i < JA_MAX_GROUPS; ++i) { + JA_soundVolume[i] = v; + } + } else if (group >= 0 && group < JA_MAX_GROUPS) { + JA_soundVolume[group] = v; + } else { + return v; // Grupo inválido + } + + // Aplicar volumen a canales activos + for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { + if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) { + if (group == -1 || channels[i].group == group) { + if (channels[i].stream) { + SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[channels[i].group]); + } + } + } + } + return v; +} + +inline void JA_EnableSound(const bool value) { + if (!value) { + JA_StopChannel(-1); // Detener todos los canales + } + JA_soundEnabled = value; +} + +inline float JA_SetVolume(float volume) { + float v = JA_SetMusicVolume(volume); + JA_SetSoundVolume(v, -1); // Aplicar a todos los grupos de sonido + return v; +} \ No newline at end of file diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp new file mode 100644 index 0000000..6af3f87 --- /dev/null +++ b/source/core/input/input.cpp @@ -0,0 +1,318 @@ +#include "core/input/input.hpp" + +#include + +#include +#include +#include +#include +#include + +// Singleton +Input* Input::instance = nullptr; + +void Input::init(const std::string& game_controller_db_path) { + Input::instance = new Input(game_controller_db_path); +} + +void Input::destroy() { delete Input::instance; } +auto Input::get() -> Input* { return Input::instance; } + +// Constructor +// Los bindings de sistema se mapean a F1-F12. +// Añade los bindings de tu juego (LEFT, RIGHT, etc.) llamando a bindKey() después de init(). +Input::Input(std::string game_controller_db_path) + : gamepad_mappings_file_(std::move(game_controller_db_path)) { + keyboard_.bindings = { + // Controles de sistema + {Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}}, + {Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}}, + {Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}}, + {Action::TOGGLE_POSTFX, KeyState{.scancode = SDL_SCANCODE_F4}}, + {Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}}, + {Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}}, + {Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}}, + {Action::TOGGLE_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}}, + {Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}}, + {Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}}, + {Action::TOGGLE_DEBUG, KeyState{.scancode = SDL_SCANCODE_F12}}}; + + initSDLGamePad(); +} + +void Input::bindKey(Action action, SDL_Scancode code) { + keyboard_.bindings[action].scancode = code; +} + +void Input::bindGameControllerButton(const std::shared_ptr& gamepad, Action action, SDL_GamepadButton button) { // NOLINT(readability-convert-member-functions-to-static) + if (gamepad != nullptr) { + gamepad->bindings[action].button = button; + } +} + +void Input::bindGameControllerButton(const std::shared_ptr& gamepad, Action action_target, Action action_source) { // NOLINT(readability-convert-member-functions-to-static) + if (gamepad != nullptr) { + gamepad->bindings[action_target].button = gamepad->bindings[action_source].button; + } +} + +auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const std::shared_ptr& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static) + bool success_keyboard = false; + bool success_controller = false; + + if (check_keyboard) { + if (repeat) { + success_keyboard = keyboard_.bindings[action].is_held; + } else { + success_keyboard = keyboard_.bindings[action].just_pressed; + } + } + + std::shared_ptr active_gamepad = gamepad; + if (active_gamepad == nullptr && !gamepads_.empty()) { + active_gamepad = gamepads_[0]; + } + + if (active_gamepad != nullptr) { + success_controller = checkAxisInput(action, active_gamepad, repeat); + + if (!success_controller) { + success_controller = checkTriggerInput(action, active_gamepad, repeat); + } + + if (!success_controller) { + if (repeat) { + success_controller = active_gamepad->bindings[action].is_held; + } else { + success_controller = active_gamepad->bindings[action].just_pressed; + } + } + } + + return (success_keyboard || success_controller); +} + +auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static) + if (check_keyboard) { + for (const auto& pair : keyboard_.bindings) { + if (pair.second.just_pressed) return true; + } + } + + std::shared_ptr active_gamepad = gamepad; + if (active_gamepad == nullptr && !gamepads_.empty()) { + active_gamepad = gamepads_[0]; + } + + if (active_gamepad != nullptr) { + for (const auto& pair : active_gamepad->bindings) { + if (pair.second.just_pressed) return true; + } + } + + return false; +} + +auto Input::checkAnyButton(bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static) + for (auto bi : BUTTON_INPUTS) { + if (checkAction(bi, repeat, CHECK_KEYBOARD)) return true; + for (const auto& gamepad : gamepads_) { + if (checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad)) return true; + } + } + return false; +} + +auto Input::gameControllerFound() const -> bool { return !gamepads_.empty(); } + +auto Input::getControllerName(const std::shared_ptr& gamepad) -> std::string { + return gamepad == nullptr ? std::string() : gamepad->name; +} + +auto Input::getControllerNames() const -> std::vector { + std::vector names; + for (const auto& gamepad : gamepads_) { + names.push_back(gamepad->name); + } + return names; +} + +auto Input::getNumGamepads() const -> int { return gamepads_.size(); } + +auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr { // NOLINT(readability-convert-member-functions-to-static) + for (const auto& gamepad : gamepads_) { + if (gamepad->instance_id == id) return gamepad; + } + return nullptr; +} + +auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr { // NOLINT(readability-convert-member-functions-to-static) + for (const auto& gamepad : gamepads_) { + if (gamepad && gamepad->name == name) return gamepad; + } + return nullptr; +} + +auto Input::getControllerBinding(const std::shared_ptr& gamepad, Action action) -> SDL_GamepadButton { // NOLINT(readability-convert-member-functions-to-static) + return static_cast(gamepad->bindings[action].button); +} + +auto Input::checkAxisInput(Action action, const std::shared_ptr& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static) + auto& binding = gamepad->bindings[action]; + if (binding.button < 200) return false; + + bool axis_active_now = false; + if (binding.button == 200) { + axis_active_now = SDL_GetGamepadAxis(gamepad->pad, SDL_GAMEPAD_AXIS_LEFTX) < -AXIS_THRESHOLD; + } else if (binding.button == 201) { + axis_active_now = SDL_GetGamepadAxis(gamepad->pad, SDL_GAMEPAD_AXIS_LEFTX) > AXIS_THRESHOLD; + } else { + return false; + } + + if (repeat) return axis_active_now; + if (axis_active_now && !binding.axis_active) { binding.axis_active = true; return true; } + if (!axis_active_now && binding.axis_active) { binding.axis_active = false; } + return false; +} + +auto Input::checkTriggerInput(Action action, const std::shared_ptr& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static) + if (gamepad->bindings[action].button == static_cast(SDL_GAMEPAD_BUTTON_INVALID)) return false; + + int button = gamepad->bindings[action].button; + bool trigger_active_now = false; + + if (button == TRIGGER_L2_AS_BUTTON) { + trigger_active_now = SDL_GetGamepadAxis(gamepad->pad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > TRIGGER_THRESHOLD; + } else if (button == TRIGGER_R2_AS_BUTTON) { + trigger_active_now = SDL_GetGamepadAxis(gamepad->pad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > TRIGGER_THRESHOLD; + } else { + return false; + } + + auto& binding = gamepad->bindings[action]; + if (repeat) return trigger_active_now; + if (trigger_active_now && !binding.trigger_active) { binding.trigger_active = true; return true; } + if (!trigger_active_now && binding.trigger_active) { binding.trigger_active = false; } + return false; +} + +void Input::addGamepadMappingsFromFile() { // NOLINT(readability-convert-member-functions-to-static) + if (SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str()) < 0) { + std::cout << "Error, could not load " << gamepad_mappings_file_.c_str() << ": " << SDL_GetError() << '\n'; + } +} + +void Input::discoverGamepads() { // NOLINT(readability-convert-member-functions-to-static) + SDL_Event event; + while (SDL_PollEvent(&event)) { + handleEvent(event); + } +} + +void Input::initSDLGamePad() { + if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) { + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GAMEPAD could not initialize! SDL Error: %s", SDL_GetError()); + } else { + addGamepadMappingsFromFile(); + discoverGamepads(); + std::cout << "\n** INPUT SYSTEM **\n"; + std::cout << "Input System initialized successfully\n"; + } + } +} + +void Input::resetInputStates() { + for (auto& key : keyboard_.bindings) { + key.second.is_held = false; + key.second.just_pressed = false; + } + for (const auto& gamepad : gamepads_) { + for (auto& binding : gamepad->bindings) { + binding.second.is_held = false; + binding.second.just_pressed = false; + binding.second.trigger_active = false; + } + } +} + +void Input::update() { // NOLINT(readability-convert-member-functions-to-static) + const bool* key_states = SDL_GetKeyboardState(nullptr); + + for (auto& binding : keyboard_.bindings) { + bool key_is_down_now = key_states[binding.second.scancode]; + binding.second.just_pressed = key_is_down_now && !binding.second.is_held; + binding.second.is_held = key_is_down_now; + } + + for (const auto& gamepad : gamepads_) { + for (auto& binding : gamepad->bindings) { + bool button_is_down_now = static_cast(SDL_GetGamepadButton(gamepad->pad, static_cast(binding.second.button))) != 0; + binding.second.just_pressed = button_is_down_now && !binding.second.is_held; + binding.second.is_held = button_is_down_now; + } + } +} + +auto Input::handleEvent(const SDL_Event& event) -> std::string { // NOLINT(readability-convert-member-functions-to-static) + switch (event.type) { + case SDL_EVENT_GAMEPAD_ADDED: + return addGamepad(event.gdevice.which); + case SDL_EVENT_GAMEPAD_REMOVED: + return removeGamepad(event.gdevice.which); + } + return {}; +} + +auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static) + SDL_Gamepad* pad = SDL_OpenGamepad(device_index); + if (pad == nullptr) { + std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n'; + return {}; + } + + auto gamepad = std::make_shared(pad); + auto name = gamepad->name; + std::cout << "Gamepad connected (" << name << ")\n"; + gamepads_.push_back(std::move(gamepad)); + return name + " CONNECTED"; +} + +auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readability-convert-member-functions-to-static) + auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr& gamepad) -> bool { + return gamepad->instance_id == id; + }); + + if (it != gamepads_.end()) { + std::string name = (*it)->name; + std::cout << "Gamepad disconnected (" << name << ")\n"; + gamepads_.erase(it); + return name + " DISCONNECTED"; + } + std::cerr << "No se encontró el gamepad con ID " << id << '\n'; + return {}; +} + +void Input::printConnectedGamepads() const { // NOLINT(readability-convert-member-functions-to-static) + if (gamepads_.empty()) { + std::cout << "No hay gamepads conectados.\n"; + return; + } + std::cout << "Gamepads conectados:\n"; + for (const auto& gamepad : gamepads_) { + std::string name = gamepad->name.empty() ? "Desconocido" : gamepad->name; + std::cout << " - ID: " << gamepad->instance_id << ", Nombre: " << name << "\n"; + } +} + +auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr { // NOLINT(readability-convert-member-functions-to-static) + if (gamepads_.empty()) return nullptr; + for (const auto& gamepad : gamepads_) { + if (gamepad && gamepad->name == gamepad_name) return gamepad; + } + for (const auto& gamepad : gamepads_) { + if (gamepad) return gamepad; + } + return nullptr; +} diff --git a/source/core/input/input.hpp b/source/core/input/input.hpp new file mode 100644 index 0000000..d1e2dca --- /dev/null +++ b/source/core/input/input.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include // Para SDL_Scancode, SDL_GamepadButton, SDL_JoystickID, SDL_CloseGamepad, SDL_Gamepad, etc. + +#include // Para array +#include // Para shared_ptr +#include // Para string +#include // Para unordered_map +#include // Para pair +#include // Para vector + +#include "core/input/input_types.hpp" // Para InputAction + +// --- Clase Input: gestiona la entrada de teclado y mandos (singleton) --- +class Input { + public: + // --- Constantes --- + static constexpr bool ALLOW_REPEAT = true; + static constexpr bool DO_NOT_ALLOW_REPEAT = false; + static constexpr bool CHECK_KEYBOARD = true; + static constexpr bool DO_NOT_CHECK_KEYBOARD = false; + static constexpr int TRIGGER_L2_AS_BUTTON = 100; + static constexpr int TRIGGER_R2_AS_BUTTON = 101; + + // --- Tipos --- + using Action = InputAction; + + // --- Estructuras --- + struct KeyState { + Uint8 scancode{0}; + bool is_held{false}; + bool just_pressed{false}; + }; + + struct ButtonState { + int button{static_cast(SDL_GAMEPAD_BUTTON_INVALID)}; + bool is_held{false}; + bool just_pressed{false}; + bool axis_active{false}; + bool trigger_active{false}; + }; + + struct Keyboard { + std::unordered_map bindings; + }; + + struct Gamepad { + SDL_Gamepad* pad{nullptr}; + SDL_JoystickID instance_id{0}; + std::string name; + std::string path; + std::unordered_map bindings; + + explicit Gamepad(SDL_Gamepad* gamepad) + : pad(gamepad), + instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))), + name(std::string(SDL_GetGamepadName(gamepad))), + path(std::string(SDL_GetGamepadPath(pad))), + bindings{} {} // Sin bindings por defecto — define los de tu juego en main + + ~Gamepad() { + if (pad != nullptr) { + SDL_CloseGamepad(pad); + } + } + + void rebindAction(Action action, SDL_GamepadButton new_button) { + bindings[action].button = static_cast(new_button); + } + }; + + using Gamepads = std::vector>; + + // --- Singleton --- + static void init(const std::string& game_controller_db_path); + static void destroy(); + static auto get() -> Input*; + + // --- Actualización --- + void update(); + + // --- Configuración de controles --- + void bindKey(Action action, SDL_Scancode code); + static void bindGameControllerButton(const std::shared_ptr& gamepad, Action action, SDL_GamepadButton button); + static void bindGameControllerButton(const std::shared_ptr& gamepad, Action action_target, Action action_source); + + // --- Consulta de entrada --- + auto checkAction(Action action, bool repeat = true, bool check_keyboard = true, const std::shared_ptr& gamepad = nullptr) -> bool; + auto checkAnyInput(bool check_keyboard = true, const std::shared_ptr& gamepad = nullptr) -> bool; + auto checkAnyButton(bool repeat = DO_NOT_ALLOW_REPEAT) -> bool; + void resetInputStates(); + + // --- Gestión de gamepads --- + [[nodiscard]] auto gameControllerFound() const -> bool; + [[nodiscard]] auto getNumGamepads() const -> int; + [[nodiscard]] auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr; + [[nodiscard]] auto getGamepadByName(const std::string& name) const -> std::shared_ptr; + [[nodiscard]] auto getGamepads() const -> const Gamepads& { return gamepads_; } + auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr; + static auto getControllerName(const std::shared_ptr& gamepad) -> std::string; + [[nodiscard]] auto getControllerNames() const -> std::vector; + [[nodiscard]] static auto getControllerBinding(const std::shared_ptr& gamepad, Action action) -> SDL_GamepadButton; + void printConnectedGamepads() const; + + // --- Eventos --- + auto handleEvent(const SDL_Event& event) -> std::string; + + private: + static constexpr Sint16 AXIS_THRESHOLD = 30000; + static constexpr Sint16 TRIGGER_THRESHOLD = 16384; + // Si tu juego tiene acciones con botones analógicos (triggers/ejes), añádelos aquí: + static constexpr std::array BUTTON_INPUTS = {}; + + explicit Input(std::string game_controller_db_path); + ~Input() = default; + + void initSDLGamePad(); + static auto checkAxisInput(Action action, const std::shared_ptr& gamepad, bool repeat) -> bool; + static auto checkTriggerInput(Action action, const std::shared_ptr& gamepad, bool repeat) -> bool; + auto addGamepad(int device_index) -> std::string; + auto removeGamepad(SDL_JoystickID id) -> std::string; + void addGamepadMappingsFromFile(); + void discoverGamepads(); + + static Input* instance; + + Gamepads gamepads_; + Keyboard keyboard_{}; + std::string gamepad_mappings_file_; +}; diff --git a/source/core/input/input_types.cpp b/source/core/input/input_types.cpp new file mode 100644 index 0000000..1b3fe61 --- /dev/null +++ b/source/core/input/input_types.cpp @@ -0,0 +1,75 @@ +#include "input_types.hpp" + +#include // Para pair + +// Definición de los mapas acción ↔ string +// Si añades acciones al enum, añádelas también aquí para que sean serializables +const std::unordered_map ACTION_TO_STRING = { + // Sistema + {InputAction::WINDOW_INC_ZOOM, "WINDOW_INC_ZOOM"}, + {InputAction::WINDOW_DEC_ZOOM, "WINDOW_DEC_ZOOM"}, + {InputAction::TOGGLE_FULLSCREEN, "TOGGLE_FULLSCREEN"}, + {InputAction::TOGGLE_VSYNC, "TOGGLE_VSYNC"}, + {InputAction::TOGGLE_INTEGER_SCALE, "TOGGLE_INTEGER_SCALE"}, + {InputAction::TOGGLE_POSTFX, "TOGGLE_POSTFX"}, + {InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"}, + {InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"}, + {InputAction::TOGGLE_BORDER, "TOGGLE_BORDER"}, + {InputAction::TOGGLE_MUSIC, "TOGGLE_MUSIC"}, + {InputAction::NEXT_PALETTE, "NEXT_PALETTE"}, + {InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"}, + {InputAction::SHOW_DEBUG_INFO, "SHOW_DEBUG_INFO"}, + {InputAction::TOGGLE_DEBUG, "TOGGLE_DEBUG"}, + // Añade aquí los mapeos de tus acciones de juego + {InputAction::NONE, "NONE"}}; + +const std::unordered_map STRING_TO_ACTION = { + // Sistema + {"WINDOW_INC_ZOOM", InputAction::WINDOW_INC_ZOOM}, + {"WINDOW_DEC_ZOOM", InputAction::WINDOW_DEC_ZOOM}, + {"TOGGLE_FULLSCREEN", InputAction::TOGGLE_FULLSCREEN}, + {"TOGGLE_VSYNC", InputAction::TOGGLE_VSYNC}, + {"TOGGLE_INTEGER_SCALE", InputAction::TOGGLE_INTEGER_SCALE}, + {"TOGGLE_POSTFX", InputAction::TOGGLE_POSTFX}, + {"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET}, + {"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING}, + {"TOGGLE_BORDER", InputAction::TOGGLE_BORDER}, + {"TOGGLE_MUSIC", InputAction::TOGGLE_MUSIC}, + {"NEXT_PALETTE", InputAction::NEXT_PALETTE}, + {"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE}, + {"SHOW_DEBUG_INFO", InputAction::SHOW_DEBUG_INFO}, + {"TOGGLE_DEBUG", InputAction::TOGGLE_DEBUG}, + // Añade aquí los mapeos de tus acciones de juego + {"NONE", InputAction::NONE}}; + +const std::unordered_map BUTTON_TO_STRING = { + {SDL_GAMEPAD_BUTTON_WEST, "WEST"}, + {SDL_GAMEPAD_BUTTON_NORTH, "NORTH"}, + {SDL_GAMEPAD_BUTTON_EAST, "EAST"}, + {SDL_GAMEPAD_BUTTON_SOUTH, "SOUTH"}, + {SDL_GAMEPAD_BUTTON_START, "START"}, + {SDL_GAMEPAD_BUTTON_BACK, "BACK"}, + {SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "LEFT_SHOULDER"}, + {SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "RIGHT_SHOULDER"}, + {SDL_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP"}, + {SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"}, + {SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"}, + {SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"}, + {static_cast(100), "L2_AS_BUTTON"}, + {static_cast(101), "R2_AS_BUTTON"}}; + +const std::unordered_map STRING_TO_BUTTON = { + {"WEST", SDL_GAMEPAD_BUTTON_WEST}, + {"NORTH", SDL_GAMEPAD_BUTTON_NORTH}, + {"EAST", SDL_GAMEPAD_BUTTON_EAST}, + {"SOUTH", SDL_GAMEPAD_BUTTON_SOUTH}, + {"START", SDL_GAMEPAD_BUTTON_START}, + {"BACK", SDL_GAMEPAD_BUTTON_BACK}, + {"LEFT_SHOULDER", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}, + {"RIGHT_SHOULDER", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, + {"DPAD_UP", SDL_GAMEPAD_BUTTON_DPAD_UP}, + {"DPAD_DOWN", SDL_GAMEPAD_BUTTON_DPAD_DOWN}, + {"DPAD_LEFT", SDL_GAMEPAD_BUTTON_DPAD_LEFT}, + {"DPAD_RIGHT", SDL_GAMEPAD_BUTTON_DPAD_RIGHT}, + {"L2_AS_BUTTON", static_cast(100)}, + {"R2_AS_BUTTON", static_cast(101)}}; diff --git a/source/core/input/input_types.hpp b/source/core/input/input_types.hpp new file mode 100644 index 0000000..bfa0098 --- /dev/null +++ b/source/core/input/input_types.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include + +// --- Enum InputAction --- +// Acciones de entrada disponibles. +// +// Las acciones de sistema (F1-F12) están listas para usar. +// Añade tus acciones de juego debajo del bloque marcado. +enum class InputAction : int { + // [SISTEMA] Controles de ventana y video (mapeados por defecto a F1-F12) + WINDOW_DEC_ZOOM, // F1 - Reduce el zoom de la ventana + WINDOW_INC_ZOOM, // F2 - Aumenta el zoom de la ventana + TOGGLE_FULLSCREEN, // F3 - Pantalla completa / ventana + TOGGLE_POSTFX, // F4 - Activa/desactiva PostFX + NEXT_PALETTE, // F5 - Siguiente paleta + PREVIOUS_PALETTE, // F6 - Paleta anterior + TOGGLE_INTEGER_SCALE, // F7 - Escalado entero + TOGGLE_MUSIC, // F8 - Silencia/activa música + TOGGLE_BORDER, // F9 - Muestra/oculta borde + TOGGLE_VSYNC, // F10 - VSync + TOGGLE_SUPERSAMPLING, // Supersampling PostFX + NEXT_POSTFX_PRESET, // Siguiente preset PostFX + SHOW_DEBUG_INFO, // Muestra info de debug + TOGGLE_DEBUG, // F12 - Activa/desactiva debug + + // [JUEGO] Añade aquí las acciones específicas de tu juego, por ejemplo: + // LEFT, + // RIGHT, + // JUMP, + // ATTACK, + // ACCEPT, + // CANCEL, + // PAUSE, + + // Obligatorio - no eliminar + NONE, + SIZE, +}; + +// --- Mapeos --- +extern const std::unordered_map ACTION_TO_STRING; // Acción → string +extern const std::unordered_map STRING_TO_ACTION; // String → acción +extern const std::unordered_map BUTTON_TO_STRING; // Botón SDL → string +extern const std::unordered_map STRING_TO_BUTTON; // String → botón SDL diff --git a/source/core/input/mouse.cpp b/source/core/input/mouse.cpp new file mode 100644 index 0000000..87c1de4 --- /dev/null +++ b/source/core/input/mouse.cpp @@ -0,0 +1,25 @@ +#include "core/input/mouse.hpp" + +namespace Mouse { + Uint32 cursor_hide_time = 3000; // Tiempo en milisegundos para ocultar el cursor + Uint32 last_mouse_move_time = 0; // Última vez que el ratón se movió + bool cursor_visible = true; // Estado del cursor + + void handleEvent(const SDL_Event& event) { + if (event.type == SDL_EVENT_MOUSE_MOTION) { + last_mouse_move_time = SDL_GetTicks(); + if (!cursor_visible) { + SDL_ShowCursor(); + cursor_visible = true; + } + } + } + + void updateCursorVisibility() { + Uint32 current_time = SDL_GetTicks(); + if (cursor_visible && (current_time - last_mouse_move_time > cursor_hide_time)) { + SDL_HideCursor(); + cursor_visible = false; + } + } +} // namespace Mouse diff --git a/source/core/input/mouse.hpp b/source/core/input/mouse.hpp new file mode 100644 index 0000000..abc5d3f --- /dev/null +++ b/source/core/input/mouse.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace Mouse { + extern Uint32 cursor_hide_time; // Tiempo en milisegundos para ocultar el cursor + extern Uint32 last_mouse_move_time; // Última vez que el ratón se movió + extern bool cursor_visible; // Estado del cursor + + void handleEvent(const SDL_Event& event); + void updateCursorVisibility(); +} // namespace Mouse \ No newline at end of file diff --git a/source/core/options.hpp b/source/core/options.hpp new file mode 100644 index 0000000..cdf33e4 --- /dev/null +++ b/source/core/options.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include // Para string +#include // Para vector + +// --- Namespace Options: configuración del juego --- +// Rellena los valores antes de llamar a Screen::init() y Audio::init() +namespace Options { + + // Borde alrededor del área de juego (en pixels de la resolución lógica) + struct Border { + bool enabled{false}; // Activa el borde + float width{0.0F}; // Ancho del borde (izquierda + derecha) + float height{0.0F}; // Alto del borde (arriba + abajo) + }; + + // Opciones de vídeo + struct Video { + bool fullscreen{false}; // Pantalla completa al arrancar + int filter{0}; // Filtro de escalado: 0 = NEAREST, 1 = LINEAR + bool vertical_sync{false}; // VSync + bool postfx{false}; // Efectos PostFX (shaders) + bool supersampling{false}; // Supersampling 3× para PostFX + bool integer_scale{false}; // Escalado entero en fullscreen + Border border{}; // Borde de pantalla + std::string palette{}; // Nombre de la paleta activa (sin extensión) + std::string palettes_path{}; // Directorio donde buscar ficheros .pal + std::string info; // Info del modo de vídeo (rellenada por Screen) + }; + + // Opciones de ventana + struct Window { + std::string caption{"Game"}; // Título de la ventana + int zoom{2}; // Factor de zoom inicial + int max_zoom{2}; // Máximo zoom (calculado por Screen) + }; + + // Resolución lógica del juego (en pixels indexados) + struct Game { + float width{320.0F}; + float height{180.0F}; + }; + + // Opciones de música + struct Music { + bool enabled{true}; // Activa la música + float volume{1.0F}; // Volumen (0.0 – 1.0) + }; + + // Opciones de efectos de sonido + struct Sound { + bool enabled{true}; // Activa los efectos + float volume{1.0F}; // Volumen (0.0 – 1.0) + }; + + // Opciones de audio + struct Audio { + Music music{}; // Música + Sound sound{}; // Efectos + bool enabled{true}; // Activa el sistema de audio + float volume{1.0F}; // Volumen global (0.0 – 1.0) + }; + + // Preset de efectos PostFX + struct PostFXPreset { + std::string name; + float vignette{0.6F}; // Viñeta (0 = ninguna, 1 = máxima) + float scanlines{0.7F}; // Scanlines CRT + float chroma{0.15F}; // Aberración cromática + float mask{0.0F}; // Máscara de fósforo RGB + float gamma{0.0F}; // Corrección gamma + float curvature{0.0F}; // Distorsión barrel CRT + float bleeding{0.0F}; // Sangrado de color NTSC + float flicker{0.0F}; // Parpadeo de fósforo + }; + + // Configuración de fuente de texto para Screen + struct TextConfig { + std::string surface_path{}; // Ruta al GIF con el bitmap de la fuente + std::string fnt_path{}; // Ruta al fichero .fnt de definición de glifos + }; + + // --- Variables globales (inicializa antes de arrancar el juego) --- + inline Game game{}; + inline Video video{}; + inline Window window{}; + inline Audio audio{}; + inline TextConfig text_config{}; + inline bool console{false}; // Imprime mensajes de debug por consola + + inline std::vector postfx_presets{}; + inline int current_postfx_preset{0}; + +} // namespace Options diff --git a/source/core/rendering/gif.cpp b/source/core/rendering/gif.cpp new file mode 100644 index 0000000..654090e --- /dev/null +++ b/source/core/rendering/gif.cpp @@ -0,0 +1,295 @@ +#include "core/rendering/gif.hpp" + +#include // Para memcpy, size_t +#include // Para std::cout +#include // Para runtime_error +#include // Para allocator, char_traits, operator==, basic_string + +namespace GIF { + + // Función inline para reemplazar el macro READ. + // Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'. + inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) { + std::memcpy(dst, buffer, size); + buffer += size; + } + + // Inicializa el diccionario LZW con los valores iniciales + inline void initializeDictionary(std::vector& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming) + int size = 1 << code_length; + dictionary.resize(1 << (code_length + 1)); + for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) { + dictionary[dictionary_ind].byte = static_cast(dictionary_ind); + dictionary[dictionary_ind].prev = -1; + dictionary[dictionary_ind].len = 1; + } + dictionary_ind += 2; // Reservamos espacio para clear y stop codes + } + + // Lee los próximos bits del stream de entrada para formar un código + inline auto readNextCode(const uint8_t*& input, int& input_length, unsigned int& mask, int code_length) -> int { + int code = 0; + for (int i = 0; i < (code_length + 1); i++) { + if (input_length <= 0) { + throw std::runtime_error("Unexpected end of input in decompress"); + } + int bit = ((*input & mask) != 0) ? 1 : 0; + mask <<= 1; + if (mask == 0x100) { + mask = 0x01; + input++; + input_length--; + } + code |= (bit << i); + } + return code; + } + + // Encuentra el primer byte de una cadena del diccionario + inline auto findFirstByte(const std::vector& dictionary, int code) -> uint8_t { + int ptr = code; + while (dictionary[ptr].prev != -1) { + ptr = dictionary[ptr].prev; + } + return dictionary[ptr].byte; + } + + // Agrega una nueva entrada al diccionario + inline void addDictionaryEntry(std::vector& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { // NOLINT(readability-identifier-naming) + uint8_t first_byte; + if (code == dictionary_ind) { + first_byte = findFirstByte(dictionary, prev); + } else { + first_byte = findFirstByte(dictionary, code); + } + + dictionary[dictionary_ind].byte = first_byte; + dictionary[dictionary_ind].prev = prev; + dictionary[dictionary_ind].len = dictionary[prev].len + 1; + dictionary_ind++; + + if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) { + code_length++; + dictionary.resize(1 << (code_length + 1)); + } + } + + // Escribe la cadena decodificada al buffer de salida + inline auto writeDecodedString(const std::vector& dictionary, int code, uint8_t*& out) -> int { + int cur_code = code; + int match_len = dictionary[cur_code].len; + while (cur_code != -1) { + out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte; + if (dictionary[cur_code].prev == cur_code) { + std::cerr << "Internal error; self-reference detected." << '\n'; + throw std::runtime_error("Internal error in decompress: self-reference"); + } + cur_code = dictionary[cur_code].prev; + } + out += match_len; + return match_len; + } + + void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { // NOLINT(readability-convert-member-functions-to-static) + // Verifica que el code_length tenga un rango razonable. + if (code_length < 2 || code_length > 12) { + throw std::runtime_error("Invalid LZW code length"); + } + + int prev = -1; + std::vector dictionary; + int dictionary_ind; + unsigned int mask = 0x01; + int reset_code_length = code_length; + int clear_code = 1 << code_length; + int stop_code = clear_code + 1; + + // Inicializamos el diccionario con el tamaño correspondiente. + initializeDictionary(dictionary, code_length, dictionary_ind); + + // Bucle principal: procesar el stream comprimido. + while (input_length > 0) { + int code = readNextCode(input, input_length, mask, code_length); + + if (code == clear_code) { + // Reinicia el diccionario. + code_length = reset_code_length; + initializeDictionary(dictionary, code_length, dictionary_ind); + prev = -1; + continue; + } + + if (code == stop_code) { + break; + } + + if (prev > -1 && code_length < 12) { + if (code > dictionary_ind) { + std::cerr << "code = " << std::hex << code + << ", but dictionary_ind = " << dictionary_ind << '\n'; + throw std::runtime_error("LZW error: code exceeds dictionary_ind."); + } + + addDictionaryEntry(dictionary, dictionary_ind, code_length, prev, code); + } + + prev = code; + + // Verifica que 'code' sea un índice válido antes de usarlo. + if (code < 0 || static_cast(code) >= dictionary.size()) { + std::cerr << "Invalid LZW code " << code + << ", dictionary size " << dictionary.size() << '\n'; + throw std::runtime_error("LZW error: invalid code encountered"); + } + + writeDecodedString(dictionary, code, out); + } + } + + auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector { // NOLINT(readability-convert-member-functions-to-static) + std::vector data; + uint8_t block_size = *buffer; + buffer++; + while (block_size != 0) { + data.insert(data.end(), buffer, buffer + block_size); + buffer += block_size; + block_size = *buffer; + buffer++; + } + return data; + } + + auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector& gct, int resolution_bits) -> std::vector { // NOLINT(readability-convert-member-functions-to-static) + ImageDescriptor image_descriptor; + // Lee 9 bytes para el image descriptor. + readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); + + uint8_t lzw_code_size; + readBytes(buffer, &lzw_code_size, sizeof(uint8_t)); + + std::vector compressed_data = readSubBlocks(buffer); + int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height; + std::vector uncompressed_data(uncompressed_data_length); + + decompress(lzw_code_size, compressed_data.data(), static_cast(compressed_data.size()), uncompressed_data.data()); + return uncompressed_data; + } + + auto Gif::loadPalette(const uint8_t* buffer) -> std::vector { // NOLINT(readability-convert-member-functions-to-static) + uint8_t header[6]; + std::memcpy(header, buffer, 6); + buffer += 6; + + ScreenDescriptor screen_descriptor; + std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor)); + buffer += sizeof(ScreenDescriptor); + + std::vector global_color_table; + if ((screen_descriptor.fields & 0x80) != 0) { + int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1); + global_color_table.resize(global_color_table_size); + for (int i = 0; i < global_color_table_size; ++i) { + uint8_t r = buffer[0]; + uint8_t g = buffer[1]; + uint8_t b = buffer[2]; + global_color_table[i] = (r << 16) | (g << 8) | b; + buffer += 3; + } + } + return global_color_table; + } + + auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { // NOLINT(readability-convert-member-functions-to-static) + // Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a") + uint8_t header[6]; + std::memcpy(header, buffer, 6); + buffer += 6; + + // Opcional: Validar header + std::string header_str(reinterpret_cast(header), 6); + if (header_str != "GIF87a" && header_str != "GIF89a") { + throw std::runtime_error("Formato de archivo GIF inválido."); + } + + // Leer el Screen Descriptor (7 bytes, empaquetado sin padding) + ScreenDescriptor screen_descriptor; + readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor)); + + // Asigna ancho y alto + w = screen_descriptor.width; + h = screen_descriptor.height; + + int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; + std::vector global_color_table; + if ((screen_descriptor.fields & 0x80) != 0) { + int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1); + global_color_table.resize(global_color_table_size); + std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size); + buffer += 3 * global_color_table_size; + } + + // Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B + uint8_t block_type = *buffer++; + while (block_type != TRAILER) { + if (block_type == EXTENSION_INTRODUCER) // 0x21 + { + // Se lee la etiqueta de extensión, la cual indica el tipo de extensión. + uint8_t extension_label = *buffer++; + switch (extension_label) { + case GRAPHIC_CONTROL: // 0xF9 + { + // Procesar Graphic Control Extension: + uint8_t block_size = *buffer++; // Normalmente, blockSize == 4 + buffer += block_size; // Saltamos los 4 bytes del bloque fijo + // Saltar los sub-bloques + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + case APPLICATION_EXTENSION: // 0xFF + case COMMENT_EXTENSION: // 0xFE + case PLAINTEXT_EXTENSION: // 0x01 + { + // Para estas extensiones, saltamos el bloque fijo y los sub-bloques. + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + default: { + // Si la etiqueta de extensión es desconocida, saltarla también: + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + } + } else if (block_type == IMAGE_DESCRIPTOR) { + // Procesar el Image Descriptor y retornar los datos de imagen + return processImageDescriptor(buffer, global_color_table, color_resolution_bits); + } else { + std::cerr << "Unrecognized block type " << std::hex << static_cast(block_type) << '\n'; + return std::vector{}; + } + block_type = *buffer++; + } + + return std::vector{}; + } + + auto Gif::loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { + return processGifStream(buffer, w, h); + } + +} // namespace GIF diff --git a/source/core/rendering/gif.hpp b/source/core/rendering/gif.hpp new file mode 100644 index 0000000..b6443f1 --- /dev/null +++ b/source/core/rendering/gif.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include // Para uint8_t, uint16_t, uint32_t +#include // Para vector + +namespace GIF { + + // Constantes definidas con constexpr, en lugar de macros + constexpr uint8_t EXTENSION_INTRODUCER = 0x21; + constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C; + constexpr uint8_t TRAILER = 0x3B; + constexpr uint8_t GRAPHIC_CONTROL = 0xF9; + constexpr uint8_t APPLICATION_EXTENSION = 0xFF; + constexpr uint8_t COMMENT_EXTENSION = 0xFE; + constexpr uint8_t PLAINTEXT_EXTENSION = 0x01; + +#pragma pack(push, 1) + struct ScreenDescriptor { + uint16_t width; + uint16_t height; + uint8_t fields; + uint8_t background_color_index; + uint8_t pixel_aspect_ratio; + }; + + struct RGB { + uint8_t r, g, b; + }; + + struct ImageDescriptor { + uint16_t image_left_position; + uint16_t image_top_position; + uint16_t image_width; + uint16_t image_height; + uint8_t fields; + }; +#pragma pack(pop) + + struct DictionaryEntry { + uint8_t byte; + int prev; + int len; + }; + + struct Extension { + uint8_t extension_code; + uint8_t block_size; + }; + + struct GraphicControlExtension { + uint8_t fields; + uint16_t delay_time; + uint8_t transparent_color_index; + }; + + struct ApplicationExtension { + uint8_t application_id[8]; + uint8_t version[3]; + }; + + struct PlaintextExtension { + uint16_t left, top, width, height; + uint8_t cell_width, cell_height; + uint8_t foreground_color, background_color; + }; + + class Gif { + public: + // Descompone (uncompress) el bloque comprimido usando LZW. + // Este método puede lanzar std::runtime_error en caso de error. + static void decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out); + + // Carga la paleta (global color table) a partir de un buffer, + // retornándola en un vector de uint32_t (cada color se compone de R, G, B). + static auto loadPalette(const uint8_t* buffer) -> std::vector; + + // Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y + // asigna el ancho y alto mediante referencias. + static auto loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector; + + private: + // Lee los sub-bloques de datos y los acumula en un std::vector. + static auto readSubBlocks(const uint8_t*& buffer) -> std::vector; + + // Procesa el Image Descriptor y retorna el vector de datos sin comprimir. + static auto processImageDescriptor(const uint8_t*& buffer, const std::vector& gct, int resolution_bits) -> std::vector; + + // Procesa el stream completo del GIF y devuelve los datos sin comprimir. + static auto processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector; + }; + +} // namespace GIF diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp new file mode 100644 index 0000000..39a5a85 --- /dev/null +++ b/source/core/rendering/screen.cpp @@ -0,0 +1,636 @@ +#include "core/rendering/screen.hpp" + +#include + +#include // Para max, min, transform +#include // Para toupper +#include // Para directory_iterator +#include // Para basic_ostream, operator<<, endl, basic_... +#include // Para cerr +#include // Para istreambuf_iterator, operator== +#include // Para char_traits, string, operator+, operator== + +#include "core/input/mouse.hpp" // Para updateCursorVisibility +#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader +#include "core/rendering/surface.hpp" // Para Surface, readPalFile +#include "core/rendering/text.hpp" // Para Text +#include "core/options.hpp" // Para Options +#include "utils/utils.hpp" // Para loadFileBytes + + +// [SINGLETON] +Screen* Screen::screen = nullptr; + +// [SINGLETON] Crearemos el objeto con esta función estática +void Screen::init() { + Screen::screen = new Screen(); +} + +// [SINGLETON] Destruiremos el objeto con esta función estática +void Screen::destroy() { + delete Screen::screen; +} + +// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él +auto Screen::get() -> Screen* { + return Screen::screen; +} + +// Constructor +Screen::Screen() { + // Escanear el directorio de paletas para encontrar ficheros .pal + if (!Options::video.palettes_path.empty()) { + try { + for (const auto& entry : std::filesystem::directory_iterator(Options::video.palettes_path)) { + if (entry.is_regular_file() && entry.path().extension() == ".pal") { + palettes_.push_back(entry.path().string()); + } + } + std::sort(palettes_.begin(), palettes_.end()); + } catch (const std::exception&) { + // Si el directorio no existe o falla, palettes_ queda vacío + } + } + + // Arranca SDL VIDEO, crea la ventana y el renderizador + initSDLVideo(); + if (Options::video.fullscreen) { + SDL_HideCursor(); + } + + // Ajusta los tamaños + game_surface_dstrect_ = {.x = Options::video.border.width, .y = Options::video.border.height, .w = Options::game.width, .h = Options::game.height}; + // adjustWindowSize(); + current_palette_ = findPalette(Options::video.palette); + + // Define el color del borde para el modo de pantalla completa + border_color_ = static_cast(PaletteColor::BLACK); + + // Crea la textura donde se dibujan los graficos del juego + game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, Options::game.width, Options::game.height); + if (game_texture_ == nullptr) { + // Registrar el error si está habilitado + if (Options::console) { + std::cerr << "Error: game_texture_ could not be created!\nSDL Error: " << SDL_GetError() << '\n'; + } + } + SDL_SetTextureScaleMode(game_texture_, SDL_SCALEMODE_NEAREST); + + // Crea la textura donde se dibuja el borde que rodea el area de juego + border_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, Options::game.width + (Options::video.border.width * 2), Options::game.height + (Options::video.border.height * 2)); + if (border_texture_ == nullptr) { + // Registrar el error si está habilitado + if (Options::console) { + std::cerr << "Error: border_texture_ could not be created!\nSDL Error: " << SDL_GetError() << '\n'; + } + } + SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST); + + // Cargar la paleta una sola vez + Palette initial_palette{}; + if (!palettes_.empty()) { + initial_palette = readPalFile(palettes_.at(current_palette_)); + } else { + // Sin ficheros .pal: paleta de grises por defecto + for (int i = 0; i < 256; ++i) { + const auto v = static_cast(i); + initial_palette[static_cast(i)] = (255U << 24U) | (v << 16U) | (v << 8U) | v; + } + } + + // Crea la surface donde se dibujan los graficos del juego + game_surface_ = std::make_shared(Options::game.width, Options::game.height); + game_surface_->setPalette(initial_palette); + game_surface_->clear(static_cast(PaletteColor::BLACK)); + + // Crea la surface para el borde de colores + border_surface_ = std::make_shared(Options::game.width + (Options::video.border.width * 2), Options::game.height + (Options::video.border.height * 2)); + border_surface_->setPalette(initial_palette); + border_surface_->clear(border_color_); + + // Establece la surface que actuará como renderer para recibir las llamadas a render() + renderer_surface_ = std::make_shared>(game_surface_); + + // Crea el objeto de texto para la pantalla de carga + createText(); + + // Extrae el nombre de las paletas desde su ruta + processPaletteList(); + + // Renderizar una vez la textura vacía para que tenga contenido válido + // antes de inicializar los shaders (evita pantalla negra) + SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr); + SDL_RenderTexture(renderer_, border_texture_, nullptr, nullptr); + + // Ahora sí inicializar los shaders + initShaders(); +} + +// Destructor +Screen::~Screen() { + SDL_DestroyTexture(game_texture_); + SDL_DestroyTexture(border_texture_); +} + +// Limpia el renderer +void Screen::clearRenderer(Color color) { + SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF); + SDL_RenderClear(renderer_); +} + +// Prepara para empezar a dibujar en la textura de juego +void Screen::start() { setRendererSurface(nullptr); } + +// Vuelca el contenido del renderizador en pantalla +void Screen::render() { + fps_.increment(); + + // Renderiza todos los overlays (escribe en game_surface_ CPU-side) + renderOverlays(); + + // En el path SDL3GPU, los píxeles se suben directamente desde la Surface. + // En el path SDL_Renderer, primero copiamos la surface a la SDL_Texture. + if (!(shader_backend_ && shader_backend_->isHardwareAccelerated())) { + surfaceToTexture(); + } + + // Copia la textura al renderizador (o hace el present GPU) + textureToRenderer(); +} + +// Establece el modo de video +void Screen::setVideoMode(bool mode) { + // Actualiza las opciones + Options::video.fullscreen = mode; + + // Configura el modo de pantalla y ajusta la ventana + SDL_SetWindowFullscreen(window_, Options::video.fullscreen); + adjustWindowSize(); + adjustRenderLogicalSize(); +} + +// Camibia entre pantalla completa y ventana +void Screen::toggleVideoMode() { + Options::video.fullscreen = !Options::video.fullscreen; + setVideoMode(Options::video.fullscreen); +} + +// Reduce el tamaño de la ventana +auto Screen::decWindowZoom() -> bool { + if (static_cast(Options::video.fullscreen) == 0) { + const int PREVIOUS_ZOOM = Options::window.zoom; + --Options::window.zoom; + Options::window.zoom = std::max(Options::window.zoom, 1); + + if (Options::window.zoom != PREVIOUS_ZOOM) { + setVideoMode(Options::video.fullscreen); + return true; + } + } + + return false; +} + +// Aumenta el tamaño de la ventana +auto Screen::incWindowZoom() -> bool { + if (static_cast(Options::video.fullscreen) == 0) { + const int PREVIOUS_ZOOM = Options::window.zoom; + ++Options::window.zoom; + Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom); + + if (Options::window.zoom != PREVIOUS_ZOOM) { + setVideoMode(Options::video.fullscreen); + return true; + } + } + + return false; +} + +// Cambia el color del borde +void Screen::setBorderColor(Uint8 color) { + border_color_ = color; + border_surface_->clear(border_color_); +} + +// Cambia entre borde visible y no visible +void Screen::toggleBorder() { + Options::video.border.enabled = !Options::video.border.enabled; + setVideoMode(Options::video.fullscreen); + initShaders(); +} + +// Dibuja las notificaciones +void Screen::renderNotifications() const { + // Las notificaciones se implementan en el juego concreto + (void)notifications_enabled_; +} + +// Cambia el estado del PostFX +void Screen::togglePostFX() { + Options::video.postfx = !Options::video.postfx; + if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { + if (Options::video.postfx) { + applyCurrentPostFXPreset(); + } else { + // Pass-through: efectos a 0, el shader copia la textura sin modificar + shader_backend_->setPostFXParams(Rendering::PostFXParams{}); + } + } else { + // Backend no inicializado aún — inicializarlo ahora + initShaders(); + } +} + +// Recarga el shader del preset actual sin toggle +void Screen::reloadPostFX() { + if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { + // El backend ya está activo: solo actualizar uniforms, sin recrear el pipeline + applyCurrentPostFXPreset(); + } else if (Options::video.postfx) { + initShaders(); + } +} + +// Actualiza la lógica de la clase (versión nueva con delta_time para escenas migradas) +void Screen::update(float delta_time) { + (void)delta_time; + fps_.calculate(SDL_GetTicks()); + Mouse::updateCursorVisibility(); +} + +// Calcula el tamaño de la ventana +void Screen::adjustWindowSize() { + window_width_ = Options::game.width + (Options::video.border.enabled ? Options::video.border.width * 2 : 0); + window_height_ = Options::game.height + (Options::video.border.enabled ? Options::video.border.height * 2 : 0); + + // Establece el nuevo tamaño + if (static_cast(Options::video.fullscreen) == 0) { + int old_width; + int old_height; + SDL_GetWindowSize(window_, &old_width, &old_height); + + int old_pos_x; + int old_pos_y; + SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y); + + const int NEW_POS_X = old_pos_x + ((old_width - (window_width_ * Options::window.zoom)) / 2); + const int NEW_POS_Y = old_pos_y + ((old_height - (window_height_ * Options::window.zoom)) / 2); + + SDL_SetWindowSize(window_, window_width_ * Options::window.zoom, window_height_ * Options::window.zoom); + SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0)); + } +} + +// Ajusta el tamaño lógico del renderizador +void Screen::adjustRenderLogicalSize() { + SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); +} + +// Establece el renderizador para las surfaces +void Screen::setRendererSurface(const std::shared_ptr& surface) { + (surface) ? renderer_surface_ = std::make_shared>(surface) : renderer_surface_ = std::make_shared>(game_surface_); +} + +// Cambia la paleta +void Screen::nextPalette() { + ++current_palette_; + if (current_palette_ == static_cast(palettes_.size())) { + current_palette_ = 0; + } + + setPalete(); +} + +// Cambia la paleta +void Screen::previousPalette() { + if (current_palette_ > 0) { + --current_palette_; + } else { + current_palette_ = static_cast(palettes_.size() - 1); + } + + setPalete(); +} + +// Establece la paleta +void Screen::setPalete() { // NOLINT(readability-convert-member-functions-to-static) + auto palette = readPalFile(palettes_.at(current_palette_)); + game_surface_->loadPalette(palette); + border_surface_->loadPalette(palette); + + Options::video.palette = palettes_.at(current_palette_); + + // Eliminar ".gif" + size_t pos = Options::video.palette.find(".pal"); + if (pos != std::string::npos) { + Options::video.palette.erase(pos, 4); + } + + // Convertir a mayúsculas + std::ranges::transform(Options::video.palette, Options::video.palette.begin(), ::toupper); +} + +// Extrae los nombres de las paletas +void Screen::processPaletteList() { + for (auto& palette : palettes_) { + palette = getFileName(palette); + } +} + +// Copia la surface a la textura +void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static) + if (Options::video.border.enabled) { + border_surface_->copyToTexture(renderer_, border_texture_); + game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_); + } else { + game_surface_->copyToTexture(renderer_, game_texture_); + } +} + +// Copia la textura al renderizador (o hace el present GPU) +void Screen::textureToRenderer() { + SDL_Texture* texture_to_render = Options::video.border.enabled ? border_texture_ : game_texture_; + + if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { + // ---- SDL3 GPU path: convertir Surface → ARGB → upload → PostFX/pass-through → present ---- + if (Options::video.border.enabled) { + // El border_surface_ solo tiene el color de borde; hay que componer encima el game_surface_ + const int BORDER_W = static_cast(border_surface_->getWidth()); + const int BORDER_H = static_cast(border_surface_->getHeight()); + pixel_buffer_.resize(static_cast(BORDER_W * BORDER_H)); + border_surface_->toARGBBuffer(pixel_buffer_.data()); + + // Compositar game_surface_ en la posición correcta dentro del buffer + const int GAME_W = static_cast(game_surface_->getWidth()); + const int GAME_H = static_cast(game_surface_->getHeight()); + const int OFF_X = static_cast(game_surface_dstrect_.x); + const int OFF_Y = static_cast(game_surface_dstrect_.y); + std::vector game_pixels(static_cast(GAME_W * GAME_H)); + game_surface_->toARGBBuffer(game_pixels.data()); + for (int y = 0; y < GAME_H; ++y) { + for (int x = 0; x < GAME_W; ++x) { + pixel_buffer_[static_cast(((OFF_Y + y) * BORDER_W) + (OFF_X + x))] = game_pixels[static_cast((y * GAME_W) + x)]; + } + } + shader_backend_->uploadPixels(pixel_buffer_.data(), BORDER_W, BORDER_H); + } else { + const int GAME_W = static_cast(game_surface_->getWidth()); + const int GAME_H = static_cast(game_surface_->getHeight()); + pixel_buffer_.resize(static_cast(GAME_W * GAME_H)); + game_surface_->toARGBBuffer(pixel_buffer_.data()); + shader_backend_->uploadPixels(pixel_buffer_.data(), GAME_W, GAME_H); + } + shader_backend_->render(); + } else { + // ---- SDL_Renderer path (fallback / no-shader) ---- + SDL_SetRenderTarget(renderer_, nullptr); + SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(renderer_); + SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr); + SDL_RenderPresent(renderer_); + } +} + +// Renderiza todos los overlays +void Screen::renderOverlays() { + renderNotifications(); +#ifdef _DEBUG + renderInfo(); +#endif +} + +// Localiza la paleta dentro del vector de paletas +auto Screen::findPalette(const std::string& name) -> size_t { // NOLINT(readability-convert-member-functions-to-static) + std::string upper_name = toUpper(name + ".pal"); + + for (size_t i = 0; i < palettes_.size(); ++i) { + if (toUpper(getFileName(palettes_[i])) == upper_name) { + return i; + } + } + return static_cast(0); +} + +// Muestra información por pantalla +void Screen::renderInfo() const { + if (show_fps_ && text_) { + auto color = static_cast(PaletteColor::YELLOW); + auto shadow = static_cast(PaletteColor::BLACK); + + // FPS con sombra + const std::string FPS_TEXT = std::to_string(fps_.last_value) + " FPS"; + const int FPS_X = Options::game.width - text_->length(FPS_TEXT) - 1; + + text_->writeColored(FPS_X + 1, 1, FPS_TEXT, shadow); + text_->writeColored(FPS_X, 0, FPS_TEXT, color); + } +} + +// Limpia la game_surface_ +void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); } + +// Establece el tamaño del borde +void Screen::setBorderWidth(int width) { Options::video.border.width = width; } + +// Establece el tamaño del borde +void Screen::setBorderHeight(int height) { Options::video.border.height = height; } + +// Establece si se ha de ver el borde en el modo ventana +void Screen::setBorderEnabled(bool value) { Options::video.border.enabled = value; } + +// Muestra la ventana +void Screen::show() { SDL_ShowWindow(window_); } + +// Oculta la ventana +void Screen::hide() { SDL_HideWindow(window_); } + +// Establece la visibilidad de las notificaciones +void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; } + +// Activa / desactiva el contador de FPS +void Screen::toggleFPS() { show_fps_ = !show_fps_; } + +// Alterna entre activar y desactivar el escalado entero +void Screen::toggleIntegerScale() { + Options::video.integer_scale = !Options::video.integer_scale; + SDL_SetRenderLogicalPresentation(renderer_, Options::game.width, Options::game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); + if (shader_backend_) { + shader_backend_->setScaleMode(Options::video.integer_scale); + } +} + +// Alterna entre activar y desactivar el V-Sync +void Screen::toggleVSync() { + Options::video.vertical_sync = !Options::video.vertical_sync; + SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED); + if (shader_backend_) { + shader_backend_->setVSync(Options::video.vertical_sync); + } +} + +// Getters +auto Screen::getRenderer() -> SDL_Renderer* { return renderer_; } +auto Screen::getRendererSurface() -> std::shared_ptr { return (*renderer_surface_); } +auto Screen::getBorderSurface() -> std::shared_ptr { return border_surface_; } + +auto loadData(const std::string& filepath) -> std::vector { + return loadFileBytes(filepath); +} + +// Activa/desactiva el supersampling global (Ctrl+F4) +void Screen::toggleSupersampling() { + Options::video.supersampling = !Options::video.supersampling; + if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { + applyCurrentPostFXPreset(); + } +} + +// Aplica los parámetros del preset actual al backend de shaders +void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static) + if (shader_backend_ && !Options::postfx_presets.empty()) { + const auto& p = Options::postfx_presets[static_cast(Options::current_postfx_preset)]; + // Supersampling es un toggle global (Options::video.supersampling), no por preset. + // setOversample primero: puede recrear texturas antes de que setPostFXParams + // decida si hornear scanlines en CPU o aplicarlas en GPU. + shader_backend_->setOversample(Options::video.supersampling ? 3 : 1); + Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .flicker = p.flicker}; + shader_backend_->setPostFXParams(params); + } +} + +// Inicializa los shaders +// El device GPU se crea siempre (independientemente de postfx) para evitar +// conflictos SDL_Renderer/SDL_GPU al hacer toggle F4 en Windows/Vulkan. +void Screen::initShaders() { + SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_; + + if (!shader_backend_) { + shader_backend_ = std::make_unique(); + } + shader_backend_->init(window_, tex, "", ""); + + // Propagar flags de vsync e integer scale al backend GPU + shader_backend_->setVSync(Options::video.vertical_sync); + shader_backend_->setScaleMode(Options::video.integer_scale); + + if (Options::video.postfx) { + applyCurrentPostFXPreset(); + } else { + // Pass-through: todos los efectos a 0, el shader solo copia la textura + shader_backend_->setPostFXParams(Rendering::PostFXParams{}); + } +} + +// Obtiene información sobre la pantalla +void Screen::getDisplayInfo() { // NOLINT(readability-convert-member-functions-to-static) + std::cout << "\n** VIDEO SYSTEM **\n"; + + int num_displays = 0; + SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); + if (displays != nullptr) { + for (int i = 0; i < num_displays; ++i) { + SDL_DisplayID instance_id = displays[i]; + const char* name = SDL_GetDisplayName(instance_id); + + std::cout << "Display " << instance_id << ": " << ((name != nullptr) ? name : "Unknown") << '\n'; + } + + const auto* dm = SDL_GetCurrentDisplayMode(displays[0]); + + // Guarda información del monitor en display_monitor_ + const char* first_display_name = SDL_GetDisplayName(displays[0]); + display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown"; + display_monitor_.width = static_cast(dm->w); + display_monitor_.height = static_cast(dm->h); + display_monitor_.refresh_rate = static_cast(dm->refresh_rate); + + // Calcula el máximo factor de zoom que se puede aplicar a la pantalla + Options::window.max_zoom = std::min(dm->w / Options::game.width, dm->h / Options::game.height); + Options::window.zoom = std::min(Options::window.zoom, Options::window.max_zoom); + + // Muestra información sobre el tamaño de la pantalla y de la ventana de juego + std::cout << "Current display mode: " << static_cast(dm->w) << "x" << static_cast(dm->h) << " @ " << static_cast(dm->refresh_rate) << "Hz\n"; + + std::cout << "Window resolution: " << static_cast(Options::game.width) << "x" << static_cast(Options::game.height) << " x" << Options::window.zoom << '\n'; + + Options::video.info = std::to_string(static_cast(dm->w)) + "x" + + std::to_string(static_cast(dm->h)) + " @ " + + std::to_string(static_cast(dm->refresh_rate)) + " Hz"; + + // Calcula el máximo factor de zoom que se puede aplicar a la pantalla + const int MAX_ZOOM = std::min(dm->w / Options::game.width, (dm->h - WINDOWS_DECORATIONS) / Options::game.height); + + // Normaliza los valores de zoom + Options::window.zoom = std::min(Options::window.zoom, MAX_ZOOM); + + SDL_free(displays); + } +} + +// Arranca SDL VIDEO y crea la ventana +auto Screen::initSDLVideo() -> bool { + // Inicializar SDL + if (!SDL_Init(SDL_INIT_VIDEO)) { + std::cerr << "FATAL: Failed to initialize SDL_VIDEO! SDL Error: " << SDL_GetError() << '\n'; + return false; + } + + // Obtener información de la pantalla + getDisplayInfo(); + + // Configurar hint para renderizado +#ifdef __APPLE__ + if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) { + std::cout << "WARNING: Failed to set Metal hint!\n"; + } +#endif + + // Crear ventana + const auto WINDOW_WIDTH = Options::video.border.enabled ? Options::game.width + (Options::video.border.width * 2) : Options::game.width; + const auto WINDOW_HEIGHT = Options::video.border.enabled ? Options::game.height + (Options::video.border.height * 2) : Options::game.height; + SDL_WindowFlags window_flags = 0; + if (Options::video.fullscreen) { + window_flags |= SDL_WINDOW_FULLSCREEN; + } + window_ = SDL_CreateWindow(Options::window.caption.c_str(), WINDOW_WIDTH * Options::window.zoom, WINDOW_HEIGHT * Options::window.zoom, window_flags); + + if (window_ == nullptr) { + std::cerr << "FATAL: Failed to create window! SDL Error: " << SDL_GetError() << '\n'; + SDL_Quit(); + return false; + } + + // Crear renderer + renderer_ = SDL_CreateRenderer(window_, nullptr); + if (renderer_ == nullptr) { + std::cerr << "FATAL: Failed to create renderer! SDL Error: " << SDL_GetError() << '\n'; + SDL_DestroyWindow(window_); + window_ = nullptr; + SDL_Quit(); + return false; + } + + // Configurar renderer + const int EXTRA_WIDTH = Options::video.border.enabled ? Options::video.border.width * 2 : 0; + const int EXTRA_HEIGHT = Options::video.border.enabled ? Options::video.border.height * 2 : 0; + SDL_SetRenderLogicalPresentation( + renderer_, + Options::game.width + EXTRA_WIDTH, + Options::game.height + EXTRA_HEIGHT, + Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); + SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF); + SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND); + SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED); + + std::cout << "Video system initialized successfully\n"; + return true; +} + +// Crea el objeto de texto +void Screen::createText() { + if (!Options::text_config.surface_path.empty() && !Options::text_config.fnt_path.empty()) { + auto surface = std::make_shared(Options::text_config.surface_path); + text_ = std::make_shared(surface, Options::text_config.fnt_path); + } +} \ No newline at end of file diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp new file mode 100644 index 0000000..bdafa6b --- /dev/null +++ b/source/core/rendering/screen.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include +#include // Para Uint32 + +#include // Para size_t +#include // Para shared_ptr, __shared_ptr_access +#include // Para string +#include // Para vector + +#include "utils/utils.hpp" // Para Color +class Surface; +class Text; +namespace Rendering { + class ShaderBackend; +} + +class Screen { + public: + // Tipos de filtro + enum class Filter : Uint32 { + NEAREST = 0, + LINEAR = 1, + }; + + // Singleton + static void init(); // Crea el singleton + static void destroy(); // Destruye el singleton + static auto get() -> Screen*; // Obtiene el singleton + + // Renderizado + void clearRenderer(Color color = {0x00, 0x00, 0x00}); // Limpia el renderer + void clearSurface(Uint8 index); // Limpia la game_surface_ + void start(); // Prepara para empezar a dibujar en la textura de juego + void render(); // Vuelca el contenido del renderizador en pantalla + void update(float delta_time); // Actualiza la lógica de la clase + + // Video y ventana + void setVideoMode(bool mode); // Establece el modo de video + void toggleVideoMode(); // Cambia entre pantalla completa y ventana + void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero + void toggleVSync(); // Alterna entre activar y desactivar el V-Sync + auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana + auto incWindowZoom() -> bool; // Aumenta el tamaño de la ventana + void show(); // Muestra la ventana + void hide(); // Oculta la ventana + + // Borde + void setBorderColor(Uint8 color); // Cambia el color del borde + static void setBorderWidth(int width); // Establece el ancho del borde + static void setBorderHeight(int height); // Establece el alto del borde + static void setBorderEnabled(bool value); // Establece si se ha de ver el borde + void toggleBorder(); // Cambia entre borde visible y no visible + + // Paletas y PostFX + void nextPalette(); // Cambia a la siguiente paleta + void previousPalette(); // Cambia a la paleta anterior + void setPalete(); // Establece la paleta actual + void togglePostFX(); // Cambia el estado del PostFX + void toggleSupersampling(); // Activa/desactiva el supersampling global + void reloadPostFX(); // Recarga el shader del preset actual sin toggle + + // Surfaces y notificaciones + void setRendererSurface(const std::shared_ptr& surface = nullptr); // Establece el renderizador para las surfaces + void setNotificationsEnabled(bool value); // Establece la visibilidad de las notificaciones + void toggleFPS(); // Activa o desactiva el contador de FPS + + // Getters + auto getRenderer() -> SDL_Renderer*; + auto getRendererSurface() -> std::shared_ptr; + auto getBorderSurface() -> std::shared_ptr; + [[nodiscard]] auto getText() const -> std::shared_ptr { return text_; } + [[nodiscard]] auto getGameSurfaceDstRect() const -> SDL_FRect { return game_surface_dstrect_; } + + private: + // Estructuras + struct DisplayMonitor { + std::string name; + int width{0}; + int height{0}; + int refresh_rate{0}; + }; + + struct FPS { + Uint32 ticks{0}; // Tiempo en milisegundos desde que se comenzó a contar + int frame_count{0}; // Número acumulado de frames en el intervalo + int last_value{0}; // Número de frames calculado en el último segundo + + void increment() { + frame_count++; + } + + auto calculate(Uint32 current_ticks) -> int { + if (current_ticks - ticks >= 1000) { + last_value = frame_count; + frame_count = 0; + ticks = current_ticks; + } + return last_value; + } + }; + + // Constantes + static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana + + // Singleton + static Screen* screen; + + // Métodos privados + void renderNotifications() const; // Dibuja las notificaciones + void adjustWindowSize(); // Calcula el tamaño de la ventana + void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador + void processPaletteList(); // Extrae los nombres de las paletas + void surfaceToTexture(); // Copia la surface a la textura + void textureToRenderer(); // Copia la textura al renderizador + void renderOverlays(); // Renderiza todos los overlays + auto findPalette(const std::string& name) -> size_t; // Localiza la paleta dentro del vector de paletas + void initShaders(); // Inicializa los shaders + void applyCurrentPostFXPreset(); // Aplica los parámetros del preset actual al backend + void renderInfo() const; // Muestra información por pantalla + void getDisplayInfo(); // Obtiene información sobre la pantalla + auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana + void createText(); // Crea el objeto de texto + + // Constructor y destructor + Screen(); + ~Screen(); + + // Objetos SDL + SDL_Window* window_{nullptr}; // Ventana de la aplicación + SDL_Renderer* renderer_{nullptr}; // Renderizador de la ventana + SDL_Texture* game_texture_{nullptr}; // Textura donde se dibuja el juego + SDL_Texture* border_texture_{nullptr}; // Textura donde se dibuja el borde del juego + + // Surfaces y renderizado + std::shared_ptr game_surface_; // Surface principal del juego + std::shared_ptr border_surface_; // Surface para el borde de la pantalla + std::shared_ptr> renderer_surface_; // Puntero a la Surface activa + std::unique_ptr shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan) + std::shared_ptr text_; // Objeto para escribir texto + + // Configuración de ventana y pantalla + int window_width_{0}; // Ancho de la pantalla o ventana + int window_height_{0}; // Alto de la pantalla o ventana + SDL_FRect game_surface_dstrect_; // Coordenadas donde se dibuja la textura del juego + + // Paletas y colores + Uint8 border_color_{0}; // Color del borde + std::vector palettes_; // Listado de ficheros de paleta disponibles + Uint8 current_palette_{0}; // Índice para el vector de paletas + + // Estado y configuración + bool notifications_enabled_{false}; // Indica si se muestran las notificaciones + FPS fps_; // Gestor de frames por segundo + DisplayMonitor display_monitor_; // Información de la pantalla + + // Shaders + std::string info_resolution_; // Texto con la información de la pantalla + std::vector pixel_buffer_; // Buffer intermedio para SDL3GPU path (surface → ARGB) + +#ifdef _DEBUG + bool show_fps_{true}; // Indica si ha de mostrar el contador de FPS +#else + bool show_fps_{false}; // Indica si ha de mostrar el contador de FPS +#endif +}; \ No newline at end of file diff --git a/source/core/rendering/sdl3gpu/postfx_frag_spv.h b/source/core/rendering/sdl3gpu/postfx_frag_spv.h new file mode 100644 index 0000000..dbd2688 --- /dev/null +++ b/source/core/rendering/sdl3gpu/postfx_frag_spv.h @@ -0,0 +1,10341 @@ +#pragma once +#include +#include +static const uint8_t kpostfx_frag_spv[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0xbc, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x08, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x97, + 0x01, + 0x00, + 0x00, + 0x10, + 0x00, + 0x03, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x01, + 0x00, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x47, + 0x4c, + 0x5f, + 0x47, + 0x4f, + 0x4f, + 0x47, + 0x4c, + 0x45, + 0x5f, + 0x63, + 0x70, + 0x70, + 0x5f, + 0x73, + 0x74, + 0x79, + 0x6c, + 0x65, + 0x5f, + 0x6c, + 0x69, + 0x6e, + 0x65, + 0x5f, + 0x64, + 0x69, + 0x72, + 0x65, + 0x63, + 0x74, + 0x69, + 0x76, + 0x65, + 0x00, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x47, + 0x4c, + 0x5f, + 0x47, + 0x4f, + 0x4f, + 0x47, + 0x4c, + 0x45, + 0x5f, + 0x69, + 0x6e, + 0x63, + 0x6c, + 0x75, + 0x64, + 0x65, + 0x5f, + 0x64, + 0x69, + 0x72, + 0x65, + 0x63, + 0x74, + 0x69, + 0x76, + 0x65, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x72, + 0x67, + 0x62, + 0x5f, + 0x74, + 0x6f, + 0x5f, + 0x79, + 0x63, + 0x63, + 0x28, + 0x76, + 0x66, + 0x33, + 0x3b, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x72, + 0x67, + 0x62, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x5f, + 0x74, + 0x6f, + 0x5f, + 0x72, + 0x67, + 0x62, + 0x28, + 0x76, + 0x66, + 0x33, + 0x3b, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x46, + 0x00, + 0x00, + 0x00, + 0x63, + 0x62, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x63, + 0x72, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x75, + 0x76, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x76, + 0x5f, + 0x75, + 0x76, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x50, + 0x6f, + 0x73, + 0x74, + 0x46, + 0x58, + 0x55, + 0x6e, + 0x69, + 0x66, + 0x6f, + 0x72, + 0x6d, + 0x73, + 0x00, + 0x00, + 0x06, + 0x00, + 0x08, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x76, + 0x69, + 0x67, + 0x6e, + 0x65, + 0x74, + 0x74, + 0x65, + 0x5f, + 0x73, + 0x74, + 0x72, + 0x65, + 0x6e, + 0x67, + 0x74, + 0x68, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x63, + 0x68, + 0x72, + 0x6f, + 0x6d, + 0x61, + 0x5f, + 0x73, + 0x74, + 0x72, + 0x65, + 0x6e, + 0x67, + 0x74, + 0x68, + 0x00, + 0x06, + 0x00, + 0x08, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x73, + 0x63, + 0x61, + 0x6e, + 0x6c, + 0x69, + 0x6e, + 0x65, + 0x5f, + 0x73, + 0x74, + 0x72, + 0x65, + 0x6e, + 0x67, + 0x74, + 0x68, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x73, + 0x63, + 0x72, + 0x65, + 0x65, + 0x6e, + 0x5f, + 0x68, + 0x65, + 0x69, + 0x67, + 0x68, + 0x74, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x73, + 0x6b, + 0x5f, + 0x73, + 0x74, + 0x72, + 0x65, + 0x6e, + 0x67, + 0x74, + 0x68, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x67, + 0x61, + 0x6d, + 0x6d, + 0x61, + 0x5f, + 0x73, + 0x74, + 0x72, + 0x65, + 0x6e, + 0x67, + 0x74, + 0x68, + 0x00, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x63, + 0x75, + 0x72, + 0x76, + 0x61, + 0x74, + 0x75, + 0x72, + 0x65, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x62, + 0x6c, + 0x65, + 0x65, + 0x64, + 0x69, + 0x6e, + 0x67, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x75, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x7f, + 0x00, + 0x00, + 0x00, + 0x72, + 0x73, + 0x71, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x64, + 0x69, + 0x73, + 0x74, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x62, + 0x61, + 0x72, + 0x72, + 0x65, + 0x6c, + 0x53, + 0x63, + 0x61, + 0x6c, + 0x65, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x75, + 0x74, + 0x5f, + 0x63, + 0x6f, + 0x6c, + 0x6f, + 0x72, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x62, + 0x61, + 0x73, + 0x65, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x73, + 0x63, + 0x65, + 0x6e, + 0x65, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x74, + 0x77, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x5f, + 0x6c, + 0x32, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xd4, + 0x00, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x5f, + 0x6c, + 0x31, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xdf, + 0x00, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xe2, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x5f, + 0x72, + 0x31, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xea, + 0x00, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xed, + 0x00, + 0x00, + 0x00, + 0x79, + 0x63, + 0x63, + 0x5f, + 0x72, + 0x32, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0xf5, + 0x00, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x63, + 0x6f, + 0x6c, + 0x6f, + 0x75, + 0x72, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x12, + 0x01, + 0x00, + 0x00, + 0x70, + 0x61, + 0x72, + 0x61, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x1b, + 0x01, + 0x00, + 0x00, + 0x63, + 0x61, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x37, + 0x01, + 0x00, + 0x00, + 0x6c, + 0x69, + 0x6e, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x42, + 0x01, + 0x00, + 0x00, + 0x74, + 0x65, + 0x78, + 0x48, + 0x65, + 0x69, + 0x67, + 0x68, + 0x74, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x73, + 0x63, + 0x61, + 0x6c, + 0x65, + 0x59, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x4e, + 0x01, + 0x00, + 0x00, + 0x73, + 0x63, + 0x72, + 0x65, + 0x65, + 0x6e, + 0x59, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x54, + 0x01, + 0x00, + 0x00, + 0x70, + 0x6f, + 0x73, + 0x49, + 0x6e, + 0x52, + 0x6f, + 0x77, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x58, + 0x01, + 0x00, + 0x00, + 0x73, + 0x63, + 0x61, + 0x6e, + 0x4c, + 0x69, + 0x6e, + 0x65, + 0x44, + 0x59, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x5d, + 0x01, + 0x00, + 0x00, + 0x73, + 0x63, + 0x61, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x74, + 0x01, + 0x00, + 0x00, + 0x65, + 0x6e, + 0x63, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x7f, + 0x01, + 0x00, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x83, + 0x01, + 0x00, + 0x00, + 0x76, + 0x69, + 0x67, + 0x6e, + 0x65, + 0x74, + 0x74, + 0x65, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x95, + 0x01, + 0x00, + 0x00, + 0x77, + 0x68, + 0x69, + 0x63, + 0x68, + 0x4d, + 0x61, + 0x73, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x97, + 0x01, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x46, + 0x72, + 0x61, + 0x67, + 0x43, + 0x6f, + 0x6f, + 0x72, + 0x64, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x73, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x97, + 0x01, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x04, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x87, + 0x16, + 0x99, + 0x3e, + 0x15, + 0x00, + 0x04, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x45, + 0x16, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0xd5, + 0x78, + 0xe9, + 0x3d, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x56, + 0x0e, + 0x2d, + 0xbe, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0xd5, + 0x78, + 0xa9, + 0x3e, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x87, + 0xd6, + 0x3e, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x54, + 0xe3, + 0xa5, + 0x3d, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x00, + 0x00, + 0xbc, + 0x74, + 0xb3, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x54, + 0x00, + 0x00, + 0x00, + 0xc5, + 0x20, + 0xb0, + 0x3e, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x58, + 0x00, + 0x00, + 0x00, + 0xb4, + 0xc8, + 0x36, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5d, + 0x00, + 0x00, + 0x00, + 0xe5, + 0xd0, + 0xe2, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x17, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x6c, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6c, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x0a, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x70, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x70, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x73, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x02, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x84, + 0x00, + 0x00, + 0x00, + 0xcd, + 0xcc, + 0x4c, + 0x3d, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x00, + 0x00, + 0xcd, + 0xcc, + 0xcc, + 0x3d, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x86, + 0x00, + 0x00, + 0x00, + 0x84, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8c, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x85, + 0x6b, + 0x3e, + 0x17, + 0x00, + 0x04, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0xa9, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0xa9, + 0x00, + 0x00, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x07, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xab, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x09, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x03, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0xb3, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0xb3, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0xb9, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0xc3, + 0x00, + 0x00, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x09, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x41, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x01, + 0x00, + 0x00, + 0x0a, + 0xd7, + 0xa3, + 0x3b, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x31, + 0x01, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x39, + 0x01, + 0x00, + 0x00, + 0x9a, + 0x99, + 0x19, + 0x40, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x01, + 0x00, + 0x00, + 0x39, + 0x01, + 0x00, + 0x00, + 0x39, + 0x01, + 0x00, + 0x00, + 0x39, + 0x01, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x49, + 0x01, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x61, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x40, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x64, + 0x01, + 0x00, + 0x00, + 0x8f, + 0xc2, + 0xf5, + 0x3d, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x66, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x60, + 0x40, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x69, + 0x01, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x76, + 0x01, + 0x00, + 0x00, + 0x2f, + 0xba, + 0xe8, + 0x3e, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x77, + 0x01, + 0x00, + 0x00, + 0x76, + 0x01, + 0x00, + 0x00, + 0x76, + 0x01, + 0x00, + 0x00, + 0x76, + 0x01, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x01, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x96, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x96, + 0x01, + 0x00, + 0x00, + 0x97, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x98, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9b, + 0x01, + 0x00, + 0x00, + 0xaa, + 0xaa, + 0xaa, + 0x3e, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9f, + 0x01, + 0x00, + 0x00, + 0xcd, + 0xcc, + 0x4c, + 0x3f, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xa0, + 0x01, + 0x00, + 0x00, + 0x9f, + 0x01, + 0x00, + 0x00, + 0x9f, + 0x01, + 0x00, + 0x00, + 0x9f, + 0x01, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa8, + 0x01, + 0x00, + 0x00, + 0xaa, + 0xaa, + 0x2a, + 0x3f, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x7f, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xd4, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xdf, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xe2, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xea, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xed, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x12, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x37, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x42, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x4e, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x54, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x58, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x5d, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x74, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x7f, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x83, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x95, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x75, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x73, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0x75, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x78, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0x7a, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x78, + 0x00, + 0x00, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0x7a, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x7c, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x7e, + 0x00, + 0x00, + 0x00, + 0x7c, + 0x00, + 0x00, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x7e, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x94, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x82, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x7f, + 0x00, + 0x00, + 0x00, + 0x82, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x87, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x73, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x00, + 0x00, + 0x87, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x89, + 0x00, + 0x00, + 0x00, + 0x86, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x89, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x8d, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x00, + 0x00, + 0x8d, + 0x00, + 0x00, + 0x00, + 0x8c, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x90, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x7f, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x93, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x94, + 0x00, + 0x00, + 0x00, + 0x90, + 0x00, + 0x00, + 0x00, + 0x93, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x95, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x96, + 0x00, + 0x00, + 0x00, + 0x95, + 0x00, + 0x00, + 0x00, + 0x94, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x96, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x97, + 0x00, + 0x00, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x98, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x99, + 0x00, + 0x00, + 0x00, + 0x98, + 0x00, + 0x00, + 0x00, + 0x97, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x99, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x9a, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9b, + 0x00, + 0x00, + 0x00, + 0x9a, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x9b, + 0x00, + 0x00, + 0x00, + 0xbe, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x9d, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0xa8, + 0x00, + 0x04, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x9e, + 0x00, + 0x00, + 0x00, + 0x9d, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x9e, + 0x00, + 0x00, + 0x00, + 0x9f, + 0x00, + 0x00, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x9f, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xa1, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x00, + 0x00, + 0x00, + 0xa1, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa3, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x00, + 0x00, + 0x00, + 0xbe, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xa4, + 0x00, + 0x00, + 0x00, + 0xa3, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xa5, + 0x00, + 0x00, + 0x00, + 0x9d, + 0x00, + 0x00, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0xa4, + 0x00, + 0x00, + 0x00, + 0x9f, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0xa5, + 0x00, + 0x00, + 0x00, + 0xa6, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa6, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0xab, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xad, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xae, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x00, + 0x00, + 0x00, + 0xad, + 0x00, + 0x00, + 0x00, + 0xae, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x7a, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x7a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xb5, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xb6, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xb7, + 0x00, + 0x00, + 0x00, + 0xb5, + 0x00, + 0x00, + 0x00, + 0xb6, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x00, + 0x00, + 0xb7, + 0x00, + 0x00, + 0x00, + 0xb7, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0xb9, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xbb, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0xbb, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0xbe, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0xbd, + 0x00, + 0x00, + 0x00, + 0x19, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xbd, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x04, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x00, + 0x00, + 0x00, + 0x67, + 0x00, + 0x05, + 0x00, + 0xc3, + 0x00, + 0x00, + 0x00, + 0xc4, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x00, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0xc5, + 0x00, + 0x00, + 0x00, + 0xc4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xc6, + 0x00, + 0x00, + 0x00, + 0xc5, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0xc6, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xca, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0xca, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xcc, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xcf, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xd0, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0xcf, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xd1, + 0x00, + 0x00, + 0x00, + 0xd0, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xd2, + 0x00, + 0x00, + 0x00, + 0xcd, + 0x00, + 0x00, + 0x00, + 0xd1, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xd3, + 0x00, + 0x00, + 0x00, + 0xcc, + 0x00, + 0x00, + 0x00, + 0xd2, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xd5, + 0x00, + 0x00, + 0x00, + 0xd3, + 0x00, + 0x00, + 0x00, + 0xd3, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xd4, + 0x00, + 0x00, + 0x00, + 0xd5, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xd6, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0xd4, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0xd6, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xd8, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xd9, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xda, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xdb, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0xda, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xdc, + 0x00, + 0x00, + 0x00, + 0xdb, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xdd, + 0x00, + 0x00, + 0x00, + 0xd9, + 0x00, + 0x00, + 0x00, + 0xdc, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xde, + 0x00, + 0x00, + 0x00, + 0xd8, + 0x00, + 0x00, + 0x00, + 0xdd, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xe0, + 0x00, + 0x00, + 0x00, + 0xde, + 0x00, + 0x00, + 0x00, + 0xde, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xdf, + 0x00, + 0x00, + 0x00, + 0xe0, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xe1, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0xdf, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0xe1, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xe3, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xe4, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xe5, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xe6, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0xe5, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xe7, + 0x00, + 0x00, + 0x00, + 0xe6, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xe8, + 0x00, + 0x00, + 0x00, + 0xe4, + 0x00, + 0x00, + 0x00, + 0xe7, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xe9, + 0x00, + 0x00, + 0x00, + 0xe3, + 0x00, + 0x00, + 0x00, + 0xe8, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xeb, + 0x00, + 0x00, + 0x00, + 0xe9, + 0x00, + 0x00, + 0x00, + 0xe9, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xea, + 0x00, + 0x00, + 0x00, + 0xeb, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xec, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0xea, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xe2, + 0x00, + 0x00, + 0x00, + 0xec, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0xee, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xef, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xf1, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xf2, + 0x00, + 0x00, + 0x00, + 0xf1, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xf3, + 0x00, + 0x00, + 0x00, + 0xef, + 0x00, + 0x00, + 0x00, + 0xf2, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xf4, + 0x00, + 0x00, + 0x00, + 0xee, + 0x00, + 0x00, + 0x00, + 0xf3, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xf6, + 0x00, + 0x00, + 0x00, + 0xf4, + 0x00, + 0x00, + 0x00, + 0xf4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xf5, + 0x00, + 0x00, + 0x00, + 0xf6, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xed, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x07, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x00, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x07, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xfb, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xfc, + 0x00, + 0x00, + 0x00, + 0xfb, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x00, + 0x00, + 0xfc, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x07, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0xff, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0xff, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x01, + 0x01, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x02, + 0x01, + 0x00, + 0x00, + 0xe2, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x07, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x03, + 0x01, + 0x00, + 0x00, + 0x02, + 0x01, + 0x00, + 0x00, + 0x02, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x04, + 0x01, + 0x00, + 0x00, + 0x03, + 0x01, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x05, + 0x01, + 0x00, + 0x00, + 0x01, + 0x01, + 0x00, + 0x00, + 0x04, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x01, + 0x00, + 0x00, + 0xed, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x07, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x07, + 0x01, + 0x00, + 0x00, + 0x06, + 0x01, + 0x00, + 0x00, + 0x06, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x08, + 0x01, + 0x00, + 0x00, + 0x05, + 0x01, + 0x00, + 0x00, + 0x07, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x01, + 0x00, + 0x00, + 0x09, + 0x01, + 0x00, + 0x00, + 0x09, + 0x01, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x01, + 0x00, + 0x00, + 0x08, + 0x01, + 0x00, + 0x00, + 0x0a, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x01, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x01, + 0x00, + 0x00, + 0x0b, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x0c, + 0x01, + 0x00, + 0x00, + 0x0d, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x01, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x01, + 0x00, + 0x00, + 0x0b, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x0e, + 0x01, + 0x00, + 0x00, + 0x0f, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x11, + 0x01, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x13, + 0x01, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x12, + 0x01, + 0x00, + 0x00, + 0x13, + 0x01, + 0x00, + 0x00, + 0x39, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x14, + 0x01, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x12, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x15, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0xb9, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x16, + 0x01, + 0x00, + 0x00, + 0x15, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x17, + 0x01, + 0x00, + 0x00, + 0x16, + 0x01, + 0x00, + 0x00, + 0x16, + 0x01, + 0x00, + 0x00, + 0x16, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x18, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x11, + 0x01, + 0x00, + 0x00, + 0x14, + 0x01, + 0x00, + 0x00, + 0x17, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x18, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xbe, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x19, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x01, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x1a, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xbe, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xbe, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x01, + 0x00, + 0x00, + 0x1d, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x01, + 0x00, + 0x00, + 0x1e, + 0x01, + 0x00, + 0x00, + 0x1f, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x1b, + 0x01, + 0x00, + 0x00, + 0x20, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0x21, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x22, + 0x01, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x01, + 0x00, + 0x00, + 0x1b, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x24, + 0x01, + 0x00, + 0x00, + 0x23, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x25, + 0x01, + 0x00, + 0x00, + 0x22, + 0x01, + 0x00, + 0x00, + 0x24, + 0x01, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0x26, + 0x01, + 0x00, + 0x00, + 0x21, + 0x01, + 0x00, + 0x00, + 0x25, + 0x01, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x27, + 0x01, + 0x00, + 0x00, + 0x26, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x28, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x28, + 0x01, + 0x00, + 0x00, + 0x27, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0x29, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x01, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x01, + 0x00, + 0x00, + 0x1b, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x01, + 0x00, + 0x00, + 0x2b, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x01, + 0x00, + 0x00, + 0x2a, + 0x01, + 0x00, + 0x00, + 0x2c, + 0x01, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x01, + 0x00, + 0x00, + 0x29, + 0x01, + 0x00, + 0x00, + 0x2d, + 0x01, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x01, + 0x00, + 0x00, + 0x2e, + 0x01, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x30, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x30, + 0x01, + 0x00, + 0x00, + 0x2f, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x32, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x31, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x33, + 0x01, + 0x00, + 0x00, + 0x32, + 0x01, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x34, + 0x01, + 0x00, + 0x00, + 0x33, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0x36, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x34, + 0x01, + 0x00, + 0x00, + 0x35, + 0x01, + 0x00, + 0x00, + 0x36, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x35, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x38, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x38, + 0x01, + 0x00, + 0x00, + 0x3a, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x37, + 0x01, + 0x00, + 0x00, + 0x3b, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x01, + 0x00, + 0x00, + 0x37, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x31, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x40, + 0x01, + 0x00, + 0x00, + 0x3f, + 0x01, + 0x00, + 0x00, + 0x3f, + 0x01, + 0x00, + 0x00, + 0x3f, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x41, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x01, + 0x00, + 0x00, + 0x40, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x41, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x36, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x36, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0xb2, + 0x00, + 0x00, + 0x00, + 0x43, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x04, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0x44, + 0x01, + 0x00, + 0x00, + 0x43, + 0x01, + 0x00, + 0x00, + 0x67, + 0x00, + 0x05, + 0x00, + 0xc3, + 0x00, + 0x00, + 0x00, + 0x45, + 0x01, + 0x00, + 0x00, + 0x44, + 0x01, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x72, + 0x00, + 0x00, + 0x00, + 0x46, + 0x01, + 0x00, + 0x00, + 0x45, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x47, + 0x01, + 0x00, + 0x00, + 0x46, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x42, + 0x01, + 0x00, + 0x00, + 0x47, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x49, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4b, + 0x01, + 0x00, + 0x00, + 0x4a, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4c, + 0x01, + 0x00, + 0x00, + 0x42, + 0x01, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4d, + 0x01, + 0x00, + 0x00, + 0x4b, + 0x01, + 0x00, + 0x00, + 0x4c, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x4d, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x01, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x50, + 0x01, + 0x00, + 0x00, + 0x4f, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x51, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x49, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x52, + 0x01, + 0x00, + 0x00, + 0x51, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x53, + 0x01, + 0x00, + 0x00, + 0x50, + 0x01, + 0x00, + 0x00, + 0x52, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x4e, + 0x01, + 0x00, + 0x00, + 0x53, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x55, + 0x01, + 0x00, + 0x00, + 0x4e, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x56, + 0x01, + 0x00, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x8d, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x57, + 0x01, + 0x00, + 0x00, + 0x55, + 0x01, + 0x00, + 0x00, + 0x56, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x54, + 0x01, + 0x00, + 0x00, + 0x57, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x59, + 0x01, + 0x00, + 0x00, + 0x54, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5a, + 0x01, + 0x00, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5b, + 0x01, + 0x00, + 0x00, + 0x59, + 0x01, + 0x00, + 0x00, + 0x5a, + 0x01, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5c, + 0x01, + 0x00, + 0x00, + 0x5b, + 0x01, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x58, + 0x01, + 0x00, + 0x00, + 0x5c, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5e, + 0x01, + 0x00, + 0x00, + 0x58, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5f, + 0x01, + 0x00, + 0x00, + 0x58, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x60, + 0x01, + 0x00, + 0x00, + 0x5e, + 0x01, + 0x00, + 0x00, + 0x5f, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x62, + 0x01, + 0x00, + 0x00, + 0x60, + 0x01, + 0x00, + 0x00, + 0x61, + 0x01, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x63, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x62, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x65, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x63, + 0x01, + 0x00, + 0x00, + 0x64, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x67, + 0x01, + 0x00, + 0x00, + 0x65, + 0x01, + 0x00, + 0x00, + 0x66, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x5d, + 0x01, + 0x00, + 0x00, + 0x67, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x68, + 0x01, + 0x00, + 0x00, + 0x5d, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x6a, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x69, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x6b, + 0x01, + 0x00, + 0x00, + 0x6a, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x6c, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x68, + 0x01, + 0x00, + 0x00, + 0x6b, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x6e, + 0x01, + 0x00, + 0x00, + 0x6d, + 0x01, + 0x00, + 0x00, + 0x6c, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x6e, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x31, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x70, + 0x01, + 0x00, + 0x00, + 0x6f, + 0x01, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x71, + 0x01, + 0x00, + 0x00, + 0x70, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0x73, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x71, + 0x01, + 0x00, + 0x00, + 0x72, + 0x01, + 0x00, + 0x00, + 0x73, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x72, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x75, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x78, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x75, + 0x01, + 0x00, + 0x00, + 0x77, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x74, + 0x01, + 0x00, + 0x00, + 0x78, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x79, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x7a, + 0x01, + 0x00, + 0x00, + 0x74, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x31, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x7c, + 0x01, + 0x00, + 0x00, + 0x7b, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x7d, + 0x01, + 0x00, + 0x00, + 0x7c, + 0x01, + 0x00, + 0x00, + 0x7c, + 0x01, + 0x00, + 0x00, + 0x7c, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x7e, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x79, + 0x01, + 0x00, + 0x00, + 0x7a, + 0x01, + 0x00, + 0x00, + 0x7d, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x7e, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x73, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x73, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x80, + 0x01, + 0x00, + 0x00, + 0x6b, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x81, + 0x01, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x82, + 0x01, + 0x00, + 0x00, + 0x80, + 0x01, + 0x00, + 0x00, + 0x81, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x7f, + 0x01, + 0x00, + 0x00, + 0x82, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x84, + 0x01, + 0x00, + 0x00, + 0x7f, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x85, + 0x01, + 0x00, + 0x00, + 0x7f, + 0x01, + 0x00, + 0x00, + 0x94, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x86, + 0x01, + 0x00, + 0x00, + 0x84, + 0x01, + 0x00, + 0x00, + 0x85, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x87, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x88, + 0x01, + 0x00, + 0x00, + 0x87, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x89, + 0x01, + 0x00, + 0x00, + 0x86, + 0x01, + 0x00, + 0x00, + 0x88, + 0x01, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8a, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x89, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x83, + 0x01, + 0x00, + 0x00, + 0x8a, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x01, + 0x00, + 0x00, + 0x83, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8c, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x8d, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x01, + 0x00, + 0x00, + 0x8d, + 0x01, + 0x00, + 0x00, + 0x8c, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x8e, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x90, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x91, + 0x01, + 0x00, + 0x00, + 0x90, + 0x01, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x92, + 0x01, + 0x00, + 0x00, + 0x91, + 0x01, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0x94, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x92, + 0x01, + 0x00, + 0x00, + 0x93, + 0x01, + 0x00, + 0x00, + 0x94, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x93, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x98, + 0x01, + 0x00, + 0x00, + 0x99, + 0x01, + 0x00, + 0x00, + 0x97, + 0x01, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9a, + 0x01, + 0x00, + 0x00, + 0x99, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x01, + 0x00, + 0x00, + 0x9a, + 0x01, + 0x00, + 0x00, + 0x9b, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9d, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x95, + 0x01, + 0x00, + 0x00, + 0x9d, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0xa0, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa1, + 0x01, + 0x00, + 0x00, + 0x95, + 0x01, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x01, + 0x00, + 0x00, + 0xa1, + 0x01, + 0x00, + 0x00, + 0x9b, + 0x01, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0xa4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0xa2, + 0x01, + 0x00, + 0x00, + 0xa3, + 0x01, + 0x00, + 0x00, + 0xa6, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa3, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xa5, + 0x01, + 0x00, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xa5, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xa4, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa6, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x01, + 0x00, + 0x00, + 0x95, + 0x01, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x05, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xa9, + 0x01, + 0x00, + 0x00, + 0xa7, + 0x01, + 0x00, + 0x00, + 0xa8, + 0x01, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0xab, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0xa9, + 0x01, + 0x00, + 0x00, + 0xaa, + 0x01, + 0x00, + 0x00, + 0xad, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xaa, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xac, + 0x01, + 0x00, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xac, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xab, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xad, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xae, + 0x01, + 0x00, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xae, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xab, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xab, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0xa4, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0xa4, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb1, + 0x01, + 0x00, + 0x00, + 0x9e, + 0x01, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb2, + 0x01, + 0x00, + 0x00, + 0xb0, + 0x01, + 0x00, + 0x00, + 0xb1, + 0x01, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0xb3, + 0x01, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x01, + 0x00, + 0x00, + 0xb3, + 0x01, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb5, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x01, + 0x00, + 0x00, + 0xb4, + 0x01, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb6, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x01, + 0x00, + 0x00, + 0xb2, + 0x01, + 0x00, + 0x00, + 0xb5, + 0x01, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0xb6, + 0x01, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x94, + 0x01, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x94, + 0x01, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb7, + 0x01, + 0x00, + 0x00, + 0x10, + 0x01, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xb8, + 0x01, + 0x00, + 0x00, + 0xb7, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xb9, + 0x01, + 0x00, + 0x00, + 0xb7, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xba, + 0x01, + 0x00, + 0x00, + 0xb7, + 0x01, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0xa8, + 0x00, + 0x00, + 0x00, + 0xbb, + 0x01, + 0x00, + 0x00, + 0xb8, + 0x01, + 0x00, + 0x00, + 0xb9, + 0x01, + 0x00, + 0x00, + 0xba, + 0x01, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0xbb, + 0x01, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x37, + 0x00, + 0x03, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x34, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x34, + 0x00, + 0x00, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0x02, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x37, + 0x00, + 0x03, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x46, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x44, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x45, + 0x00, + 0x00, + 0x00, + 0x44, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x45, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x46, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x4b, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4c, + 0x00, + 0x00, + 0x00, + 0x4b, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4d, + 0x00, + 0x00, + 0x00, + 0x4c, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x4d, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4e, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x52, + 0x00, + 0x00, + 0x00, + 0x4e, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x53, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x55, + 0x00, + 0x00, + 0x00, + 0x46, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x56, + 0x00, + 0x00, + 0x00, + 0x54, + 0x00, + 0x00, + 0x00, + 0x55, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x00, + 0x00, + 0x53, + 0x00, + 0x00, + 0x00, + 0x56, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x59, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5a, + 0x00, + 0x00, + 0x00, + 0x58, + 0x00, + 0x00, + 0x00, + 0x59, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5b, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x00, + 0x00, + 0x5a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5c, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5e, + 0x00, + 0x00, + 0x00, + 0x46, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5f, + 0x00, + 0x00, + 0x00, + 0x5d, + 0x00, + 0x00, + 0x00, + 0x5e, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x60, + 0x00, + 0x00, + 0x00, + 0x5c, + 0x00, + 0x00, + 0x00, + 0x5f, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x61, + 0x00, + 0x00, + 0x00, + 0x52, + 0x00, + 0x00, + 0x00, + 0x5b, + 0x00, + 0x00, + 0x00, + 0x60, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x62, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x65, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x63, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x66, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x61, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0x65, + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0x02, + 0x00, + 0x66, + 0x00, + 0x00, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00}; +static const size_t kpostfx_frag_spv_size = 10336; diff --git a/source/core/rendering/sdl3gpu/postfx_vert_spv.h b/source/core/rendering/sdl3gpu/postfx_vert_spv.h new file mode 100644 index 0000000..1be581e --- /dev/null +++ b/source/core/rendering/sdl3gpu/postfx_vert_spv.h @@ -0,0 +1,1449 @@ +#pragma once +#include +#include +static const uint8_t kpostfx_vert_spv[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x01, + 0x00, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x47, + 0x4c, + 0x5f, + 0x47, + 0x4f, + 0x4f, + 0x47, + 0x4c, + 0x45, + 0x5f, + 0x63, + 0x70, + 0x70, + 0x5f, + 0x73, + 0x74, + 0x79, + 0x6c, + 0x65, + 0x5f, + 0x6c, + 0x69, + 0x6e, + 0x65, + 0x5f, + 0x64, + 0x69, + 0x72, + 0x65, + 0x63, + 0x74, + 0x69, + 0x76, + 0x65, + 0x00, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x47, + 0x4c, + 0x5f, + 0x47, + 0x4f, + 0x4f, + 0x47, + 0x4c, + 0x45, + 0x5f, + 0x69, + 0x6e, + 0x63, + 0x6c, + 0x75, + 0x64, + 0x65, + 0x5f, + 0x64, + 0x69, + 0x72, + 0x65, + 0x63, + 0x74, + 0x69, + 0x76, + 0x65, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x50, + 0x65, + 0x72, + 0x56, + 0x65, + 0x72, + 0x74, + 0x65, + 0x78, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x50, + 0x6f, + 0x73, + 0x69, + 0x74, + 0x69, + 0x6f, + 0x6e, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x50, + 0x6f, + 0x69, + 0x6e, + 0x74, + 0x53, + 0x69, + 0x7a, + 0x65, + 0x00, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x43, + 0x6c, + 0x69, + 0x70, + 0x44, + 0x69, + 0x73, + 0x74, + 0x61, + 0x6e, + 0x63, + 0x65, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x43, + 0x75, + 0x6c, + 0x6c, + 0x44, + 0x69, + 0x73, + 0x74, + 0x61, + 0x6e, + 0x63, + 0x65, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x67, + 0x6c, + 0x5f, + 0x56, + 0x65, + 0x72, + 0x74, + 0x65, + 0x78, + 0x49, + 0x6e, + 0x64, + 0x65, + 0x78, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x69, + 0x6e, + 0x64, + 0x65, + 0x78, + 0x61, + 0x62, + 0x6c, + 0x65, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x04, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x76, + 0x5f, + 0x75, + 0x76, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x05, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x69, + 0x6e, + 0x64, + 0x65, + 0x78, + 0x61, + 0x62, + 0x6c, + 0x65, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x06, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x04, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0xbf, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x40, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x20, + 0x00, + 0x04, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00}; +static const size_t kpostfx_vert_spv_size = 1444; diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp new file mode 100644 index 0000000..5d29ecd --- /dev/null +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -0,0 +1,720 @@ +#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" + +#include + +#include // std::min, std::max, std::floor +#include // std::floor +#include // memcpy, strlen + +#ifndef __APPLE__ +#include "core/rendering/sdl3gpu/postfx_frag_spv.h" +#include "core/rendering/sdl3gpu/postfx_vert_spv.h" +#endif + +#ifdef __APPLE__ +// ============================================================================ +// MSL shaders (Metal Shading Language) — macOS +// ============================================================================ + +// NOLINTBEGIN(readability-identifier-naming) +static const char* POSTFX_VERT_MSL = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +vertex PostVOut postfx_vs(uint vid [[vertex_id]]) { + const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} }; + const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} }; + PostVOut out; + out.pos = float4(positions[vid], 0.0, 1.0); + out.uv = uvs[vid]; + return out; +} +)"; + +static const char* POSTFX_FRAG_MSL = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +struct PostFXUniforms { + float vignette_strength; + float chroma_strength; + float scanline_strength; + float screen_height; + float mask_strength; + float gamma_strength; + float curvature; + float bleeding; + float pixel_scale; + float time; + float oversample; // 1.0 = sin SS, 3.0 = 3× supersampling + float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz +}; + +// YCbCr helpers for NTSC bleeding +static float3 rgb_to_ycc(float3 rgb) { + return float3( + 0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b, + -0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f, + 0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f + ); +} +static float3 ycc_to_rgb(float3 ycc) { + float y = ycc.x; + float cb = ycc.y - 0.5f; + float cr = ycc.z - 0.5f; + return clamp(float3( + y + 1.402f*cr, + y - 0.344f*cb - 0.714f*cr, + y + 1.772f*cb + ), 0.0f, 1.0f); +} + +fragment float4 postfx_fs(PostVOut in [[stage_in]], + texture2d scene [[texture(0)]], + sampler samp [[sampler(0)]], + constant PostFXUniforms& u [[buffer(0)]]) { + float2 uv = in.uv; + + // Curvatura barrel CRT + if (u.curvature > 0.0f) { + float2 c = uv - 0.5f; + float rsq = dot(c, c); + float2 dist = float2(0.05f, 0.1f) * u.curvature; + float2 barrelScale = 1.0f - 0.23f * dist; + c += c * (dist * rsq); + c *= barrelScale; + if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) { + return float4(0.0f, 0.0f, 0.0f, 1.0f); + } + uv = c + 0.5f; + } + + // Muestra base + float3 base = scene.sample(samp, uv).rgb; + + // Sangrado NTSC — difuminado horizontal de crominancia. + // step = 1 pixel de juego en espacio UV (corrige SS: scene.get_width() = game_w * oversample). + float3 colour; + if (u.bleeding > 0.0f) { + float tw = float(scene.get_width()); + float step = u.oversample / tw; // 1 pixel lógico en UV + float3 ycc = rgb_to_ycc(base); + float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb); + float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb); + float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb); + float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb); + ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f; + colour = mix(base, ycc_to_rgb(ycc), u.bleeding); + } else { + colour = base; + } + + // Aberración cromática (drift animado con time para efecto NTSC real) + float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f)); + colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r; + colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b; + + // Corrección gamma (linealizar antes de scanlines, codificar después) + if (u.gamma_strength > 0.0f) { + float3 lin = pow(colour, float3(2.4f)); + colour = mix(colour, lin, u.gamma_strength); + } + + // Scanlines — 1 pixel físico oscuro por fila lógica. + // Usa uv.y (independiente del offset de letterbox) con pixel_scale para + // calcular la posición dentro de la fila en coordenadas físicas. + // 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright. + // bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original). + if (u.scanline_strength > 0.0f) { + float ps = max(1.0f, round(u.pixel_scale)); + float frac_in_row = fract(uv.y * u.screen_height); + float row_pos = floor(frac_in_row * ps); + float is_dark = step(ps - 1.0f, row_pos); + float scan = mix(3.5f, 0.42f, is_dark); + colour *= mix(1.0f, scan, u.scanline_strength); + } + + if (u.gamma_strength > 0.0f) { + float3 enc = pow(colour, float3(1.0f/2.2f)); + colour = mix(colour, enc, u.gamma_strength); + } + + // Viñeta + float2 d = uv - 0.5f; + float vignette = 1.0f - dot(d, d) * u.vignette_strength; + colour *= clamp(vignette, 0.0f, 1.0f); + + // Máscara de fósforo RGB — después de scanlines (orden original): + // filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible. + if (u.mask_strength > 0.0f) { + float whichMask = fract(in.pos.x * 0.3333333f); + float3 mask = float3(0.80f); + if (whichMask < 0.3333333f) mask.x = 1.0f; + else if (whichMask < 0.6666667f) mask.y = 1.0f; + else mask.z = 1.0f; + colour = mix(colour, colour * mask, u.mask_strength); + } + + // Parpadeo de fósforo CRT (~50 Hz) + if (u.flicker > 0.0f) { + float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f; + colour *= 1.0f - u.flicker * 0.04f * flicker_wave; + } + + return float4(colour, 1.0f); +} +)"; +// NOLINTEND(readability-identifier-naming) + +#endif // __APPLE__ + +namespace Rendering { + + // --------------------------------------------------------------------------- + // Destructor + // --------------------------------------------------------------------------- + SDL3GPUShader::~SDL3GPUShader() { + destroy(); + } + + // --------------------------------------------------------------------------- + // init + // --------------------------------------------------------------------------- + auto SDL3GPUShader::init(SDL_Window* window, + SDL_Texture* texture, + const std::string& /*vertex_source*/, + const std::string& /*fragment_source*/) -> bool { + // Si ya estaba inicializado (p.ej. al cambiar borde), liberar recursos + // de textura/pipeline pero mantener el device vivo para evitar conflictos + // con SDL_Renderer en Windows/Vulkan. + if (is_initialized_) { + cleanup(); + } + + window_ = window; + + // Dimensions from the SDL_Texture placeholder + float fw = 0.0F; + float fh = 0.0F; + SDL_GetTextureSize(texture, &fw, &fh); + game_width_ = static_cast(fw); + game_height_ = static_cast(fh); + tex_width_ = game_width_ * oversample_; + tex_height_ = game_height_ * oversample_; + uniforms_.screen_height = static_cast(tex_height_); // Altura de la textura GPU + uniforms_.oversample = static_cast(oversample_); + + // ---------------------------------------------------------------- + // 1. Create GPU device (solo si no existe ya) + // ---------------------------------------------------------------- + if (device_ == nullptr) { +#ifdef __APPLE__ + const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB; +#else + const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_SPIRV; +#endif + device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr); + if (device_ == nullptr) { + SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError()); + return false; + } + SDL_Log("SDL3GPUShader: driver = %s", SDL_GetGPUDeviceDriver(device_)); + + // ---------------------------------------------------------------- + // 2. Claim window (una sola vez — no liberar hasta destroy()) + // ---------------------------------------------------------------- + if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { + SDL_Log("SDL3GPUShader: SDL_ClaimWindowForGPUDevice failed: %s", SDL_GetError()); + SDL_DestroyGPUDevice(device_); + device_ = nullptr; + return false; + } + SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE); + } + + // ---------------------------------------------------------------- + // 3. Create scene texture (upload target + sampler source) + // Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE + // ---------------------------------------------------------------- + SDL_GPUTextureCreateInfo tex_info = {}; + tex_info.type = SDL_GPU_TEXTURETYPE_2D; + tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; + tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; + tex_info.width = static_cast(tex_width_); + tex_info.height = static_cast(tex_height_); + tex_info.layer_count_or_depth = 1; + tex_info.num_levels = 1; + scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info); + if (scene_texture_ == nullptr) { + SDL_Log("SDL3GPUShader: failed to create scene texture: %s", SDL_GetError()); + cleanup(); + return false; + } + + // ---------------------------------------------------------------- + // 4. Create upload transfer buffer (CPU → GPU, size = w*h*4 bytes) + // ---------------------------------------------------------------- + SDL_GPUTransferBufferCreateInfo tb_info = {}; + tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + tb_info.size = static_cast(tex_width_ * tex_height_ * 4); + upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info); + if (upload_buffer_ == nullptr) { + SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError()); + cleanup(); + return false; + } + + // ---------------------------------------------------------------- + // 5. Create samplers: NEAREST (pixel art) + LINEAR (supersampling) + // ---------------------------------------------------------------- + SDL_GPUSamplerCreateInfo samp_info = {}; + samp_info.min_filter = SDL_GPU_FILTER_NEAREST; + samp_info.mag_filter = SDL_GPU_FILTER_NEAREST; + samp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; + samp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + samp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + samp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + sampler_ = SDL_CreateGPUSampler(device_, &samp_info); + if (sampler_ == nullptr) { + SDL_Log("SDL3GPUShader: failed to create sampler: %s", SDL_GetError()); + cleanup(); + return false; + } + + SDL_GPUSamplerCreateInfo lsamp_info = {}; + lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR; + lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR; + lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; + lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info); + if (linear_sampler_ == nullptr) { + SDL_Log("SDL3GPUShader: failed to create linear sampler: %s", SDL_GetError()); + cleanup(); + return false; + } + + // ---------------------------------------------------------------- + // 6. Create PostFX graphics pipeline + // ---------------------------------------------------------------- + if (!createPipeline()) { + cleanup(); + return false; + } + + is_initialized_ = true; + SDL_Log("SDL3GPUShader: initialized OK (%dx%d)", tex_width_, tex_height_); + return true; + } + + // --------------------------------------------------------------------------- + // createPipeline + // --------------------------------------------------------------------------- + auto SDL3GPUShader::createPipeline() -> bool { + const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); + +#ifdef __APPLE__ + SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); +#else + SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); +#endif + + if ((vert == nullptr) || (frag == nullptr)) { + SDL_Log("SDL3GPUShader: failed to compile PostFX shaders"); + if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); } + if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); } + return false; + } + + SDL_GPUColorTargetBlendState no_blend = {}; + no_blend.enable_blend = false; + no_blend.enable_color_write_mask = false; + + SDL_GPUColorTargetDescription color_target = {}; + color_target.format = SWAPCHAIN_FMT; + color_target.blend_state = no_blend; + + SDL_GPUVertexInputState no_input = {}; + + SDL_GPUGraphicsPipelineCreateInfo pipe_info = {}; + pipe_info.vertex_shader = vert; + pipe_info.fragment_shader = frag; + pipe_info.vertex_input_state = no_input; + pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + pipe_info.target_info.num_color_targets = 1; + pipe_info.target_info.color_target_descriptions = &color_target; + + pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info); + + SDL_ReleaseGPUShader(device_, vert); + SDL_ReleaseGPUShader(device_, frag); + + if (pipeline_ == nullptr) { + SDL_Log("SDL3GPUShader: pipeline creation failed: %s", SDL_GetError()); + return false; + } + return true; + } + + // --------------------------------------------------------------------------- + // uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer. + // Con supersampling (oversample_ > 1) expande cada pixel del juego a un bloque + // oversample × oversample y hornea la scanline oscura en la última fila del bloque. + // --------------------------------------------------------------------------- + void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) { + if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; } + + void* mapped = SDL_MapGPUTransferBuffer(device_, upload_buffer_, false); + if (mapped == nullptr) { + SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError()); + return; + } + + if (oversample_ <= 1) { + // Path sin supersampling: copia directa + std::memcpy(mapped, pixels, static_cast(width * height * 4)); + } else { + // Path con supersampling: expande cada pixel a OS×OS, oscurece última fila. + // Replica la fórmula del shader: mix(3.5, 0.42, scanline_strength). + auto* out = static_cast(mapped); + const int OS = oversample_; + const float BRIGHT_MUL = 1.0F + (baked_scanline_strength_ * 2.5F); // rows 0..OS-2 + const float DARK_MUL = 1.0F - (baked_scanline_strength_ * 0.58F); // row OS-1 + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const Uint32 SRC = pixels[(y * width) + x]; + const Uint32 ALPHA = (SRC >> 24) & 0xFFU; + const auto FR = static_cast((SRC >> 16) & 0xFFU); + const auto FG = static_cast((SRC >> 8) & 0xFFU); + const auto FB = static_cast(SRC & 0xFFU); + + auto make_px = [ALPHA](float rv, float gv, float bv) -> Uint32 { + auto cl = [](float v) -> Uint32 { return static_cast(std::min(255.0F, v)); }; + return (ALPHA << 24) | (cl(rv) << 16) | (cl(gv) << 8) | cl(bv); + }; + + const Uint32 BRIGHT = make_px(FR * BRIGHT_MUL, FG * BRIGHT_MUL, FB * BRIGHT_MUL); + const Uint32 DARK = make_px(FR * DARK_MUL, FG * DARK_MUL, FB * DARK_MUL); + + for (int dy = 0; dy < OS; ++dy) { + const Uint32 OUT_PX = (dy == OS - 1) ? DARK : BRIGHT; + const int DST_Y = (y * OS) + dy; + for (int dx = 0; dx < OS; ++dx) { + out[(DST_Y * (width * OS)) + ((x * OS) + dx)] = OUT_PX; + } + } + } + } + } + + SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); + } + + // --------------------------------------------------------------------------- + // render — upload scene texture + PostFX pass → swapchain + // --------------------------------------------------------------------------- + void SDL3GPUShader::render() { + if (!is_initialized_) { return; } + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); + if (cmd == nullptr) { + SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError()); + return; + } + + // ---- Copy pass: transfer buffer → scene texture ---- + SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd); + if (copy != nullptr) { + SDL_GPUTextureTransferInfo src = {}; + src.transfer_buffer = upload_buffer_; + src.offset = 0; + src.pixels_per_row = static_cast(tex_width_); + src.rows_per_layer = static_cast(tex_height_); + + SDL_GPUTextureRegion dst = {}; + dst.texture = scene_texture_; + dst.w = static_cast(tex_width_); + dst.h = static_cast(tex_height_); + dst.d = 1; + + SDL_UploadToGPUTexture(copy, &src, &dst, false); + SDL_EndGPUCopyPass(copy); + } + + // ---- Acquire swapchain texture ---- + SDL_GPUTexture* swapchain = nullptr; + Uint32 sw = 0; + Uint32 sh = 0; + if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh)) { + SDL_Log("SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError()); + SDL_SubmitGPUCommandBuffer(cmd); + return; + } + if (swapchain == nullptr) { + // Window minimized — skip frame + SDL_SubmitGPUCommandBuffer(cmd); + return; + } + + // ---- Render pass: PostFX → swapchain ---- + SDL_GPUColorTargetInfo color_target = {}; + color_target.texture = swapchain; + color_target.load_op = SDL_GPU_LOADOP_CLEAR; + color_target.store_op = SDL_GPU_STOREOP_STORE; + color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; + + SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr); + if (pass != nullptr) { + SDL_BindGPUGraphicsPipeline(pass, pipeline_); + + // Calcular viewport usando las dimensiones lógicas del canvas (game_width_/height_), + // no las de la textura GPU (que pueden ser game×3 con supersampling). + // El GPU escala la textura para cubrir el viewport independientemente de su resolución. + float vx = 0.0F; + float vy = 0.0F; + float vw = 0.0F; + float vh = 0.0F; + if (integer_scale_) { + const int SCALE = std::max(1, std::min(static_cast(sw) / game_width_, static_cast(sh) / game_height_)); + vw = static_cast(game_width_ * SCALE); + vh = static_cast(game_height_ * SCALE); + } else { + const float SCALE = std::min( + static_cast(sw) / static_cast(game_width_), + static_cast(sh) / static_cast(game_height_)); + vw = static_cast(game_width_) * SCALE; + vh = static_cast(game_height_) * SCALE; + } + vx = std::floor((static_cast(sw) - vw) * 0.5F); + vy = std::floor((static_cast(sh) - vh) * 0.5F); + SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F}; + SDL_SetGPUViewport(pass, &vp); + + // pixel_scale: pixels físicos por pixel lógico de juego (para scanlines sin SS). + // Con SS las scanlines están horneadas en CPU → scanline_strength=0 → no se usa. + uniforms_.pixel_scale = (game_height_ > 0) + ? (vh / static_cast(game_height_)) + : 1.0F; + uniforms_.time = static_cast(SDL_GetTicks()) / 1000.0F; + uniforms_.oversample = static_cast(oversample_); + + // Con supersampling usamos LINEAR para que el escalado a zooms no-múltiplo-de-3 + // promedia correctamente las filas de scanline horneadas en CPU. + SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) + ? linear_sampler_ + : sampler_; + + SDL_GPUTextureSamplerBinding binding = {}; + binding.texture = scene_texture_; + binding.sampler = active_sampler; + SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); + + SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); + + SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); + SDL_EndGPURenderPass(pass); + } + + SDL_SubmitGPUCommandBuffer(cmd); + } + + // --------------------------------------------------------------------------- + // cleanup — libera pipeline/texturas/buffer pero mantiene device + swapchain + // --------------------------------------------------------------------------- + void SDL3GPUShader::cleanup() { + is_initialized_ = false; + + if (device_ != nullptr) { + SDL_WaitForGPUIdle(device_); + + if (pipeline_ != nullptr) { + SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_); + pipeline_ = nullptr; + } + if (scene_texture_ != nullptr) { + SDL_ReleaseGPUTexture(device_, scene_texture_); + scene_texture_ = nullptr; + } + if (upload_buffer_ != nullptr) { + SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_); + upload_buffer_ = nullptr; + } + if (sampler_ != nullptr) { + SDL_ReleaseGPUSampler(device_, sampler_); + sampler_ = nullptr; + } + if (linear_sampler_ != nullptr) { + SDL_ReleaseGPUSampler(device_, linear_sampler_); + linear_sampler_ = nullptr; + } + // device_ y el claim de la ventana se mantienen vivos + } + } + + // --------------------------------------------------------------------------- + // destroy — limpieza completa incluyendo device y swapchain (solo al cerrar) + // --------------------------------------------------------------------------- + void SDL3GPUShader::destroy() { + cleanup(); + + if (device_ != nullptr) { + if (window_ != nullptr) { + SDL_ReleaseWindowFromGPUDevice(device_, window_); + } + SDL_DestroyGPUDevice(device_); + device_ = nullptr; + } + window_ = nullptr; + } + + // --------------------------------------------------------------------------- + // Shader creation helpers + // --------------------------------------------------------------------------- + auto SDL3GPUShader::createShaderMSL(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info = {}; + info.code = reinterpret_cast(msl_source); + info.code_size = std::strlen(msl_source) + 1; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); + if (shader == nullptr) { + SDL_Log("SDL3GPUShader: MSL shader '%s' failed: %s", entrypoint, SDL_GetError()); + } + return shader; + } + + auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device, // NOLINT(readability-convert-member-functions-to-static) + const uint8_t* spv_code, + size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info = {}; + info.code = spv_code; + info.code_size = spv_size; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); + if (shader == nullptr) { + SDL_Log("SDL3GPUShader: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError()); + } + return shader; + } + + void SDL3GPUShader::setPostFXParams(const PostFXParams& p) { + uniforms_.vignette_strength = p.vignette; + uniforms_.chroma_strength = p.chroma; + uniforms_.mask_strength = p.mask; + uniforms_.gamma_strength = p.gamma; + uniforms_.curvature = p.curvature; + uniforms_.bleeding = p.bleeding; + uniforms_.flicker = p.flicker; + + // Con supersampling las scanlines se hornean en CPU (uploadPixels). + // El shader recibe strength=0 para no aplicarlas de nuevo en GPU. + baked_scanline_strength_ = p.scanlines; + uniforms_.scanline_strength = (oversample_ > 1) ? 0.0F : p.scanlines; + } + + void SDL3GPUShader::setVSync(bool vsync) { + vsync_ = vsync; + if (device_ != nullptr && window_ != nullptr) { + SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE); + } + } + + void SDL3GPUShader::setScaleMode(bool integer_scale) { + integer_scale_ = integer_scale; + } + + // --------------------------------------------------------------------------- + // setOversample — cambia el factor SS; recrea texturas si ya está inicializado + // --------------------------------------------------------------------------- + void SDL3GPUShader::setOversample(int factor) { + const int NEW_FACTOR = std::max(1, factor); + if (NEW_FACTOR == oversample_) { return; } + oversample_ = NEW_FACTOR; + if (is_initialized_) { + reinitTexturesAndBuffer(); + // scanline_strength se actualizará en el próximo setPostFXParams + } + } + + // --------------------------------------------------------------------------- + // reinitTexturesAndBuffer — recrea scene_texture_ y upload_buffer_ con el + // tamaño actual (game × oversample_). No toca pipeline ni samplers. + // --------------------------------------------------------------------------- + auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool { + if (device_ == nullptr) { return false; } + SDL_WaitForGPUIdle(device_); + + if (scene_texture_ != nullptr) { + SDL_ReleaseGPUTexture(device_, scene_texture_); + scene_texture_ = nullptr; + } + if (upload_buffer_ != nullptr) { + SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_); + upload_buffer_ = nullptr; + } + + tex_width_ = game_width_ * oversample_; + tex_height_ = game_height_ * oversample_; + uniforms_.screen_height = static_cast(tex_height_); + uniforms_.oversample = static_cast(oversample_); + + SDL_GPUTextureCreateInfo tex_info = {}; + tex_info.type = SDL_GPU_TEXTURETYPE_2D; + tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; + tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; + tex_info.width = static_cast(tex_width_); + tex_info.height = static_cast(tex_height_); + tex_info.layer_count_or_depth = 1; + tex_info.num_levels = 1; + scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info); + if (scene_texture_ == nullptr) { + SDL_Log("SDL3GPUShader: reinit — failed to create scene texture: %s", SDL_GetError()); + return false; + } + + SDL_GPUTransferBufferCreateInfo tb_info = {}; + tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + tb_info.size = static_cast(tex_width_ * tex_height_ * 4); + upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info); + if (upload_buffer_ == nullptr) { + SDL_Log("SDL3GPUShader: reinit — failed to create upload buffer: %s", SDL_GetError()); + SDL_ReleaseGPUTexture(device_, scene_texture_); + scene_texture_ = nullptr; + return false; + } + + SDL_Log("SDL3GPUShader: oversample %d → texture %dx%d", oversample_, tex_width_, tex_height_); + return true; + } + +} // namespace Rendering diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp new file mode 100644 index 0000000..7878854 --- /dev/null +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#include "core/rendering/shader_backend.hpp" + +// PostFX uniforms pushed to fragment stage each frame. +// Must match the MSL struct and GLSL uniform block layout. +// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement. +struct PostFXUniforms { + float vignette_strength; // 0 = none, ~0.8 = subtle + float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration + float scanline_strength; // 0 = off, 1 = full + float screen_height; // logical height in pixels (used by bleeding effect) + float mask_strength; // 0 = off, 1 = full phosphor dot mask + float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction + float curvature; // 0 = flat, 1 = max barrel distortion + float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding + float pixel_scale; // physical pixels per logical pixel (vh / tex_height_) + float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f) + float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS) + float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16) +}; + +namespace Rendering { + + /** + * @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux) + * + * Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS. + * Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene) + * → PostFX render pass → swapchain → present + */ + class SDL3GPUShader : public ShaderBackend { + public: + SDL3GPUShader() = default; + ~SDL3GPUShader() override; + + auto init(SDL_Window* window, + SDL_Texture* texture, + const std::string& vertex_source, + const std::string& fragment_source) -> bool override; + + void render() override; + void setTextureSize(float width, float height) override {} + void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo + void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar + [[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; } + + // Sube píxeles ARGB8888 desde CPU; llamado antes de render() + void uploadPixels(const Uint32* pixels, int width, int height) override; + + // Actualiza los parámetros de intensidad de los efectos PostFX + void setPostFXParams(const PostFXParams& p) override; + + // Activa/desactiva VSync en el swapchain + void setVSync(bool vsync) override; + + // Activa/desactiva escalado entero (integer scale) + void setScaleMode(bool integer_scale) override; + + // Establece factor de supersampling (1 = off, 3 = 3×SS) + void setOversample(int factor) override; + + private: + static auto createShaderMSL(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader*; + + static auto createShaderSPIRV(SDL_GPUDevice* device, + const uint8_t* spv_code, + size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader*; + + auto createPipeline() -> bool; + auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual + + SDL_Window* window_ = nullptr; + SDL_GPUDevice* device_ = nullptr; + SDL_GPUGraphicsPipeline* pipeline_ = nullptr; + SDL_GPUTexture* scene_texture_ = nullptr; + SDL_GPUTransferBuffer* upload_buffer_ = nullptr; + SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling + SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling + + PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F}; + + int game_width_ = 0; // Dimensiones originales del canvas (sin SS) + int game_height_ = 0; + int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_) + int tex_height_ = 0; + int oversample_ = 1; // Factor SS actual (1 o 3) + float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU + bool is_initialized_ = false; + bool vsync_ = true; + bool integer_scale_ = false; + }; + +} // namespace Rendering diff --git a/source/core/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp new file mode 100644 index 0000000..a040f4d --- /dev/null +++ b/source/core/rendering/shader_backend.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include + +#include + +namespace Rendering { + + /** + * @brief Parámetros de intensidad de los efectos PostFX + * Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp + */ + struct PostFXParams { + float vignette = 0.0F; // Intensidad de la viñeta + float scanlines = 0.0F; // Intensidad de las scanlines + float chroma = 0.0F; // Aberración cromática + float mask = 0.0F; // Máscara de fósforo RGB + float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full) + float curvature = 0.0F; // Curvatura barrel CRT + float bleeding = 0.0F; // Sangrado de color NTSC + float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz + }; + + /** + * @brief Interfaz abstracta para backends de renderizado con shaders + * + * Esta interfaz define el contrato que todos los backends de shaders + * deben cumplir (OpenGL, Metal, Vulkan, etc.) + */ + class ShaderBackend { + public: + virtual ~ShaderBackend() = default; + + /** + * @brief Inicializa el backend de shaders + * @param window Ventana SDL + * @param texture Textura de backbuffer a la que aplicar shaders + * @param vertex_source Código fuente del vertex shader + * @param fragment_source Código fuente del fragment shader + * @return true si la inicialización fue exitosa + */ + virtual auto init(SDL_Window* window, + SDL_Texture* texture, + const std::string& vertex_source, + const std::string& fragment_source) -> bool = 0; + + /** + * @brief Renderiza la textura con los shaders aplicados + */ + virtual void render() = 0; + + /** + * @brief Establece el tamaño de la textura como parámetro del shader + * @param width Ancho de la textura + * @param height Alto de la textura + */ + virtual void setTextureSize(float width, float height) = 0; + + /** + * @brief Limpia y libera recursos del backend + */ + virtual void cleanup() = 0; + + /** + * @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders + * Usado por SDL3GPUShader para evitar pasar por SDL_Texture + */ + virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {} + + /** + * @brief Establece los parámetros de intensidad de los efectos PostFX + * @param p Struct con todos los parámetros PostFX + */ + virtual void setPostFXParams(const PostFXParams& /*p*/) {} + + /** + * @brief Activa o desactiva VSync en el swapchain del GPU device + */ + virtual void setVSync(bool /*vsync*/) {} + + /** + * @brief Activa o desactiva el escalado entero (integer scale) + */ + virtual void setScaleMode(bool /*integer_scale*/) {} + + /** + * @brief Establece el factor de supersampling (1 = off, 3 = 3× SS) + * Con factor > 1, la textura GPU se crea a game×factor resolución y + * las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR. + */ + virtual void setOversample(int /*factor*/) {} + + /** + * @brief Verifica si el backend está usando aceleración por hardware + * @return true si usa aceleración (OpenGL/Metal/Vulkan) + */ + [[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0; + }; + +} // namespace Rendering diff --git a/source/core/rendering/sprites/animated_sprite.cpp b/source/core/rendering/sprites/animated_sprite.cpp new file mode 100644 index 0000000..8ddbd3a --- /dev/null +++ b/source/core/rendering/sprites/animated_sprite.cpp @@ -0,0 +1,344 @@ +#include "core/rendering/sprites/animated_sprite.hpp" + +#include // Para size_t +#include // Para basic_ostream, basic_istream, operator<<, basic... +#include // Para cout, cerr +#include // Para basic_stringstream +#include // Para runtime_error +#include + +#include "core/rendering/surface.hpp" +#include "utils/utils.hpp" + + +#include "external/fkyaml_node.hpp" // Para fkyaml::node + +// Helper: Convierte un nodo YAML de frames (array) a vector de SDL_FRect +auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector { + std::vector frames; + SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height}; + + for (const auto& frame_index_node : frames_node) { + const int NUM_TILE = frame_index_node.get_value(); + if (NUM_TILE <= max_tiles) { + rect.x = (NUM_TILE % frames_per_row) * frame_width; + rect.y = (NUM_TILE / frames_per_row) * frame_height; + frames.emplace_back(rect); + } + } + + return frames; +} + +// Carga las animaciones desde un fichero YAML +auto AnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr& surface, float& frame_width, float& frame_height) -> std::vector { // NOLINT(readability-convert-member-functions-to-static) + std::vector animations; + + // Extract filename for logging + const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1); + + try { + // Load YAML file using ResourceHelper (supports both filesystem and pack) + auto file_data = loadFileBytes(file_path); + + if (file_data.empty()) { + std::cerr << "Error: Unable to load animation file " << FILE_NAME << '\n'; + throw std::runtime_error("Animation file not found: " + file_path); + } + + printWithDots("Animation : ", FILE_NAME, "[ LOADED ]"); + + // Parse YAML from string + std::string yaml_content(file_data.begin(), file_data.end()); + auto yaml = fkyaml::node::deserialize(yaml_content); + + // --- Parse global configuration --- + if (yaml.contains("tileSetFile")) { + auto tile_set_file = yaml["tileSetFile"].get_value(); + surface = std::make_shared(tile_set_file); + } + + if (yaml.contains("frameWidth")) { + frame_width = static_cast(yaml["frameWidth"].get_value()); + } + + if (yaml.contains("frameHeight")) { + frame_height = static_cast(yaml["frameHeight"].get_value()); + } + + // Calculate sprite sheet parameters + int frames_per_row = 1; + int max_tiles = 1; + if (surface) { + frames_per_row = surface->getWidth() / static_cast(frame_width); + const int W = surface->getWidth() / static_cast(frame_width); + const int H = surface->getHeight() / static_cast(frame_height); + max_tiles = W * H; + } + + // --- Parse animations array --- + if (yaml.contains("animations") && yaml["animations"].is_sequence()) { + const auto& animations_node = yaml["animations"]; + + for (const auto& anim_node : animations_node) { + AnimationData animation; + + // Parse animation name + if (anim_node.contains("name")) { + animation.name = anim_node["name"].get_value(); + } + + // Parse speed (seconds per frame) + if (anim_node.contains("speed")) { + animation.speed = anim_node["speed"].get_value(); + } + + // Parse loop frame index + if (anim_node.contains("loop")) { + animation.loop = anim_node["loop"].get_value(); + } + + // Parse frames array + if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) { + animation.frames = convertYAMLFramesToRects( + anim_node["frames"], + frame_width, + frame_height, + frames_per_row, + max_tiles); + } + + animations.push_back(animation); + } + } + + } catch (const fkyaml::exception& e) { + std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n'; + throw; + } catch (const std::exception& e) { + std::cerr << "Error loading animation " << FILE_NAME << ": " << e.what() << '\n'; + throw; + } + + return animations; +} + +// Constructor con bytes YAML del cache (parsing lazy) +AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) { + // Parsear YAML desde los bytes cargados en cache + std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end()); + + try { + auto yaml = fkyaml::node::deserialize(yaml_content); + + // Variables para almacenar configuración global + float frame_width = 0.0F; + float frame_height = 0.0F; + + // --- Parse global configuration --- + if (yaml.contains("tileSetFile")) { + auto tile_set_file = yaml["tileSetFile"].get_value(); + // Ahora SÍ podemos acceder al cache (ya está completamente cargado) + surface_ = std::make_shared(tile_set_file); + } + + if (yaml.contains("frameWidth")) { + frame_width = static_cast(yaml["frameWidth"].get_value()); + } + + if (yaml.contains("frameHeight")) { + frame_height = static_cast(yaml["frameHeight"].get_value()); + } + + // Calculate sprite sheet parameters + int frames_per_row = 1; + int max_tiles = 1; + if (surface_) { + frames_per_row = surface_->getWidth() / static_cast(frame_width); + const int W = surface_->getWidth() / static_cast(frame_width); + const int H = surface_->getHeight() / static_cast(frame_height); + max_tiles = W * H; + } + + // --- Parse animations array --- + if (yaml.contains("animations") && yaml["animations"].is_sequence()) { + const auto& animations_node = yaml["animations"]; + + for (const auto& anim_node : animations_node) { + AnimationData animation; + + // Parse animation name + if (anim_node.contains("name")) { + animation.name = anim_node["name"].get_value(); + } + + // Parse speed (seconds per frame) + if (anim_node.contains("speed")) { + animation.speed = anim_node["speed"].get_value(); + } + + // Parse loop frame index + if (anim_node.contains("loop")) { + animation.loop = anim_node["loop"].get_value(); + } + + // Parse frames array + if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) { + animation.frames = convertYAMLFramesToRects( + anim_node["frames"], + frame_width, + frame_height, + frames_per_row, + max_tiles); + } + + animations_.push_back(animation); + } + } + + // Set dimensions + setWidth(frame_width); + setHeight(frame_height); + + // Inicializar con la primera animación si existe + if (!animations_.empty() && !animations_[0].frames.empty()) { + setClip(animations_[0].frames[0]); + } + + } catch (const fkyaml::exception& e) { + std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n'; + throw; + } catch (const std::exception& e) { + std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n'; + throw; + } +} + +// Constructor per a subclasses amb surface directa (sense YAML) +AnimatedSprite::AnimatedSprite(std::shared_ptr surface, SDL_FRect pos) + : MovingSprite(std::move(surface), pos) { + // animations_ queda buit (protegit per el guard de animate()) + if (surface_) { + clip_ = {.x = 0, .y = 0, .w = surface_->getWidth(), .h = surface_->getHeight()}; + } +} + +// Obtiene el indice de la animación a partir del nombre +auto AnimatedSprite::getIndex(const std::string& name) -> int { // NOLINT(readability-convert-member-functions-to-static) + auto index = -1; + + for (const auto& a : animations_) { + index++; + if (a.name == name) { + return index; + } + } + std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n'; + return -1; +} + +// Calcula el frame correspondiente a la animación (time-based) +void AnimatedSprite::animate(float delta_time) { // NOLINT(readability-convert-member-functions-to-static) + if (animations_.empty()) { return; } + if (animations_[current_animation_].speed <= 0.0F) { + return; + } + + // Acumula el tiempo transcurrido + animations_[current_animation_].accumulated_time += delta_time; + + // Calcula el frame actual a partir del tiempo acumulado + const int TARGET_FRAME = static_cast( + animations_[current_animation_].accumulated_time / + animations_[current_animation_].speed); + + // Si alcanza el final de la animación, maneja el loop + if (TARGET_FRAME >= static_cast(animations_[current_animation_].frames.size())) { + if (animations_[current_animation_].loop == -1) { + // Si no hay loop, congela en el último frame + animations_[current_animation_].current_frame = + static_cast(animations_[current_animation_].frames.size()) - 1; + animations_[current_animation_].completed = true; + + // Establece el clip del último frame + if (animations_[current_animation_].current_frame >= 0) { + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } + } else { + // Si hay loop, vuelve al frame indicado + animations_[current_animation_].accumulated_time = + static_cast(animations_[current_animation_].loop) * + animations_[current_animation_].speed; + animations_[current_animation_].current_frame = animations_[current_animation_].loop; + + // Establece el clip del frame de loop + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } + } else { + // Actualiza el frame actual + animations_[current_animation_].current_frame = TARGET_FRAME; + + // Establece el clip del frame actual + if (animations_[current_animation_].current_frame >= 0 && + animations_[current_animation_].current_frame < + static_cast(animations_[current_animation_].frames.size())) { + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } + } +} + +// Comprueba si ha terminado la animación +auto AnimatedSprite::animationIsCompleted() -> bool { + return animations_[current_animation_].completed; +} + +// Establece la animacion actual +void AnimatedSprite::setCurrentAnimation(const std::string& name) { + const auto NEW_ANIMATION = getIndex(name); + if (current_animation_ != NEW_ANIMATION) { + current_animation_ = NEW_ANIMATION; + animations_[current_animation_].current_frame = 0; + animations_[current_animation_].accumulated_time = 0.0F; + animations_[current_animation_].completed = false; + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } +} + +// Establece la animacion actual +void AnimatedSprite::setCurrentAnimation(int index) { + const auto NEW_ANIMATION = index; + if (current_animation_ != NEW_ANIMATION) { + current_animation_ = NEW_ANIMATION; + animations_[current_animation_].current_frame = 0; + animations_[current_animation_].accumulated_time = 0.0F; + animations_[current_animation_].completed = false; + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } +} + +// Actualiza las variables del objeto (time-based) +void AnimatedSprite::update(float delta_time) { + animate(delta_time); + MovingSprite::update(delta_time); +} + +// Reinicia la animación +void AnimatedSprite::resetAnimation() { + animations_[current_animation_].current_frame = 0; + animations_[current_animation_].accumulated_time = 0.0F; + animations_[current_animation_].completed = false; +} + +// Establece el frame actual de la animación +void AnimatedSprite::setCurrentAnimationFrame(int num) { + // Descarta valores fuera de rango + if (num < 0 || num >= static_cast(animations_[current_animation_].frames.size())) { + num = 0; + } + + // Cambia el valor de la variable + animations_[current_animation_].current_frame = num; + + // Escoge el frame correspondiente de la animación + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); +} diff --git a/source/core/rendering/sprites/animated_sprite.hpp b/source/core/rendering/sprites/animated_sprite.hpp new file mode 100644 index 0000000..6e18154 --- /dev/null +++ b/source/core/rendering/sprites/animated_sprite.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include // Para uint8_t +#include // Para shared_ptr +#include // Para string +#include +#include // Para vector + +#include "core/rendering/sprites/moving_sprite.hpp" // Para MovingSprite + +class Surface; + +// Recurso de animación: bytes crudos de un fichero YAML (para carga lazy) +struct AnimationResource { + std::string name; // Nombre del fichero + std::vector yaml_data; // Bytes del archivo YAML sin parsear +}; + +class AnimatedSprite : public MovingSprite { + public: + using Animations = std::vector; + + struct AnimationData { + std::string name; + std::vector frames; + float speed{0.083F}; // Segundos por frame + int loop{0}; // Frame al que vuelve al terminar (-1 = sin loop) + bool completed{false}; + int current_frame{0}; + float accumulated_time{0.0F}; + }; + + // Carga las animaciones desde un fichero YAML en el sistema de ficheros + static auto loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr& surface, float& frame_width, float& frame_height) -> std::vector; + + // Constructor con datos pre-cargados (bytes YAML en memoria) + explicit AnimatedSprite(const AnimationResource& cached_data); + + ~AnimatedSprite() override = default; + + void update(float delta_time) override; + + auto animationIsCompleted() -> bool; + auto getIndex(const std::string& name) -> int; + auto getCurrentAnimationSize() -> int { return static_cast(animations_[current_animation_].frames.size()); } + + void setCurrentAnimation(const std::string& name = "default"); + void setCurrentAnimation(int index = 0); + void resetAnimation(); + void setCurrentAnimationFrame(int num); + + protected: + AnimatedSprite(std::shared_ptr surface, SDL_FRect pos); + + void animate(float delta_time); + + private: + std::vector animations_; + int current_animation_{0}; +}; diff --git a/source/core/rendering/sprites/dissolve_sprite.cpp b/source/core/rendering/sprites/dissolve_sprite.cpp new file mode 100644 index 0000000..5ba0815 --- /dev/null +++ b/source/core/rendering/sprites/dissolve_sprite.cpp @@ -0,0 +1,188 @@ +#include "core/rendering/sprites/dissolve_sprite.hpp" + +#include // Para min +#include // Para uint32_t + +#include "core/rendering/surface.hpp" // Para Surface + +// Hash 2D estable per a dithering (rank aleatori per posició de píxel) +static auto pixelRank(int col, int row) -> float { + auto h = (static_cast(col) * 2246822519U) ^ (static_cast(row) * 2654435761U); + h ^= (h >> 13); + h *= 1274126177U; + h ^= (h >> 16); + return static_cast(h & 0xFFFFU) / 65536.0F; +} + +// Rang per a un píxel tenint en compte direcció (70% direccional + 30% aleatori) +auto DissolveSprite::computePixelRank(int col, int row, int frame_h, DissolveDirection dir) -> float { + const float RANDOM = pixelRank(col, row); + if (dir == DissolveDirection::NONE || frame_h <= 0) { + return RANDOM; + } + + float y_factor = 0.0F; + if (dir == DissolveDirection::DOWN) { + y_factor = static_cast(row) / static_cast(frame_h); + } else { + y_factor = static_cast(frame_h - 1 - row) / static_cast(frame_h); + } + + return (y_factor * 0.7F) + (RANDOM * 0.3F); +} + +// Constructor per a surface directa (sense AnimationResource) +DissolveSprite::DissolveSprite(std::shared_ptr surface, SDL_FRect pos) + : AnimatedSprite(std::move(surface), pos) { + if (surface_) { + const int W = static_cast(surface_->getWidth()); + const int H = static_cast(surface_->getHeight()); + surface_display_ = std::make_shared(W, H); + surface_display_->setTransparentColor(surface_->getTransparentColor()); + surface_display_->clear(surface_->getTransparentColor()); + } +} + +// Constructor +DissolveSprite::DissolveSprite(const AnimationResource& data) + : AnimatedSprite(data) { + if (surface_) { + const int W = static_cast(surface_->getWidth()); + const int H = static_cast(surface_->getHeight()); + surface_display_ = std::make_shared(W, H); + surface_display_->setTransparentColor(surface_->getTransparentColor()); + // Inicialitza tots els píxels com a transparents + surface_display_->clear(surface_->getTransparentColor()); + } +} + +// Reconstrueix la surface_display_ filtrant píxels per progress_ +void DissolveSprite::rebuildDisplaySurface() { + if (!surface_ || !surface_display_) { + return; + } + + const SDL_FRect CLIP = clip_; + const int SX = static_cast(CLIP.x); + const int SY = static_cast(CLIP.y); + const int SW = static_cast(CLIP.w); + const int SH = static_cast(CLIP.h); + + if (SW <= 0 || SH <= 0) { + return; + } + + auto src_data = surface_->getSurfaceData(); + auto dst_data = surface_display_->getSurfaceData(); + + const int SRC_W = static_cast(src_data->width); + const int DST_W = static_cast(dst_data->width); + const Uint8 TRANSPARENT = surface_->getTransparentColor(); + + // Esborra frame anterior si ha canviat + if (prev_clip_.w > 0 && prev_clip_.h > 0 && + (prev_clip_.x != CLIP.x || prev_clip_.y != CLIP.y || + prev_clip_.w != CLIP.w || prev_clip_.h != CLIP.h)) { + surface_display_->fillRect(&prev_clip_, TRANSPARENT); + } + + // Esborra la zona del frame actual (reconstrucció neta) + surface_display_->fillRect(&CLIP, TRANSPARENT); + + // Copia píxels filtrats per progress_ + for (int row = 0; row < SH; ++row) { + for (int col = 0; col < SW; ++col) { + const Uint8 COLOR = src_data->data[((SY + row) * SRC_W) + (SX + col)]; + if (COLOR == TRANSPARENT) { + continue; + } + const float RANK = computePixelRank(col, row, SH, direction_); + if (RANK >= progress_) { + const Uint8 OUT = (COLOR == source_color_) ? target_color_ : COLOR; + dst_data->data[((SY + row) * DST_W) + (SX + col)] = OUT; + } + } + } + + prev_clip_ = CLIP; + needs_rebuild_ = false; +} + +// Actualitza animació, moviment i transició temporal +void DissolveSprite::update(float delta_time) { + const SDL_FRect OLD_CLIP = clip_; + AnimatedSprite::update(delta_time); + + // Detecta canvi de frame d'animació + if (clip_.x != OLD_CLIP.x || clip_.y != OLD_CLIP.y || + clip_.w != OLD_CLIP.w || clip_.h != OLD_CLIP.h) { + needs_rebuild_ = true; + } + + // Actualitza transició temporal si activa + if (transition_mode_ != TransitionMode::NONE) { + transition_elapsed_ += delta_time * 1000.0F; + const float T = std::min(transition_elapsed_ / transition_duration_, 1.0F); + progress_ = (transition_mode_ == TransitionMode::DISSOLVING) ? T : (1.0F - T); + needs_rebuild_ = true; + if (T >= 1.0F) { + transition_mode_ = TransitionMode::NONE; + } + } + + if (needs_rebuild_) { + rebuildDisplaySurface(); + } +} + +// Renderitza: usa surface_display_ (amb color replace) si disponible +void DissolveSprite::render() { + if (!surface_display_) { + AnimatedSprite::render(); + return; + } + surface_display_->render(static_cast(pos_.x), static_cast(pos_.y), &clip_, flip_); +} + +// Estableix el progrés manualment +void DissolveSprite::setProgress(float progress) { + progress_ = std::min(std::max(progress, 0.0F), 1.0F); + needs_rebuild_ = true; +} + +// Inicia dissolució temporal (visible → invisible) +void DissolveSprite::startDissolve(float duration_ms, DissolveDirection dir) { + direction_ = dir; + transition_mode_ = TransitionMode::DISSOLVING; + transition_duration_ = duration_ms; + transition_elapsed_ = 0.0F; + progress_ = 0.0F; + needs_rebuild_ = true; +} + +// Inicia generació temporal (invisible → visible) +void DissolveSprite::startGenerate(float duration_ms, DissolveDirection dir) { + direction_ = dir; + transition_mode_ = TransitionMode::GENERATING; + transition_duration_ = duration_ms; + transition_elapsed_ = 0.0F; + progress_ = 1.0F; + needs_rebuild_ = true; +} + +// Atura la transició temporal +void DissolveSprite::stopTransition() { + transition_mode_ = TransitionMode::NONE; +} + +// Retorna si la transició ha acabat +auto DissolveSprite::isTransitionDone() const -> bool { + return transition_mode_ == TransitionMode::NONE; +} + +// Configura substitució de color per a la reconstrucció +void DissolveSprite::setColorReplace(Uint8 source, Uint8 target) { + source_color_ = source; + target_color_ = target; + needs_rebuild_ = true; +} diff --git a/source/core/rendering/sprites/dissolve_sprite.hpp b/source/core/rendering/sprites/dissolve_sprite.hpp new file mode 100644 index 0000000..5e4b1e7 --- /dev/null +++ b/source/core/rendering/sprites/dissolve_sprite.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include // Para shared_ptr + +#include "core/rendering/sprites/animated_sprite.hpp" // Para AnimatedSprite + +class Surface; + +// Direcció de la dissolució +enum class DissolveDirection { NONE, + DOWN, + UP }; + +// Sprite que pot dissoldre's o generar-se de forma aleatòria en X mil·lisegons. +// progress_ va de 0.0 (totalment visible) a 1.0 (totalment invisible). +class DissolveSprite : public AnimatedSprite { + public: + explicit DissolveSprite(const AnimationResource& data); + DissolveSprite(std::shared_ptr surface, SDL_FRect pos); + ~DissolveSprite() override = default; + + void update(float delta_time) override; + void render() override; + + // Progrés manual [0.0 = totalment visible, 1.0 = totalment invisible] + void setProgress(float progress); + [[nodiscard]] auto getProgress() const -> float { return progress_; } + + // Inicia una dissolució temporal (visible → invisible en duration_ms) + void startDissolve(float duration_ms, DissolveDirection dir = DissolveDirection::NONE); + + // Inicia una generació temporal (invisible → visible en duration_ms) + void startGenerate(float duration_ms, DissolveDirection dir = DissolveDirection::NONE); + + void stopTransition(); + [[nodiscard]] auto isTransitionDone() const -> bool; + + // Substitució de color: en reconstruir, substitueix source per target + void setColorReplace(Uint8 source, Uint8 target); + + private: + enum class TransitionMode { NONE, + DISSOLVING, + GENERATING }; + + std::shared_ptr surface_display_; // Superfície amb els píxels filtrats + + float progress_{0.0F}; // [0=visible, 1=invisible] + DissolveDirection direction_{DissolveDirection::NONE}; + TransitionMode transition_mode_{TransitionMode::NONE}; + float transition_duration_{0.0F}; + float transition_elapsed_{0.0F}; + SDL_FRect prev_clip_{.x = 0, .y = 0, .w = 0, .h = 0}; + bool needs_rebuild_{false}; + Uint8 source_color_{255}; // 255 = transparent = sense replace per defecte + Uint8 target_color_{0}; + + void rebuildDisplaySurface(); + [[nodiscard]] static auto computePixelRank(int col, int row, int frame_h, DissolveDirection dir) -> float; +}; diff --git a/source/core/rendering/sprites/moving_sprite.cpp b/source/core/rendering/sprites/moving_sprite.cpp new file mode 100644 index 0000000..78d1a5e --- /dev/null +++ b/source/core/rendering/sprites/moving_sprite.cpp @@ -0,0 +1,103 @@ +#include "core/rendering/sprites/moving_sprite.hpp" + +#include + +#include "core/rendering/surface.hpp" // Para Surface + +// Constructor +MovingSprite::MovingSprite(std::shared_ptr surface, SDL_FRect pos, SDL_FlipMode flip) + : Sprite(std::move(surface), pos), + x_(pos.x), + y_(pos.y), + flip_(flip) { Sprite::pos_ = pos; } + +MovingSprite::MovingSprite(std::shared_ptr surface, SDL_FRect pos) + : Sprite(std::move(surface), pos), + x_(pos.x), + y_(pos.y) { Sprite::pos_ = pos; } + +MovingSprite::MovingSprite() { Sprite::clear(); } + +MovingSprite::MovingSprite(std::shared_ptr surface) + : Sprite(std::move(surface)) { Sprite::clear(); } + +// Reinicia todas las variables +void MovingSprite::clear() { + // Resetea posición + x_ = 0.0F; + y_ = 0.0F; + + // Resetea velocidad + vx_ = 0.0F; + vy_ = 0.0F; + + // Resetea aceleración + ax_ = 0.0F; + ay_ = 0.0F; + + // Resetea flip + flip_ = SDL_FLIP_NONE; + + Sprite::clear(); +} + +// Mueve el sprite (time-based) +// Nota: vx_, vy_ ahora se interpretan como pixels/segundo +// Nota: ax_, ay_ ahora se interpretan como pixels/segundo² +void MovingSprite::move(float delta_time) { + // Aplica aceleración a velocidad (time-based) + vx_ += ax_ * delta_time; + vy_ += ay_ * delta_time; + + // Aplica velocidad a posición (time-based) + x_ += vx_ * delta_time; + y_ += vy_ * delta_time; + + // Actualiza posición entera para renderizado + pos_.x = static_cast(x_); + pos_.y = static_cast(y_); +} + +// Actualiza las variables internas del objeto (time-based) +void MovingSprite::update(float delta_time) { + move(delta_time); +} + +// Muestra el sprite por pantalla +void MovingSprite::render() { + surface_->render(pos_.x, pos_.y, &clip_, flip_); +} + +// Muestra el sprite por pantalla +void MovingSprite::render(Uint8 source_color, Uint8 target_color) { + surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_, flip_); +} + +// Establece la posición y_ el tamaño del objeto +void MovingSprite::setPos(SDL_FRect rect) { + x_ = rect.x; + y_ = rect.y; + + pos_ = rect; +} + +// Establece el valor de las variables +void MovingSprite::setPos(float x, float y) { + x_ = x; + y_ = y; + + pos_.x = static_cast(x_); + pos_.y = static_cast(y_); +} + +// Establece el valor de la variable +void MovingSprite::setPosX(float value) { + x_ = value; + pos_.x = static_cast(x_); +} + +// Establece el valor de la variable +void MovingSprite::setPosY(float value) { + y_ = value; + pos_.y = static_cast(y_); +} \ No newline at end of file diff --git a/source/core/rendering/sprites/moving_sprite.hpp b/source/core/rendering/sprites/moving_sprite.hpp new file mode 100644 index 0000000..5276a67 --- /dev/null +++ b/source/core/rendering/sprites/moving_sprite.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include // Para shared_ptr + +#include "core/rendering/sprites/sprite.hpp" // Para Sprite +class Surface; // lines 8-8 + +// Clase MovingSprite. Añade movimiento y flip al sprite +class MovingSprite : public Sprite { + public: + // Constructores + MovingSprite(std::shared_ptr surface, SDL_FRect pos, SDL_FlipMode flip); + MovingSprite(std::shared_ptr surface, SDL_FRect pos); + explicit MovingSprite(); + explicit MovingSprite(std::shared_ptr surface); + ~MovingSprite() override = default; + + // Actualización y renderizado + void update(float delta_time) override; // Actualiza variables internas (time-based) + void render() override; // Muestra el sprite por pantalla + void render(Uint8 source_color, Uint8 target_color) override; // Renderiza con reemplazo de color + + // Gestión de estado + void clear() override; // Reinicia todas las variables a cero + + // Getters de posición + [[nodiscard]] auto getPosX() const -> float { return x_; } + [[nodiscard]] auto getPosY() const -> float { return y_; } + + // Getters de velocidad + [[nodiscard]] auto getVelX() const -> float { return vx_; } + [[nodiscard]] auto getVelY() const -> float { return vy_; } + + // Getters de aceleración + [[nodiscard]] auto getAccelX() const -> float { return ax_; } + [[nodiscard]] auto getAccelY() const -> float { return ay_; } + + // Setters de posición + void setPos(SDL_FRect rect); // Establece posición y tamaño del objeto + void setPos(float x, float y); // Establece posición x, y + void setPosX(float value); // Establece posición X + void setPosY(float value); // Establece posición Y + + // Setters de velocidad + void setVelX(float value) { vx_ = value; } + void setVelY(float value) { vy_ = value; } + + // Setters de aceleración + void setAccelX(float value) { ax_ = value; } + void setAccelY(float value) { ay_ = value; } + + // Gestión de flip (volteo horizontal) + void setFlip(SDL_FlipMode flip) { flip_ = flip; } // Establece modo de flip + auto getFlip() -> SDL_FlipMode { return flip_; } // Obtiene modo de flip + void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } // Alterna flip horizontal + + protected: + // Métodos protegidos + void move(float delta_time); // Mueve el sprite (time-based) + + // Variables miembro - Posición + float x_{0.0F}; // Posición en el eje X + float y_{0.0F}; // Posición en el eje Y + + // Variables miembro - Velocidad (pixels/segundo) + float vx_{0.0F}; // Velocidad en el eje X + float vy_{0.0F}; // Velocidad en el eje Y + + // Variables miembro - Aceleración (pixels/segundo²) + float ax_{0.0F}; // Aceleración en el eje X + float ay_{0.0F}; // Aceleración en el eje Y + + // Variables miembro - Renderizado + SDL_FlipMode flip_{SDL_FLIP_NONE}; // Modo de volteo del sprite +}; \ No newline at end of file diff --git a/source/core/rendering/sprites/sprite.cpp b/source/core/rendering/sprites/sprite.cpp new file mode 100644 index 0000000..262f5f2 --- /dev/null +++ b/source/core/rendering/sprites/sprite.cpp @@ -0,0 +1,76 @@ +#include "core/rendering/sprites/sprite.hpp" + +#include + +#include "core/rendering/surface.hpp" // Para Surface + +// Constructor +Sprite::Sprite(std::shared_ptr surface, float x, float y, float w, float h) + : surface_(std::move(surface)), + pos_{.x = x, .y = y, .w = w, .h = h}, + clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {} + +Sprite::Sprite(std::shared_ptr surface, SDL_FRect rect) + : surface_(std::move(surface)), + pos_(rect), + clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {} + +Sprite::Sprite() = default; + +Sprite::Sprite(std::shared_ptr surface) + : surface_(std::move(surface)), + pos_{0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()}, + clip_(pos_) {} + +// Muestra el sprite por pantalla +void Sprite::render() { + surface_->render(pos_.x, pos_.y, &clip_); +} + +void Sprite::render(Uint8 source_color, Uint8 target_color) { + surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_); +} + +void Sprite::renderWithVerticalFade(int fade_h, int canvas_height) { + surface_->renderWithVerticalFade( + static_cast(pos_.x), + static_cast(pos_.y), + fade_h, + canvas_height, + &clip_); +} + +void Sprite::renderWithVerticalFade(int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color) { + surface_->renderWithVerticalFade( + static_cast(pos_.x), + static_cast(pos_.y), + fade_h, + canvas_height, + source_color, + target_color, + &clip_); +} + +// Establece la posición del objeto +void Sprite::setPosition(float x, float y) { + pos_.x = x; + pos_.y = y; +} + +// Establece la posición del objeto +void Sprite::setPosition(SDL_FPoint p) { + pos_.x = p.x; + pos_.y = p.y; +} + +// Reinicia las variables a cero +void Sprite::clear() { + pos_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; + clip_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; +} + +// Actualiza el estado del sprite (time-based) +void Sprite::update(float delta_time) { + // Base implementation does nothing (static sprites) + (void)delta_time; // Evita warning de parámetro no usado +} \ No newline at end of file diff --git a/source/core/rendering/sprites/sprite.hpp b/source/core/rendering/sprites/sprite.hpp new file mode 100644 index 0000000..bc30233 --- /dev/null +++ b/source/core/rendering/sprites/sprite.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include +class Surface; // lines 5-5 + +// Clase Sprite +class Sprite { + public: + // Constructores + Sprite(std::shared_ptr, float x, float y, float w, float h); + Sprite(std::shared_ptr, SDL_FRect rect); + Sprite(); + explicit Sprite(std::shared_ptr); + + // Destructor + virtual ~Sprite() = default; + + // Actualización y renderizado + virtual void update(float delta_time); // Actualiza el estado del sprite (time-based) + virtual void render(); // Muestra el sprite por pantalla + virtual void render(Uint8 source_color, Uint8 target_color); // Renderiza con reemplazo de color + virtual void renderWithVerticalFade(int fade_h, int canvas_height); // Renderiza amb dissolució vertical (hash 2D, sense parpelleig) + virtual void renderWithVerticalFade(int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color); // Idem amb reemplaç de color + + // Gestión de estado + virtual void clear(); // Reinicia las variables a cero + + // Obtención de propiedades + [[nodiscard]] auto getX() const -> float { return pos_.x; } + [[nodiscard]] auto getY() const -> float { return pos_.y; } + [[nodiscard]] auto getWidth() const -> float { return pos_.w; } + [[nodiscard]] auto getHeight() const -> float { return pos_.h; } + [[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; } + [[nodiscard]] auto getClip() const -> SDL_FRect { return clip_; } + [[nodiscard]] auto getSurface() const -> std::shared_ptr { return surface_; } + auto getRect() -> SDL_FRect& { return pos_; } + + // Modificación de posición y tamaño + void setX(float x) { pos_.x = x; } + void setY(float y) { pos_.y = y; } + void setWidth(float w) { pos_.w = w; } + void setHeight(float h) { pos_.h = h; } + void setPosition(float x, float y); + void setPosition(SDL_FPoint p); + void setPosition(SDL_FRect r) { pos_ = r; } + void incX(float value) { pos_.x += value; } + void incY(float value) { pos_.y += value; } + + // Modificación de clip y surface + void setClip(SDL_FRect rect) { clip_ = rect; } + void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{.x = x, .y = y, .w = w, .h = h}; } + void setSurface(std::shared_ptr surface) { surface_ = std::move(surface); } + + protected: + // Variables miembro + std::shared_ptr surface_{nullptr}; // Surface donde estan todos los dibujos del sprite + SDL_FRect pos_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; // Posición y tamaño donde dibujar el sprite + SDL_FRect clip_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; // Rectangulo de origen de la surface que se dibujará en pantalla +}; \ No newline at end of file diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp new file mode 100644 index 0000000..89ec13a --- /dev/null +++ b/source/core/rendering/surface.cpp @@ -0,0 +1,679 @@ +// IWYU pragma: no_include +#include "core/rendering/surface.hpp" + +#include + +#include // Para min, max, copy_n, fill +#include // Para abs +#include // Para uint32_t +#include // Para memcpy, size_t +#include // Para basic_ifstream, basic_ostream, basic_ist... +#include // Para cerr +#include // Para shared_ptr, __shared_ptr_access, default... +#include // Para basic_istringstream +#include // Para runtime_error +#include // Para vector + +#include "core/rendering/gif.hpp" +#include "utils/utils.hpp" +#include "core/rendering/screen.hpp" // Para Screen + + +// Carga una paleta desde un archivo .gif +auto loadPalette(const std::string& file_path) -> Palette { + // Carga el fichero desde el sistema de ficheros + auto buffer = loadFileBytes(file_path); + if (buffer.empty()) { + throw std::runtime_error("Error opening file: " + file_path); + } + + // Cargar la paleta usando los datos del buffer + std::vector pal = GIF::Gif::loadPalette(buffer.data()); + if (pal.empty()) { + throw std::runtime_error("No palette found in GIF file: " + file_path); + } + + // Crear la paleta y copiar los datos desde 'pal' + Palette palette = {}; // Inicializa la paleta con ceros + std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin()); + + // Mensaje de depuración + printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); + + return palette; +} + +// Carga una paleta desde un archivo .pal +auto readPalFile(const std::string& file_path) -> Palette { + Palette palette{}; + palette.fill(0); // Inicializar todo con 0 (transparente por defecto) + + // Carga el fichero desde el sistema de ficheros + auto file_data = loadFileBytes(file_path); + if (file_data.empty()) { + throw std::runtime_error("No se pudo abrir el archivo .pal: " + file_path); + } + + // Convert bytes to string for parsing + std::string content(file_data.begin(), file_data.end()); + std::istringstream stream(content); + + std::string line; + int line_number = 0; + int color_index = 0; + + while (std::getline(stream, line)) { + ++line_number; + + // Ignorar las tres primeras líneas del archivo + if (line_number <= 3) { + continue; + } + + // Procesar las líneas restantes con valores RGB + std::istringstream ss(line); + int r; + int g; + int b; + if (ss >> r >> g >> b) { + // Construir el color ARGB (A = 255 por defecto) + Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b; + palette[color_index++] = color; + + // Limitar a un máximo de 256 colores (opcional) + if (color_index >= 256) { + break; + } + } + } + + printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); + return palette; +} + +// Constructor +Surface::Surface(int w, int h) + : surface_data_(std::make_shared(w, h)), + transparent_color_(static_cast(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); } + +Surface::Surface(const std::string& file_path) + : transparent_color_(static_cast(PaletteColor::TRANSPARENT)) { + SurfaceData loaded_data = loadSurface(file_path); + surface_data_ = std::make_shared(std::move(loaded_data)); + + initializeSubPalette(sub_palette_); +} + +// Carga una superficie desde un archivo +auto Surface::loadSurface(const std::string& file_path) -> SurfaceData { // NOLINT(readability-convert-member-functions-to-static) + // Carga el fichero desde el sistema de ficheros + std::vector buffer = loadFileBytes(file_path); + if (buffer.empty()) { + std::cerr << "Error opening file: " << file_path << '\n'; + throw std::runtime_error("Error opening file"); + } + + // Crear un objeto Gif y llamar a la función loadGif + Uint16 w = 0; + Uint16 h = 0; + std::vector raw_pixels = GIF::Gif::loadGif(buffer.data(), w, h); + if (raw_pixels.empty()) { + std::cerr << "Error loading GIF from file: " << file_path << '\n'; + throw std::runtime_error("Error loading GIF"); + } + + // Si el constructor de Surface espera un std::shared_ptr, + // reservamos un bloque dinámico y copiamos los datos del vector. + size_t pixel_count = raw_pixels.size(); + auto pixels = std::shared_ptr(new Uint8[pixel_count], std::default_delete()); + std::memcpy(pixels.get(), raw_pixels.data(), pixel_count); + + // Crear y devolver directamente el objeto SurfaceData + printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); + return {static_cast(w), static_cast(h), pixels}; +} + +// Carga una paleta desde un archivo +void Surface::loadPalette(const std::string& file_path) { + palette_ = ::loadPalette(file_path); +} + +// Carga una paleta desde otra paleta +void Surface::loadPalette(const Palette& palette) { + palette_ = palette; +} + +// Establece un color en la paleta +void Surface::setColor(int index, Uint32 color) { + palette_.at(index) = color; +} + +// Rellena la superficie con un color +void Surface::clear(Uint8 color) { // NOLINT(readability-convert-member-functions-to-static) + const size_t TOTAL_PIXELS = surface_data_->width * surface_data_->height; + Uint8* data_ptr = surface_data_->data.get(); + std::fill(data_ptr, data_ptr + TOTAL_PIXELS, color); +} + +// Pone un pixel en la SurfaceData +void Surface::putPixel(int x, int y, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static) + if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) { + return; // Coordenadas fuera de rango + } + + const int INDEX = x + (y * surface_data_->width); + surface_data_->data.get()[INDEX] = color; +} + +// Obtiene el color de un pixel de la surface_data +auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * static_cast(surface_data_->width))]; } + +// Dibuja un rectangulo relleno +void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static) + // Limitar los valores del rectángulo al tamaño de la superficie + float x_start = std::max(0.0F, rect->x); + float y_start = std::max(0.0F, rect->y); + float x_end = std::min(rect->x + rect->w, surface_data_->width); + float y_end = std::min(rect->y + rect->h, surface_data_->height); + + // Recorrer cada píxel dentro del rectángulo directamente + for (int y = y_start; y < y_end; ++y) { + for (int x = x_start; x < x_end; ++x) { + const int INDEX = x + (y * surface_data_->width); + surface_data_->data.get()[INDEX] = color; + } + } +} + +// Dibuja el borde de un rectangulo +void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static) + // Limitar los valores del rectángulo al tamaño de la superficie + float x_start = std::max(0.0F, rect->x); + float y_start = std::max(0.0F, rect->y); + float x_end = std::min(rect->x + rect->w, surface_data_->width); + float y_end = std::min(rect->y + rect->h, surface_data_->height); + + // Dibujar bordes horizontales + for (int x = x_start; x < x_end; ++x) { + // Borde superior + const int TOP_INDEX = x + (y_start * surface_data_->width); + surface_data_->data.get()[TOP_INDEX] = color; + + // Borde inferior + const int BOTTOM_INDEX = x + ((y_end - 1) * surface_data_->width); + surface_data_->data.get()[BOTTOM_INDEX] = color; + } + + // Dibujar bordes verticales + for (int y = y_start; y < y_end; ++y) { + // Borde izquierdo + const int LEFT_INDEX = x_start + (y * surface_data_->width); + surface_data_->data.get()[LEFT_INDEX] = color; + + // Borde derecho + const int RIGHT_INDEX = (x_end - 1) + (y * surface_data_->width); + surface_data_->data.get()[RIGHT_INDEX] = color; + } +} + +// Dibuja una linea +void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static) + // Calcula las diferencias + float dx = std::abs(x2 - x1); + float dy = std::abs(y2 - y1); + + // Determina la dirección del incremento + float sx = (x1 < x2) ? 1 : -1; + float sy = (y1 < y2) ? 1 : -1; + + float err = dx - dy; + + while (true) { + // Asegúrate de no dibujar fuera de los límites de la superficie + if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) { + surface_data_->data.get()[static_cast(x1 + (y1 * surface_data_->width))] = color; + } + + // Si alcanzamos el punto final, salimos + if (x1 == x2 && y1 == y2) { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + } +} + +void Surface::render(float dx, float dy, float sx, float sy, float w, float h) { // NOLINT(readability-make-member-function-const) + auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); + + // Limitar la región para evitar accesos fuera de rango en origen + w = std::min(w, surface_data_->width - sx); + h = std::min(h, surface_data_->height - sy); + + // Limitar la región para evitar accesos fuera de rango en destino + w = std::min(w, surface_data->width - dx); + h = std::min(h, surface_data->height - dy); + + for (int iy = 0; iy < h; ++iy) { + for (int ix = 0; ix < w; ++ix) { + // Verificar que las coordenadas de destino están dentro de los límites + if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) { + if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) { + int src_x = sx + ix; + int src_y = sy + iy; + + Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; + if (color != static_cast(transparent_color_)) { + surface_data->data.get()[static_cast(dest_x + (dest_y * surface_data->width))] = sub_palette_[color]; + } + } + } + } + } +} + +void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // NOLINT(readability-make-member-function-const) + auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + + // Determina la región de origen (clip) a renderizar + float sx = (src_rect != nullptr) ? src_rect->x : 0; + float sy = (src_rect != nullptr) ? src_rect->y : 0; + float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width; + float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height; + + // Limitar la región para evitar accesos fuera de rango en origen + w = std::min(w, surface_data_->width - sx); + h = std::min(h, surface_data_->height - sy); + w = std::min(w, surface_data_dest->width - x); + h = std::min(h, surface_data_dest->height - y); + + // Limitar la región para evitar accesos fuera de rango en destino + w = std::min(w, surface_data_dest->width - x); + h = std::min(h, surface_data_dest->height - y); + + // Renderiza píxel por píxel aplicando el flip si es necesario + for (int iy = 0; iy < h; ++iy) { + for (int ix = 0; ix < w; ++ix) { + // Coordenadas de origen + int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); + int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); + + // Coordenadas de destino + int dest_x = x + ix; + int dest_y = y + iy; + + // Verificar que las coordenadas de destino están dentro de los límites + if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) { + // Copia el píxel si no es transparente + Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; + if (color != static_cast(transparent_color_)) { + surface_data_dest->data[dest_x + (dest_y * surface_data_dest->width)] = sub_palette_[color]; + } + } + } + } +} + +// Helper para calcular coordenadas con flip +void Surface::calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y) { + src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); + src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); +} + +// Helper para copiar un pixel si no es transparente +void Surface::copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const { + if (dest_x < 0 || dest_y < 0) { + return; + } + + Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; + if (color != static_cast(transparent_color_)) { + dest_data[dest_x + (dest_y * dest_width)] = sub_palette_[color]; + } +} + +// Copia una región de la superficie de origen a la de destino +void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip) { + auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); + + // Si srcRect es nullptr, tomar toda la superficie fuente + float sx = (src_rect != nullptr) ? src_rect->x : 0; + float sy = (src_rect != nullptr) ? src_rect->y : 0; + float sw = (src_rect != nullptr) ? src_rect->w : surface_data_->width; + float sh = (src_rect != nullptr) ? src_rect->h : surface_data_->height; + + // Si dstRect es nullptr, asignar las mismas dimensiones que srcRect + float dx = (dst_rect != nullptr) ? dst_rect->x : 0; + float dy = (dst_rect != nullptr) ? dst_rect->y : 0; + float dw = (dst_rect != nullptr) ? dst_rect->w : sw; + float dh = (dst_rect != nullptr) ? dst_rect->h : sh; + + // Asegurarse de que srcRect y dstRect tienen las mismas dimensiones + if (sw != dw || sh != dh) { + dw = sw; // Respetar las dimensiones de srcRect + dh = sh; + } + + // Limitar la región para evitar accesos fuera de rango en src y dst + sw = std::min(sw, surface_data_->width - sx); + sh = std::min(sh, surface_data_->height - sy); + dw = std::min(dw, surface_data->width - dx); + dh = std::min(dh, surface_data->height - dy); + + int final_width = std::min(sw, dw); + int final_height = std::min(sh, dh); + + // Renderiza píxel por píxel aplicando el flip si es necesario + for (int iy = 0; iy < final_height; ++iy) { + for (int ix = 0; ix < final_width; ++ix) { + int src_x = 0; + int src_y = 0; + calculateFlippedCoords(ix, iy, sx, sy, final_width, final_height, flip, src_x, src_y); + + int dest_x = dx + ix; + int dest_y = dy + iy; + + // Verificar límites de destino antes de copiar + if (dest_x >= 0 && dest_x < surface_data->width && dest_y >= 0 && dest_y < surface_data->height) { + copyPixelIfNotTransparent(surface_data->data.get(), dest_x, dest_y, surface_data->width, src_x, src_y); + } + } + } +} + +// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro +void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const { + auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); + + // Determina la región de origen (clip) a renderizar + float sx = (src_rect != nullptr) ? src_rect->x : 0; + float sy = (src_rect != nullptr) ? src_rect->y : 0; + float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width; + float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height; + + // Limitar la región para evitar accesos fuera de rango + w = std::min(w, surface_data_->width - sx); + h = std::min(h, surface_data_->height - sy); + + // Renderiza píxel por píxel aplicando el flip si es necesario + for (int iy = 0; iy < h; ++iy) { + for (int ix = 0; ix < w; ++ix) { + // Coordenadas de origen + int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); + int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); + + // Coordenadas de destino + int dest_x = x + ix; + int dest_y = y + iy; + + // Verifica que las coordenadas de destino estén dentro de los límites + if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height) { + continue; // Saltar píxeles fuera del rango del destino + } + + // Copia el píxel si no es transparente + Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; + if (color != static_cast(transparent_color_)) { + surface_data->data[dest_x + (dest_y * surface_data->width)] = + (color == source_color) ? target_color : color; + } + } + } +} + +// Hash 2D estable per a dithering sense flickering +static auto pixelThreshold(int col, int row) -> float { + auto h = (static_cast(col) * 2246822519U) ^ (static_cast(row) * 2654435761U); + h ^= (h >> 13); + h *= 1274126177U; + h ^= (h >> 16); + return static_cast(h & 0xFFFFU) / 65536.0F; +} + +// Calcula la densidad de fade para un pixel en posición screen_y +static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> float { + if (screen_y < fade_h) { + return static_cast(fade_h - screen_y) / static_cast(fade_h); + } + if (screen_y >= canvas_height - fade_h) { + return static_cast(screen_y - (canvas_height - fade_h)) / static_cast(fade_h); + } + return 0.0F; +} + +// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) +void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const { + const int SX = (src_rect != nullptr) ? static_cast(src_rect->x) : 0; + const int SY = (src_rect != nullptr) ? static_cast(src_rect->y) : 0; + const int SW = (src_rect != nullptr) ? static_cast(src_rect->w) : static_cast(surface_data_->width); + const int SH = (src_rect != nullptr) ? static_cast(src_rect->h) : static_cast(surface_data_->height); + + auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + + for (int row = 0; row < SH; row++) { + const int SCREEN_Y = y + row; + if (SCREEN_Y < 0 || SCREEN_Y >= static_cast(surface_data_dest->height)) { + continue; + } + + const float DENSITY = computeFadeDensity(SCREEN_Y, fade_h, canvas_height); + + for (int col = 0; col < SW; col++) { + const int SCREEN_X = x + col; + if (SCREEN_X < 0 || SCREEN_X >= static_cast(surface_data_dest->width)) { + continue; + } + + const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast(surface_data_->width)) + (SX + col)]; + if (COLOR == static_cast(transparent_color_)) { + continue; + } + + if (pixelThreshold(col, row) < DENSITY) { + continue; // Pixel tapat per la zona de fade + } + + surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast(surface_data_dest->width))] = sub_palette_[COLOR]; + } + } +} + +// Idem però reemplaçant un color índex +void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const { + const int SX = (src_rect != nullptr) ? static_cast(src_rect->x) : 0; + const int SY = (src_rect != nullptr) ? static_cast(src_rect->y) : 0; + const int SW = (src_rect != nullptr) ? static_cast(src_rect->w) : static_cast(surface_data_->width); + const int SH = (src_rect != nullptr) ? static_cast(src_rect->h) : static_cast(surface_data_->height); + + auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); + + for (int row = 0; row < SH; row++) { + const int SCREEN_Y = y + row; + if (SCREEN_Y < 0 || SCREEN_Y >= static_cast(surface_data_dest->height)) { + continue; + } + + const float DENSITY = computeFadeDensity(SCREEN_Y, fade_h, canvas_height); + + for (int col = 0; col < SW; col++) { + const int SCREEN_X = x + col; + if (SCREEN_X < 0 || SCREEN_X >= static_cast(surface_data_dest->width)) { + continue; + } + + const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast(surface_data_->width)) + (SX + col)]; + if (COLOR == static_cast(transparent_color_)) { + continue; + } + + if (pixelThreshold(col, row) < DENSITY) { + continue; // Pixel tapat per la zona de fade + } + + const Uint8 OUT_COLOR = (COLOR == source_color) ? target_color : sub_palette_[COLOR]; + surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast(surface_data_dest->width))] = OUT_COLOR; + } + } +} + +// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture ni SDL_Renderer) +void Surface::toARGBBuffer(Uint32* buffer) const { // NOLINT(readability-convert-member-functions-to-static) + if (!surface_data_ || (surface_data_->data == nullptr)) { return; } + const int WIDTH = static_cast(surface_data_->width); + const int HEIGHT = static_cast(surface_data_->height); + const Uint8* src = surface_data_->data.get(); + for (int y = 0; y < HEIGHT; ++y) { + for (int x = 0; x < WIDTH; ++x) { + buffer[(y * WIDTH) + x] = palette_[src[(y * WIDTH) + x]]; + } + } +} + +// Vuelca la superficie a una textura +void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { // NOLINT(readability-convert-member-functions-to-static) + if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { + throw std::runtime_error("Renderer or texture is null."); + } + + if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) { + throw std::runtime_error("Invalid surface dimensions or data."); + } + + Uint32* pixels = nullptr; + int pitch = 0; + + // Bloquea la textura para modificar los píxeles directamente + if (!SDL_LockTexture(texture, nullptr, reinterpret_cast(&pixels), &pitch)) { + throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError())); + } + + // Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware) + int row_stride = pitch / sizeof(Uint32); + + for (int y = 0; y < surface_data_->height; ++y) { + for (int x = 0; x < surface_data_->width; ++x) { + // Calcular la posición correcta en la textura teniendo en cuenta el stride + int texture_index = (y * row_stride) + x; + int surface_index = (y * surface_data_->width) + x; + + pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]]; + } + } + + SDL_UnlockTexture(texture); // Desbloquea la textura + + // Renderiza la textura en la pantalla completa + if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) { + throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError())); + } +} + +// Vuelca la superficie a una textura +void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) { // NOLINT(readability-convert-member-functions-to-static) + if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { + throw std::runtime_error("Renderer or texture is null."); + } + + if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) { + throw std::runtime_error("Invalid surface dimensions or data."); + } + + Uint32* pixels = nullptr; + int pitch = 0; + + SDL_Rect lock_rect; + if (dest_rect != nullptr) { + lock_rect.x = static_cast(dest_rect->x); + lock_rect.y = static_cast(dest_rect->y); + lock_rect.w = static_cast(dest_rect->w); + lock_rect.h = static_cast(dest_rect->h); + } + + // Usa lockRect solo si destRect no es nulo + if (!SDL_LockTexture(texture, (dest_rect != nullptr) ? &lock_rect : nullptr, reinterpret_cast(&pixels), &pitch)) { + throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError())); + } + + int row_stride = pitch / sizeof(Uint32); + + for (int y = 0; y < surface_data_->height; ++y) { + for (int x = 0; x < surface_data_->width; ++x) { + int texture_index = (y * row_stride) + x; + int surface_index = (y * surface_data_->width) + x; + + pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]]; + } + } + + SDL_UnlockTexture(texture); + + // Renderiza la textura con los rectángulos especificados + if (!SDL_RenderTexture(renderer, texture, src_rect, dest_rect)) { + throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError())); + } +} + +// Realiza un efecto de fundido en la paleta principal +auto Surface::fadePalette() -> bool { // NOLINT(readability-convert-member-functions-to-static) + // Verificar que el tamaño mínimo de palette_ sea adecuado + static constexpr int PALETTE_SIZE = 19; + if (sizeof(palette_) / sizeof(palette_[0]) < PALETTE_SIZE) { + throw std::runtime_error("Palette size is insufficient for fadePalette operation."); + } + + // Desplazar colores (pares e impares) + for (int i = 18; i > 1; --i) { + palette_[i] = palette_[i - 2]; + } + + // Ajustar el primer color + palette_[1] = palette_[0]; + + // Devolver si el índice 15 coincide con el índice 0 + return palette_[15] == palette_[0]; +} + +// Realiza un efecto de fundido en la paleta secundaria +auto Surface::fadeSubPalette(Uint32 delay) -> bool { // NOLINT(readability-convert-member-functions-to-static) + // Variable estática para almacenar el último tick + static Uint32 last_tick_ = 0; + + // Obtener el tiempo actual + Uint32 current_tick = SDL_GetTicks(); + + // Verificar si ha pasado el tiempo de retardo + if (current_tick - last_tick_ < delay) { + return false; // No se realiza el fade + } + + // Actualizar el último tick + last_tick_ = current_tick; + + // Verificar que el tamaño mínimo de sub_palette_ sea adecuado + static constexpr int SUB_PALETTE_SIZE = 19; + if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < SUB_PALETTE_SIZE) { + throw std::runtime_error("Palette size is insufficient for fadePalette operation."); + } + + // Desplazar colores (pares e impares) + for (int i = 18; i > 1; --i) { + sub_palette_[i] = sub_palette_[i - 2]; + } + + // Ajustar el primer color + sub_palette_[1] = sub_palette_[0]; + + // Devolver si el índice 15 coincide con el índice 0 + return sub_palette_[15] == sub_palette_[0]; +} + +// Restaura la sub paleta a su estado original +void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); } // NOLINT(readability-convert-member-functions-to-static) diff --git a/source/core/rendering/surface.hpp b/source/core/rendering/surface.hpp new file mode 100644 index 0000000..b467a10 --- /dev/null +++ b/source/core/rendering/surface.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include + +#include // Para array +#include // Para default_delete, shared_ptr, __shared_pt... +#include // Para iota +#include // Para string +#include // Para move + +#include "utils/utils.hpp" // Para PaletteColor + +// Alias +using Palette = std::array; +using SubPalette = std::array; + +// Carga una paleta desde un archivo .gif +auto loadPalette(const std::string& file_path) -> Palette; + +// Carga una paleta desde un archivo .pal +auto readPalFile(const std::string& file_path) -> Palette; + +struct SurfaceData { + std::shared_ptr data; // Usa std::shared_ptr para gestión automática + float width; // Ancho de la imagen + float height; // Alto de la imagen + + // Constructor por defecto + SurfaceData() + : data(nullptr), + width(0), + height(0) {} + + // Constructor que inicializa dimensiones y asigna memoria + SurfaceData(float w, float h) + : data(std::shared_ptr(new Uint8[static_cast(w * h)](), std::default_delete())), + width(w), + height(h) {} + + // Constructor para inicializar directamente con datos + SurfaceData(float w, float h, std::shared_ptr pixels) + : data(std::move(pixels)), + width(w), + height(h) {} + + // Constructor de movimiento + SurfaceData(SurfaceData&& other) noexcept = default; + + // Operador de movimiento + auto operator=(SurfaceData&& other) noexcept -> SurfaceData& = default; + + // Evita copias accidentales + SurfaceData(const SurfaceData&) = delete; + auto operator=(const SurfaceData&) -> SurfaceData& = delete; +}; + +class Surface { + private: + std::shared_ptr surface_data_; // Datos a dibujar + Palette palette_; // Paleta para volcar la SurfaceData a una Textura + SubPalette sub_palette_; // Paleta para reindexar colores + int transparent_color_; // Indice de la paleta que se omite en la copia de datos + + public: + // Constructor + Surface(int w, int h); + explicit Surface(const std::string& file_path); + + // Destructor + ~Surface() = default; + + // Carga una SurfaceData desde un archivo + static auto loadSurface(const std::string& file_path) -> SurfaceData; + + // Carga una paleta desde un archivo + void loadPalette(const std::string& file_path); + void loadPalette(const Palette& palette); + + // Copia una región de la SurfaceData de origen a la SurfaceData de destino + void render(float dx, float dy, float sx, float sy, float w, float h); + void render(int x, int y, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); + void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); + + // Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro + void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const; + + // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) + void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr) const; + + // Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color) + void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr) const; + + // Establece un color en la paleta + void setColor(int index, Uint32 color); + + // Rellena la SurfaceData con un color + void clear(Uint8 color); + + // Vuelca la SurfaceData a una textura + void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture); + void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect); + + // Realiza un efecto de fundido en las paletas + auto fadePalette() -> bool; + auto fadeSubPalette(Uint32 delay = 0) -> bool; + + // Restaura la sub paleta a su estado original + void resetSubPalette(); + + // Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture) + void toARGBBuffer(Uint32* buffer) const; + + // Pone un pixel en la SurfaceData + void putPixel(int x, int y, Uint8 color); + + // Obtiene el color de un pixel de la surface_data + auto getPixel(int x, int y) -> Uint8; + + // Dibuja un rectangulo relleno + void fillRect(const SDL_FRect* rect, Uint8 color); + + // Dibuja el borde de un rectangulo + void drawRectBorder(const SDL_FRect* rect, Uint8 color); + + // Dibuja una linea + void drawLine(float x1, float y1, float x2, float y2, Uint8 color); + + // Metodos para gestionar surface_data_ + [[nodiscard]] auto getSurfaceData() const -> std::shared_ptr { return surface_data_; } + void setSurfaceData(std::shared_ptr new_data) { surface_data_ = std::move(new_data); } + + // Obtien ancho y alto + [[nodiscard]] auto getWidth() const -> float { return surface_data_->width; } + [[nodiscard]] auto getHeight() const -> float { return surface_data_->height; } + + // Color transparente + [[nodiscard]] auto getTransparentColor() const -> Uint8 { return transparent_color_; } + void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; } + + // Paleta + void setPalette(const std::array& palette) { palette_ = palette; } + + // Inicializa la sub paleta + static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); } + + private: + // Helper para calcular coordenadas con flip + static void calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y); + + // Helper para copiar un pixel si no es transparente + void copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const; +}; diff --git a/source/core/rendering/text.cpp b/source/core/rendering/text.cpp new file mode 100644 index 0000000..c68a462 --- /dev/null +++ b/source/core/rendering/text.cpp @@ -0,0 +1,311 @@ +#include "core/rendering/text.hpp" + +#include + +#include // Para size_t +#include // Para cerr +#include // Para istringstream +#include // Para runtime_error + +#include "core/rendering/screen.hpp" // Para Screen +#include "core/rendering/surface.hpp" // Para Surface +#include "core/rendering/sprites/sprite.hpp" // Para Sprite + +#include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots + +// Extrae el siguiente codepoint UTF-8 de la cadena, avanzando 'pos' al byte siguiente +auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t { // NOLINT(readability-convert-member-functions-to-static) + auto c = static_cast(s[pos]); + uint32_t cp = 0; + size_t extra = 0; + + if (c < 0x80) { + cp = c; + extra = 0; + } else if (c < 0xC0) { + pos++; + return 0xFFFD; + } // byte de continuación suelto + else if (c < 0xE0) { + cp = c & 0x1F; + extra = 1; + } else if (c < 0xF0) { + cp = c & 0x0F; + extra = 2; + } else if (c < 0xF8) { + cp = c & 0x07; + extra = 3; + } else { + pos++; + return 0xFFFD; + } + + pos++; + for (size_t i = 0; i < extra && pos < s.size(); ++i, ++pos) { + auto cb = static_cast(s[pos]); + if ((cb & 0xC0) != 0x80) { return 0xFFFD; } + cp = (cp << 6) | (cb & 0x3F); + } + return cp; +} + +// Convierte un codepoint Unicode a una cadena UTF-8 +auto Text::codepointToUtf8(uint32_t cp) -> std::string { // NOLINT(readability-convert-member-functions-to-static) + std::string result; + if (cp < 0x80) { + result += static_cast(cp); + } else if (cp < 0x800) { + result += static_cast(0xC0 | (cp >> 6)); + result += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + result += static_cast(0xE0 | (cp >> 12)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } else { + result += static_cast(0xF0 | (cp >> 18)); + result += static_cast(0x80 | ((cp >> 12) & 0x3F)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } + return result; +} + +// Carga un fichero de definición de fuente .fnt +// Formato: líneas "clave valor", comentarios con #, gliphos como "codepoint ancho" +auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr { // NOLINT(readability-convert-member-functions-to-static) + auto tf = std::make_shared(); + + auto file_data = loadFileBytes(file_path); + if (file_data.empty()) { + std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n'; + throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path)); + } + + std::string content(file_data.begin(), file_data.end()); + std::istringstream stream(content); + std::string line; + int glyph_index = 0; + + while (std::getline(stream, line)) { + if (!line.empty() && line.back() == '\r') { line.pop_back(); } + if (line.empty() || line[0] == '#') { continue; } + + std::istringstream ls(line); + std::string key; + ls >> key; + + if (key == "box_width") { + ls >> tf->box_width; + } else if (key == "box_height") { + ls >> tf->box_height; + } else if (key == "columns") { + ls >> tf->columns; + } else if (key == "cell_spacing") { + ls >> tf->cell_spacing; + } else if (key == "row_spacing") { + ls >> tf->row_spacing; + } else { + // Línea de glifo: codepoint_decimal ancho_visual + uint32_t codepoint = 0; + int width = 0; + try { + codepoint = static_cast(std::stoul(key)); + ls >> width; + } catch (...) { + continue; // línea mal formateada, ignorar + } + Offset off{}; + const int ROW_SP = tf->row_spacing > 0 ? tf->row_spacing : tf->cell_spacing; + off.x = ((glyph_index % tf->columns) * (tf->box_width + tf->cell_spacing)) + tf->cell_spacing; + off.y = ((glyph_index / tf->columns) * (tf->box_height + ROW_SP)) + tf->cell_spacing; + off.w = width; + tf->offset[codepoint] = off; + ++glyph_index; + } + } + + printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]"); + return tf; +} + +// Constructor desde fichero +Text::Text(const std::shared_ptr& surface, const std::string& text_file) { + auto tf = loadTextFile(text_file); + + box_height_ = tf->box_height; + box_width_ = tf->box_width; + offset_ = tf->offset; + + sprite_ = std::make_unique(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast(box_width_), .h = static_cast(box_height_)}); +} + +// Constructor desde estructura precargada +Text::Text(const std::shared_ptr& surface, const std::shared_ptr& text_file) + : sprite_(std::make_unique(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast(text_file->box_width), .h = static_cast(text_file->box_height)})), + box_width_(text_file->box_width), + box_height_(text_file->box_height), + offset_(text_file->offset) { +} + +// Escribe texto en pantalla +void Text::write(int x, int y, const std::string& text, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static) + int shift = 0; + int glyphs_done = 0; + size_t pos = 0; + + sprite_->setY(y); + while (pos < text.size()) { + if (lenght != -1 && glyphs_done >= lenght) { break; } + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + sprite_->setClip(it->second.x, it->second.y, box_width_, box_height_); + sprite_->setX(x + shift); + sprite_->render(1, 15); + shift += it->second.w + kerning; + } + ++glyphs_done; + } +} + +// Escribe el texto en una surface +auto Text::writeToSurface(const std::string& text, int zoom, int kerning) -> std::shared_ptr { // NOLINT(readability-make-member-function-const) + auto width = length(text, kerning) * zoom; + auto height = box_height_ * zoom; + auto surface = std::make_shared(width, height); + auto previuos_renderer = Screen::get()->getRendererSurface(); + Screen::get()->setRendererSurface(surface); + surface->clear(stringToColor("transparent")); + write(0, 0, text, kerning); + Screen::get()->setRendererSurface(previuos_renderer); + + return surface; +} + +// Escribe el texto con extras en una surface +auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) -> std::shared_ptr { // NOLINT(readability-make-member-function-const) + auto width = Text::length(text, kerning) + shadow_distance; + auto height = box_height_ + shadow_distance; + auto surface = std::make_shared(width, height); + auto previuos_renderer = Screen::get()->getRendererSurface(); + Screen::get()->setRendererSurface(surface); + surface->clear(stringToColor("transparent")); + writeDX(flags, 0, 0, text, kerning, text_color, shadow_distance, shadow_color, lenght); + Screen::get()->setRendererSurface(previuos_renderer); + + return surface; +} + +// Escribe el texto con colores +void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static) + int shift = 0; + int glyphs_done = 0; + size_t pos = 0; + + sprite_->setY(y); + while (pos < text.size()) { + if (lenght != -1 && glyphs_done >= lenght) { break; } + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + sprite_->setClip(it->second.x, it->second.y, box_width_, box_height_); + sprite_->setX(x + shift); + sprite_->render(1, color); + shift += it->second.w + kerning; + } + ++glyphs_done; + } +} + +// Escribe el texto con sombra +void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int lenght) { + writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, lenght); + write(x, y, text, kerning, lenght); +} + +// Escribe el texto centrado en un punto x +void Text::writeCentered(int x, int y, const std::string& text, int kerning, int lenght) { + x -= (Text::length(text, kerning) / 2); + write(x, y, text, kerning, lenght); +} + +// Escribe texto con extras +void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) { // NOLINT(readability-convert-member-functions-to-static) + const auto CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG); + const auto SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG); + const auto COLORED = ((flags & COLOR_FLAG) == COLOR_FLAG); + const auto STROKED = ((flags & STROKE_FLAG) == STROKE_FLAG); + + if (CENTERED) { + x -= (Text::length(text, kerning) / 2); + } + + if (SHADOWED) { + writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght); + } + + if (STROKED) { + const int MAX_DIST = static_cast(shadow_distance); + for (int dist = 1; dist <= MAX_DIST; ++dist) { + for (int dy = -dist; dy <= dist; ++dy) { + for (int dx = -dist; dx <= dist; ++dx) { + writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght); + } + } + } + } + + if (COLORED) { + writeColored(x, y, text, text_color, kerning, lenght); + } else { + writeColored(x, y, text, text_color, kerning, lenght); + } +} + +// Obtiene la longitud en pixels de una cadena UTF-8 +auto Text::length(const std::string& text, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static) + int shift = 0; + size_t pos = 0; + + while (pos < text.size()) { + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + shift += it->second.w + kerning; + } + } + + return shift > 0 ? shift - kerning : 0; +} + +// Devuelve el ancho en pixels de un glifo dado su codepoint Unicode +auto Text::glyphWidth(uint32_t codepoint, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static) + auto it = offset_.find(codepoint); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { return it->second.w + kerning; } + return 0; +} + +// Devuelve el clip rect (región en el bitmap) de un glifo dado su codepoint +auto Text::getGlyphClip(uint32_t codepoint) const -> SDL_FRect { + auto it = offset_.find(codepoint); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it == offset_.end()) { return {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; } + return {.x = static_cast(it->second.x), + .y = static_cast(it->second.y), + .w = static_cast(box_width_), + .h = static_cast(box_height_)}; +} + +// Devuelve el tamaño de la caja de cada caracter +auto Text::getCharacterSize() const -> int { + return box_width_; +} + +// Establece si se usa un tamaño fijo de letra +void Text::setFixedWidth(bool value) { + fixed_width_ = value; +} diff --git a/source/core/rendering/text.hpp b/source/core/rendering/text.hpp new file mode 100644 index 0000000..66f38e2 --- /dev/null +++ b/source/core/rendering/text.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include // Para shared_ptr, unique_ptr +#include // Para string +#include // Para unordered_map + +#include "core/rendering/sprites/sprite.hpp" // Para Sprite +class Surface; // Forward declaration + +// Clase texto. Pinta texto en pantalla a partir de un bitmap con soporte UTF-8 +class Text { + public: + // Tipos anidados públicos + struct Offset { + int x{0}, y{0}, w{0}; + }; + + struct File { + int box_width{0}; // Anchura de la caja de cada caracter en el png + int box_height{0}; // Altura de la caja de cada caracter en el png + int columns{16}; // Número de columnas en el bitmap + int cell_spacing{0}; // Píxeles de separación entre columnas (y borde izquierdo/superior) + int row_spacing{0}; // Píxeles de separación entre filas (si difiere de cell_spacing) + std::unordered_map offset; // Posición y ancho de cada glifo (clave: codepoint Unicode) + }; + + // Constructor + Text(const std::shared_ptr& surface, const std::string& text_file); + Text(const std::shared_ptr& surface, const std::shared_ptr& text_file); + + // Destructor + ~Text() = default; + + // Constantes de flags para writeDX + static constexpr int COLOR_FLAG = 1; + static constexpr int SHADOW_FLAG = 2; + static constexpr int CENTER_FLAG = 4; + static constexpr int STROKE_FLAG = 8; + + void write(int x, int y, const std::string& text, int kerning = 1, int lenght = -1); // Escribe el texto en pantalla + void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int lenght = -1); // Escribe el texto con colores + void writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance = 1, int kerning = 1, int lenght = -1); // Escribe el texto con sombra + void writeCentered(int x, int y, const std::string& text, int kerning = 1, int lenght = -1); // Escribe el texto centrado en un punto x + void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 text_color = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1); // Escribe texto con extras + + auto writeToSurface(const std::string& text, int zoom = 1, int kerning = 1) -> std::shared_ptr; // Escribe el texto en una textura + auto writeDXToSurface(Uint8 flags, const std::string& text, int kerning = 1, Uint8 text_color = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1) -> std::shared_ptr; // Escribe el texto con extras en una textura + + [[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int; // Obtiene la longitud en pixels de una cadena + [[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño del caracter + [[nodiscard]] auto glyphWidth(uint32_t codepoint, int kerning = 0) const -> int; // Devuelve el ancho en pixels de un glifo + [[nodiscard]] auto getGlyphClip(uint32_t codepoint) const -> SDL_FRect; // Devuelve el clip rect del glifo + [[nodiscard]] auto getSprite() const -> Sprite* { return sprite_.get(); } // Acceso al sprite interno + + void setFixedWidth(bool value); // Establece si se usa un tamaño fijo de letra + + static auto loadTextFile(const std::string& file_path) -> std::shared_ptr; // Carga un fichero de definición de fuente .fnt + static auto codepointToUtf8(uint32_t cp) -> std::string; // Convierte un codepoint Unicode a string UTF-8 + static auto nextCodepoint(const std::string& s, size_t& pos) -> uint32_t; // Extrae el siguiente codepoint UTF-8 + + private: + // Objetos y punteros + std::unique_ptr sprite_ = nullptr; // Objeto con los graficos para el texto + + // Variables + int box_width_ = 0; // Anchura de la caja de cada caracter en el png + int box_height_ = 0; // Altura de la caja de cada caracter en el png + bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija + std::unordered_map offset_; // Posición y ancho de cada glifo (clave: codepoint Unicode) +}; diff --git a/source/external/fkyaml_node.hpp b/source/external/fkyaml_node.hpp new file mode 100644 index 0000000..05e7957 --- /dev/null +++ b/source/external/fkyaml_node.hpp @@ -0,0 +1,14726 @@ +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_NODE_HPP +#define FK_YAML_NODE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP +#define FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +// Check version definitions if already defined. +#if defined(FK_YAML_MAJOR_VERSION) && defined(FK_YAML_MINOR_VERSION) && defined(FK_YAML_PATCH_VERSION) +#if FK_YAML_MAJOR_VERSION != 0 || FK_YAML_MINOR_VERSION != 4 || FK_YAML_PATCH_VERSION != 2 +#warning Already included a different version of the fkYAML library! +#else +// define macros to skip defining macros down below. +#define FK_YAML_VERCHECK_SUCCEEDED +#endif +#endif + +#ifndef FK_YAML_VERCHECK_SUCCEEDED + +#define FK_YAML_MAJOR_VERSION 0 +#define FK_YAML_MINOR_VERSION 4 +#define FK_YAML_PATCH_VERSION 2 + +#define FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch) v##major##_##minor##_##patch + +#define FK_YAML_NAMESPACE_VERSION_CONCAT(major, minor, patch) FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch) + +#define FK_YAML_NAMESPACE_VERSION \ + FK_YAML_NAMESPACE_VERSION_CONCAT(FK_YAML_MAJOR_VERSION, FK_YAML_MINOR_VERSION, FK_YAML_PATCH_VERSION) + +#define FK_YAML_NAMESPACE_BEGIN \ + namespace fkyaml { \ + inline namespace FK_YAML_NAMESPACE_VERSION { + +#define FK_YAML_NAMESPACE_END \ + } /* inline namespace FK_YAML_NAMESPACE_VERSION */ \ + } // namespace fkyaml + +#define FK_YAML_DETAIL_NAMESPACE_BEGIN \ + FK_YAML_NAMESPACE_BEGIN \ + namespace detail { + +#define FK_YAML_DETAIL_NAMESPACE_END \ + } /* namespace detail */ \ + FK_YAML_NAMESPACE_END + +#endif // !defined(FK_YAML_VERCHECK_SUCCEEDED) + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP +#define FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP + +// This file is assumed to be included only by version_macros.hpp file. +// To avoid redundant inclusion, do not include version_macros.hpp file as the other files do. + +// With the MSVC compilers, the value of __cplusplus is by default always "199611L"(C++98). +// To avoid that, the library instead references _MSVC_LANG which is always set a correct value. +// See https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ for more details. +#if defined(_MSVC_LANG) && !defined(__clang__) +#define FK_YAML_CPLUSPLUS _MSVC_LANG +#else +#define FK_YAML_CPLUSPLUS __cplusplus +#endif + +// C++ language standard detection +// Skip detection if the definitions listed below already exist. +#if !defined(FK_YAML_HAS_CXX_23) && !defined(FK_YAML_HAS_CXX_20) && !defined(FK_YAML_HAS_CXX_17) && \ + !defined(FK_YAML_HAS_CXX_14) && !defined(FK_YAML_CXX_11) +#if FK_YAML_CPLUSPLUS >= 202302L +#define FK_YAML_HAS_CXX_23 +#define FK_YAML_HAS_CXX_20 +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 202002L +#define FK_YAML_HAS_CXX_20 +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 201703L +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 201402L +#define FK_YAML_HAS_CXX_14 +#endif + +// C++11 is the minimum required version of the fkYAML library. +#define FK_YAML_HAS_CXX_11 +#endif + +// switch usage of the deprecated attribute. [[deprecated]] is available since C++14. +#if defined(FK_YAML_HAS_CXX_14) +#define FK_YAML_DEPRECATED(msg) [[deprecated(msg)]] +#else +#if defined(_MSC_VER) +#define FK_YAML_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) || defined(__clang__) +#define FK_YAML_DEPRECATED(msg) __attribute__((deprecated(msg))) +#else +#define FK_YAML_DEPRECATED(msg) +#endif +#endif + +// switch usage of inline variables which have been available since C++17. +#if defined(FK_YAML_HAS_CXX_17) +#define FK_YAML_INLINE_VAR inline +#else +#define FK_YAML_INLINE_VAR +#endif + +// switch usage of constexpr keyword depending on active C++ standard. +#if defined(FK_YAML_HAS_CXX_17) +#define FK_YAML_CXX17_CONSTEXPR constexpr +#else +#define FK_YAML_CXX17_CONSTEXPR +#endif + +// Detect __has_* macros. +// The following macros replace redundant `defined(__has_*) && __has_*(...)`. + +#ifdef __has_include +#define FK_YAML_HAS_INCLUDE(header) __has_include(header) +#else +#define FK_YAML_HAS_INCLUDE(header) (0) +#endif + +#ifdef __has_builtin +#define FK_YAML_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else +#define FK_YAML_HAS_BUILTIN(builtin) (0) +#endif + +#ifdef __has_cpp_attribute +#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) (0) +#endif + +#ifdef __has_feature +#define FK_YAML_HAS_FEATURE(feat) __has_feature(feat) +#else +#define FK_YAML_HAS_FEATURE(feat) (0) +#endif + +// switch usage of the no_sanitize attribute only when Clang sanitizer is active. +#if defined(__clang__) && FK_YAML_HAS_FEATURE(address_sanitizer) +#define FK_YAML_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__))) +#else +#define FK_YAML_NO_SANITIZE(...) +#endif + +#if FK_YAML_HAS_INCLUDE() +// is available since C++20 +#include +#endif + +// +// C++ feature detections +// + +// switch usages of the std::to_chars()/std::from_chars() functions which have been available since C++17. +#if defined(FK_YAML_HAS_CXX_17) && defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L +#define FK_YAML_HAS_TO_CHARS (1) +#else +#define FK_YAML_HAS_TO_CHARS (0) +#endif + +// switch usage of char8_t which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +#define FK_YAML_HAS_CHAR8_T (1) +#else +#define FK_YAML_HAS_CHAR8_T (0) +#endif + +// +// utility macros +// + +// switch usage of [[likely]] C++ attribute which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define FK_YAML_LIKELY(expr) (!!(expr)) [[likely]] +#elif FK_YAML_HAS_BUILTIN(__builtin_expect) +#define FK_YAML_LIKELY(expr) (__builtin_expect(!!(expr), 1)) +#else +#define FK_YAML_LIKELY(expr) (!!(expr)) +#endif + +// switch usage of [[unlikely]] C++ attribute which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(unlikely) >= 201803L +#define FK_YAML_UNLIKELY(expr) (!!(expr)) [[unlikely]] +#elif FK_YAML_HAS_BUILTIN(__builtin_expect) +#define FK_YAML_UNLIKELY(expr) (__builtin_expect(!!(expr), 0)) +#else +#define FK_YAML_UNLIKELY(expr) (!!(expr)) +#endif + +#endif /* FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP */ + + +#endif /* FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP */ + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_ASSERT_HPP +#define FK_YAML_DETAIL_ASSERT_HPP + +// if FK_YAML_ASSERT is not user-defined. apply the default assert impl. +#ifndef FK_YAML_ASSERT +#ifndef NDEBUG +#include +#define FK_YAML_ASSERT(x) assert(x) +#else +#define FK_YAML_ASSERT(x) +#endif +#endif + +#endif /* FK_YAML_DETAIL_ASSERT_HPP */ + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP +#define FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP + +#include +#include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_NODE_TRAITS_HPP +#define FK_YAML_DETAIL_META_NODE_TRAITS_HPP + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_DETECT_HPP +#define FK_YAML_DETAIL_META_DETECT_HPP + +#include +#include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP +#define FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP + +#include +#include + +// #include + + +#ifdef FK_YAML_HAS_CXX_14 +#include +#endif + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// For contributors: +// This file is for supplementing future C++ STL implementations to utilize some useful features +// implemented in C++14 or better. +// This file is needed to keep the fkYAML library requirement to C++11. +// **DO NOT** implement features which are not included any version of STL in this file. +// Such implementations must be in the type_traits.hpp file. +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef FK_YAML_HAS_CXX_14 + +/// @brief An alias template for std::add_pointer::type with C++11. +/// @note std::add_pointer_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/add_pointer +/// @tparam T A type to be added a pointer. +template +using add_pointer_t = typename std::add_pointer::type; + +/// @brief An alias template for std::enable_if::type with C++11. +/// @note std::enable_if_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/enable_if +/// @tparam Condition A condition tested at compile time. +/// @tparam T The type defined only if Condition is true. +template +using enable_if_t = typename std::enable_if::type; + +/// @brief A simple implementation to use std::is_null_pointer with C++11. +/// @note std::is_null_pointer is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/is_null_pointer +/// @tparam T The type to be checked if it's equal to std::nullptr_t. +template +struct is_null_pointer : std::is_same::type> {}; + +/// @brief An alias template for std::remove_cv::type with C++11. +/// @note std::remove_cv_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_cv +/// @tparam T A type from which const-volatile qualifiers are removed. +template +using remove_cv_t = typename std::remove_cv::type; + +/// @brief An alias template for std::remove_pointer::type with C++11. +/// @note std::remove_pointer_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_pointer +/// @tparam T A type from which a pointer is removed. +template +using remove_pointer_t = typename std::remove_pointer::type; + +/// @brief An alias template for std::remove_reference::type with C++11. +/// @note std::remove_reference_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_reference +/// @tparam T A type from which a reference is removed. +template +using remove_reference_t = typename std::remove_reference::type; + +template +struct integer_sequence { + using value_type = T; + static constexpr std::size_t size() noexcept { + return sizeof...(I); + } +}; + +#if !FK_YAML_HAS_BUILTIN(__make_integer_seq) && !FK_YAML_HAS_BUILTIN(__integer_pack) + +namespace make_int_seq_impl { + +template +struct merger; + +template +struct merger, integer_sequence> { + using type = integer_sequence; +}; + +template +struct generator { + using type = + typename merger::type, typename generator::type>::type; +}; + +template +struct generator { + using type = integer_sequence; +}; + +template +struct generator { + using type = integer_sequence; +}; + +} // namespace make_int_seq_impl + +#endif + +template +using make_integer_sequence +#if FK_YAML_HAS_BUILTIN(__make_integer_seq) + // clang defines built-in __make_integer_seq to generate an integer sequence. + = __make_integer_seq; +#elif FK_YAML_HAS_BUILTIN(__integer_pack) + // GCC or other compilers may implement built-in __integer_pack to generate an + // integer sequence. + = integer_sequence; +#else + // fallback to the library implementation of make_integer_sequence. + = typename make_int_seq_impl::generator::type; +#endif + +template +using index_sequence = integer_sequence; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = make_index_sequence; + +#else // !defined(FK_YAML_HAS_CXX_14) + +using std::add_pointer_t; +using std::enable_if_t; +using std::index_sequence; +using std::index_sequence_for; +using std::integer_sequence; +using std::is_null_pointer; +using std::make_index_sequence; +using std::make_integer_sequence; +using std::remove_cv_t; +using std::remove_pointer_t; +using std::remove_reference_t; + +#endif // !defined(FK_YAML_HAS_CXX_14) + +#ifndef FK_YAML_HAS_CXX_17 + +/// @brief A simple implementation to use std::bool_constant with C++11/C++14. +/// @tparam Val +template +using bool_constant = std::integral_constant; + +/// @brief A simple implementation to use std::void_t with C++11/C++14. +/// @note +/// std::conjunction is available since C++17. +/// This is applied when no traits are specified as inputs. +/// @sa https://en.cppreference.com/w/cpp/types/conjunction +/// @tparam Traits Type traits to be checked if their ::value are all true. +template +struct conjunction : std::true_type {}; + +/// @brief A partial specialization of conjunction if only one Trait is given. +/// @tparam Trait Type trait to be checked if its ::value is true. +template +struct conjunction : Trait {}; + +/// @brief A partial specialization of conjunction if more than one traits are given. +/// @tparam First The first type trait to be checked if its ::value is true. +/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is true. +template +struct conjunction : std::conditional, First>::type {}; + +/// @brief A simple implementation to use std::disjunction with C++11/C++14. +/// @note +/// std::disjunction is available since C++17. +/// This is applied when no traits are specified as inputs. +/// @sa https://en.cppreference.com/w/cpp/types/disjunction +/// @tparam Traits Type traits to be checked if at least one of their ::value is true. +template +struct disjunction : std::false_type {}; + +/// @brief A partial specialization of disjunction if only one Trait is given. +/// @tparam Trait Type trait to be checked if its ::value is true. +template +struct disjunction : Trait {}; + +/// @brief A partial specialization of disjunction if more than one traits are given. +/// @tparam First The first type trait to be checked if its ::value is true. +/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is false. +template +struct disjunction : std::conditional>::type {}; + +/// @brief A simple implementation to use std::negation with C++11/C++14. +/// @note std::negation is available since C++17. +/// @sa https://en.cppreference.com/w/cpp/types/negation +/// @tparam Trait Type trait whose ::value is negated. +template +struct negation : std::integral_constant {}; + +/// @brief A helper for void_t. +/// @tparam Types Any types to be transformed to void type. +template +struct make_void { + using type = void; +}; + +/// @brief A simple implementation to use std::void_t with C++11/C++14. +/// @note std::void_t is available since C++17. +/// @sa https://en.cppreference.com/w/cpp/types/void_t +/// @tparam Types Any types to be transformed to void type. +template +using void_t = typename make_void::type; + +#else // !defined(FK_YAML_HAS_CXX_17) + +using std::bool_constant; +using std::conjunction; +using std::disjunction; +using std::negation; +using std::void_t; + +#endif // !defined(FK_YAML_HAS_CXX_17) + +#ifndef FK_YAML_HAS_CXX_20 + +/// @brief A simple implementation to use std::remove_cvref_t with C++11/C++14/C++17. +/// @note std::remove_cvref & std::remove_cvref_t are available since C++20. +/// @sa https://en.cppreference.com/w/cpp/types/remove_cvref +/// @tparam T A type from which cv-qualifiers and reference are removed. +template +using remove_cvref_t = typename std::remove_cv::type>::type; + +#else + +using std::remove_cvref_t; + +#endif + +/// @brief A wrapper function to call std::unreachable() (since C++23) or similar compiler specific extensions. +/// @note This function is implemented only for better code optimization against dead code and thus excluded from +/// coverage report. +// LCOV_EXCL_START +[[noreturn]] inline void unreachable() { + // use compiler specific extensions if possible. + // undefined behavior should be raised by an empty function with noreturn attribute. + +#if defined(FK_YAML_HAS_CXX_23) || (defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L) + std::unreachable(); +#elif defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else + __builtin_unreachable(); +#endif +} +// LCOV_EXCL_STOP + +FK_YAML_DETAIL_NAMESPACE_END + +#endif /* FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP */ + + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +/// @brief A dummy struct to represent detection failure. +struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(const nonesuch&) = delete; + nonesuch(nonesuch&&) = delete; + nonesuch& operator=(const nonesuch&) = delete; + nonesuch& operator=(nonesuch&&) = delete; +}; + +/// @brief namespace to implement detector type traits +namespace detector_impl { + +/// @brief A helper for general type detection. +/// @tparam Default A type to represent detection failure. +/// @tparam AlwaysVoid This must be void type. +/// @tparam Op A type for desired operation type. +/// @tparam Args Argument types passed to desired operation. +template class Op, typename... Args> +struct detector : std::false_type { + /// @brief A type which represents detection failure. + using type = Default; +}; + +/// @brief A partial specialization of detector if desired operation type is found. +/// @tparam Default A type to represent detection failure. +/// @tparam Op A type for desired operation type. +/// @tparam Args Argument types passed to desired operation. +template class Op, typename... Args> +struct detector>, Op, Args...> : std::true_type { + /// @brief A detected type. + using type = Op; +}; + +} // namespace detector_impl + +/// @brief Type traits to detect Op operation with Args argument types +/// @tparam Op A desired operation type. +/// @tparam Args Argument types passed to desired operation. +template