#include "core/jail/jdraw8.hpp" #include #include #include "core/resources/resource_cache.hpp" #include "core/resources/resource_helper.hpp" #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-but-set-variable" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #endif // NOLINTBEGIN(clang-analyzer-unix.Malloc): codi extern de tercers, no l'auditem. #include "external/gif.h" // NOLINTEND(clang-analyzer-unix.Malloc) #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif Jd8::Surface screen = nullptr; Jd8::Palette main_palette = nullptr; Uint32* pixel_data = nullptr; void Jd8::init() { screen = new Uint8[64000]{}; main_palette = new Color[256]{}; pixel_data = new Uint32[std::size_t{320} * 200]{}; } void Jd8::quit() { delete[] screen; delete[] main_palette; delete[] pixel_data; screen = nullptr; main_palette = nullptr; pixel_data = nullptr; } void Jd8::clearScreen(Uint8 color) { memset(screen, color, 64000); } auto Jd8::newSurface() -> Jd8::Surface { return new Uint8[64000]{}; } // Helper intern: deriva el basename d'una ruta per a buscar al Cache. static auto pathBasename(const char* file) -> std::string { std::string s = file; auto pos = s.find_last_of("/\\"); return pos == std::string::npos ? s : s.substr(pos + 1); } auto Jd8::loadSurface(const char* file) -> Jd8::Surface { // Prova primer el Resource::Cache. Si l'asset és precarregat, copiem // els 64KB des del cache (microsegons) i ens estalviem la decodificació // GIF. Mantenim el contracte de la funció: el caller rep un buffer // fresc que ha d'alliberar amb Jd8::freeSurface. if (Resource::Cache::get() != nullptr) { try { const auto& cached = Resource::Cache::get()->getSurfacePixels(pathBasename(file)); Jd8::Surface image = Jd8::newSurface(); memcpy(image, cached.data(), 64000); return image; } catch (const std::exception&) { // @INTENTIONAL: no està al cache (asset no llistat al manifest), fallback al loader. } } auto buffer = ResourceHelper::loadFile(file); unsigned short w; unsigned short h; Uint8* pixels = LoadGif(buffer.data(), &w, &h); if (pixels == nullptr) { printf("Unable to load bitmap: %s\n", SDL_GetError()); exit(1); } Jd8::Surface image = Jd8::newSurface(); memcpy(image, pixels, 64000); free(pixels); return image; } auto Jd8::loadPalette(const char* file) -> Jd8::Palette { // Sempre retorna un buffer de 256 colors reservat amb `new Color[256]` // — el caller és responsable d'alliberar-lo amb `delete[]` (o lliurar-ne // l'ownership a `Jd8::setScreenPalette`). auto* palette = new Color[256]; if (Resource::Cache::get() != nullptr) { try { const auto& cached = Resource::Cache::get()->getPaletteBytes(pathBasename(file)); memcpy(palette, cached.data(), 768); return palette; } catch (const std::exception&) { // @INTENTIONAL: no està al cache, fallback a lectura + LoadPalette. } } auto buffer = ResourceHelper::loadFile(file); Uint8* raw = LoadPalette(buffer.data()); // external malloc memcpy(palette, raw, 768); free(raw); return palette; } void Jd8::setScreenPalette(Jd8::Palette palette) { if (main_palette == palette) { return; } delete[] main_palette; main_palette = palette; } void Jd8::fillSquare(int ini, int height, Uint8 color) { const int offset = ini * 320; const int size = height * 320; memset(&screen[offset], color, size); } void Jd8::fillRect(int x, int y, int w, int h, Uint8 color) { if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x + w > 320) { w = 320 - x; } if (y + h > 200) { h = 200 - y; } if (w <= 0 || h <= 0) { return; } for (int row = y; row < y + h; ++row) { memset(&screen[x + (row * 320)], color, w); } } void Jd8::blit(const Uint8* surface) { memcpy(screen, surface, 64000); } void Jd8::blit(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh) { int src_pointer = sx + (sy * 320); int dst_pointer = x + (y * 320); for (int i = 0; i < sh; i++) { memcpy(&screen[dst_pointer], &surface[src_pointer], sw); src_pointer += 320; dst_pointer += 320; } } void Jd8::blitToSurface(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Jd8::Surface dest) { int src_pointer = sx + (sy * 320); int dst_pointer = x + (y * 320); for (int i = 0; i < sh; i++) { memcpy(&dest[dst_pointer], &surface[src_pointer], sw); src_pointer += 320; dst_pointer += 320; } } void Jd8::blitCK(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Uint8 colorkey) { int src_pointer = sx + (sy * 320); int dst_pointer = x + (y * 320); for (int j = 0; j < sh; j++) { for (int i = 0; i < sw; i++) { if (surface[src_pointer + i] != colorkey) { screen[dst_pointer + i] = surface[src_pointer + i]; } } src_pointer += 320; dst_pointer += 320; } } void Jd8::blitCKCut(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Uint8 colorkey) { int src_pointer = sx + (sy * 320); int dst_pointer = x + (y * 320); for (int j = 0; j < sh; j++) { for (int i = 0; i < sw; i++) { if (surface[src_pointer + i] != colorkey && (x + i >= 0) && (y + j >= 0) && (x + i < 320) && (y + j < 200)) { screen[dst_pointer + i] = surface[src_pointer + i]; } } src_pointer += 320; dst_pointer += 320; } } void Jd8::blitCKScroll(int y, const Uint8* surface, int sx, int sy, int sh, Uint8 colorkey) { int dst_pointer = y * 320; for (int j = sy; j < sy + sh; j++) { for (int i = 0; i < 320; i++) { int x = (i + sx) % 320; if (surface[x + (j * 320)] != colorkey) { screen[dst_pointer] = surface[x + (j * 320)]; } dst_pointer++; } } } void Jd8::blitCKToSurface(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Jd8::Surface dest, Uint8 colorkey) { int src_pointer = sx + (sy * 320); int dst_pointer = x + (y * 320); for (int j = 0; j < sh; j++) { for (int i = 0; i < sw; i++) { if (surface[src_pointer + i] != colorkey) { dest[dst_pointer + i] = surface[src_pointer + i]; } } src_pointer += 320; dst_pointer += 320; } } void Jd8::flip() { // Converteix el framebuffer indexat (paletted) a ARGB (pixel_data). // El Director crida aquesta funció després del tick de cada escena // per preparar el frame abans de presentar-lo. Ja no fa yield — // tot corre en un sol thread sense fibers des de Phase B.2. for (int x = 0; x < 320; x++) { for (int y = 0; y < 200; y++) { Uint32 color = 0xFF000000 + main_palette[screen[x + (y * 320)]].r + (main_palette[screen[x + (y * 320)]].g << 8) + (main_palette[screen[x + (y * 320)]].b << 16); pixel_data[x + (y * 320)] = color; } } } auto Jd8::getFramebuffer() -> Uint32* { return pixel_data; } void Jd8::freeSurface(Jd8::Surface surface) { // NOLINT(readability-non-const-parameter): allibera memòria, no pot ser const delete[] surface; } auto Jd8::getPixel(const Uint8* surface, int x, int y) -> Uint8 { return surface[x + (y * 320)]; } void Jd8::putPixel(Jd8::Surface surface, int x, int y, Uint8 pixel) { surface[x + (y * 320)] = pixel; } void Jd8::setPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b) { main_palette[index].r = r << 2; main_palette[index].g = g << 2; main_palette[index].b = b << 2; } // Màquina d'estats del fade. Evita que JD8_FadeOut/JD8_FadeToPal hagen de // mantindre whiles interns. Cada pas aplica un delta a la paleta activa i // el caller decideix quan fer Flip. namespace { enum class FadeType : std::uint8_t { None = 0, Out, ToPal, }; constexpr int FADE_STEPS = 32; FadeType fade_type = FadeType::None; Color fade_target[256]; int fade_step = 0; void apply_fade_step() { if (fade_type == FadeType::Out) { for (int i = 0; i < 256; i++) { main_palette[i].r = main_palette[i].r >= 8 ? main_palette[i].r - 8 : 0; main_palette[i].g = main_palette[i].g >= 8 ? main_palette[i].g - 8 : 0; main_palette[i].b = main_palette[i].b >= 8 ? main_palette[i].b - 8 : 0; } } else if (fade_type == FadeType::ToPal) { for (int i = 0; i < 256; i++) { main_palette[i].r = main_palette[i].r <= int(fade_target[i].r) - 8 ? main_palette[i].r + 8 : fade_target[i].r; main_palette[i].g = main_palette[i].g <= int(fade_target[i].g) - 8 ? main_palette[i].g + 8 : fade_target[i].g; main_palette[i].b = main_palette[i].b <= int(fade_target[i].b) - 8 ? main_palette[i].b + 8 : fade_target[i].b; } } } } // namespace void Jd8::fadeStartOut() { fade_type = FadeType::Out; fade_step = 0; } void Jd8::fadeStartToPal(const Color* pal) { fade_type = FadeType::ToPal; memcpy(fade_target, pal, sizeof(Color) * 256); fade_step = 0; } auto Jd8::fadeIsActive() -> bool { return fade_type != FadeType::None; } auto Jd8::fadeTickStep() -> bool { if (fade_type == FadeType::None) { return true; } apply_fade_step(); fade_step++; if (fade_step >= FADE_STEPS) { fade_type = FadeType::None; return true; } return false; } // Els shims bloquejants `JD8_FadeOut` i `JD8_FadeToPal` han estat // eliminats a Phase B.2: feien un bucle de 32 iteracions amb `Jd8::flip` // entre cada una que només funcionava mentre l'entorn tenia fibers i // `Jd8::flip` cedia el control al Director. Ara tot fade es fa tick a // tick via `scenes::PaletteFade` (que encapsula `Jd8::fadeStartOut` / // `Jd8::fadeStartToPal` + `Jd8::fadeTickStep`).