/* Copyright 2018 flyinghead This file is part of reicast. reicast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. reicast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with reicast. If not, see . */ #include "CustomTexture.h" #include "cfg/cfg.h" #include "oslib/directory.h" #include #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_JPEG #define STBI_ONLY_PNG #include #define STB_IMAGE_WRITE_IMPLEMENTATION #include CustomTexture custom_texture; void CustomTexture::LoaderThread() { LoadMap(); while (initialized) { BaseTextureCacheData *texture; do { texture = nullptr; { std::unique_lock lock(work_queue_mutex); if (!work_queue.empty()) { texture = work_queue.back(); work_queue.pop_back(); } } if (texture != nullptr) { texture->ComputeHash(); if (texture->custom_image_data != nullptr) { free(texture->custom_image_data); texture->custom_image_data = nullptr; } if (!texture->dirty) { int width, height; u8 *image_data = LoadCustomTexture(texture->texture_hash, width, height); if (image_data == nullptr) { image_data = LoadCustomTexture(texture->old_texture_hash, width, height); } if (image_data != nullptr) { texture->custom_width = width; texture->custom_height = height; texture->custom_image_data = image_data; } } texture->custom_load_in_progress--; } } while (texture != nullptr); wakeup_thread.Wait(); } } std::string CustomTexture::GetGameId() { std::string game_id(cfgGetGameId()); const size_t str_end = game_id.find_last_not_of(' '); if (str_end == std::string::npos) return ""; game_id = game_id.substr(0, str_end + 1); std::replace(game_id.begin(), game_id.end(), ' ', '_'); return game_id; } bool CustomTexture::Init() { if (!initialized) { initialized = true; std::string game_id = GetGameId(); if (game_id.length() > 0) { textures_path = get_readonly_data_path("textures/" + game_id) + "/"; DIR *dir = flycast::opendir(textures_path.c_str()); if (dir != nullptr) { INFO_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str()); custom_textures_available = true; closedir(dir); loader_thread.Start(); } } } return custom_textures_available; } void CustomTexture::Terminate() { if (initialized) { initialized = false; { std::unique_lock lock(work_queue_mutex); work_queue.clear(); } wakeup_thread.Set(); loader_thread.WaitToEnd(); texture_map.clear(); } } u8* CustomTexture::LoadCustomTexture(u32 hash, int& width, int& height) { auto it = texture_map.find(hash); if (it == texture_map.end()) return nullptr; FILE *file = nowide::fopen(it->second.c_str(), "rb"); if (file == nullptr) return nullptr; int n; stbi_set_flip_vertically_on_load(1); u8 *imgData = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); std::fclose(file); return imgData; } void CustomTexture::LoadCustomTextureAsync(BaseTextureCacheData *texture_data) { if (!Init()) return; texture_data->custom_load_in_progress++; { std::unique_lock lock(work_queue_mutex); work_queue.insert(work_queue.begin(), texture_data); } wakeup_thread.Set(); } void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer) { std::string base_dump_dir = get_writable_data_path("texdump/"); if (!file_exists(base_dump_dir)) make_directory(base_dump_dir); std::string game_id = GetGameId(); if (game_id.length() == 0) return; base_dump_dir += game_id + "/"; if (!file_exists(base_dump_dir)) make_directory(base_dump_dir); std::stringstream path; path << base_dump_dir << std::hex << hash << ".png"; u16 *src = (u16 *)src_buffer; u8 *dst_buffer = (u8 *)malloc(w * h * 4); // 32-bit per pixel u8 *dst = dst_buffer; for (int y = 0; y < h; y++) { switch (textype) { case TextureType::_4444: for (int x = 0; x < w; x++) { *dst++ = ((*src >> 12) & 0xF) << 4; *dst++ = ((*src >> 8) & 0xF) << 4; *dst++ = ((*src >> 4) & 0xF) << 4; *dst++ = (*src & 0xF) << 4; src++; } break; case TextureType::_565: for (int x = 0; x < w; x++) { *dst++ = ((*src >> 11) & 0x1F) << 3; *dst++ = ((*src >> 5) & 0x3F) << 2; *dst++ = (*src & 0x1F) << 3; *dst++ = 255; src++; } break; case TextureType::_5551: for (int x = 0; x < w; x++) { *dst++ = ((*src >> 11) & 0x1F) << 3; *dst++ = ((*src >> 6) & 0x1F) << 3; *dst++ = ((*src >> 1) & 0x1F) << 3; *dst++ = (*src & 1) ? 255 : 0; src++; } break; case TextureType::_8888: for (int x = 0; x < w; x++) { *(u32 *)dst = *(u32 *)src; dst += 4; src += 2; } break; default: WARN_LOG(RENDERER, "dumpTexture: unsupported picture format %x", (u32)textype); free(dst_buffer); return; } } stbi_flip_vertically_on_write(1); stbi_write_png(path.str().c_str(), w, h, STBI_rgb_alpha, dst_buffer, 0); free(dst_buffer); } void CustomTexture::LoadMap() { texture_map.clear(); DirectoryTree tree(textures_path); for (const DirectoryTree::item& item : tree) { std::string extension = get_file_extension(item.name); if (extension != "jpg" && extension != "jpeg" && extension != "png") continue; std::string::size_type dotpos = item.name.find_last_of('.'); std::string basename = item.name.substr(0, dotpos); char *endptr; u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16); if (endptr - basename.c_str() < (ptrdiff_t)basename.length()) { INFO_LOG(RENDERER, "Invalid hash %s", basename.c_str()); continue; } texture_map[hash] = item.parentPath + "/" + item.name; } custom_textures_available = !texture_map.empty(); }