Host: 2x UI textures from hd_ui.pak (AI-upscaled 40250 UI art)
The phone magnifies the 600-high 40250 UI about 1.8x, so the 1x window art and icons look soft next to the oversampled text. scripts/build_hd_ui.py upscales every UI image the zh client loads (d:/ymir work/ui/, icon/, locale/zh/) with realesrgan-x4plus, reduces it to 2x and back-projects the residual so the board grain the network denoises away comes back; the host uploads a texture found in hd_ui.pak in place of the pack file. Layout, sizes and UVs stay the engine's 1x ones. MT_HD_UI=0 disables it; the pak (135 MB, Ymir art) is built into the client dir and synced to the phone, never committed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c5b709d38d
commit
01cc252a1d
@@ -7,8 +7,13 @@
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace mt_host {
|
||||
|
||||
@@ -124,9 +129,11 @@ mtimage::Image decode_texture_bytes(const std::uint8_t* data, std::size_t size)
|
||||
}
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
bool read_live_pack_texture(const std::string& vpath, std::vector<std::uint8_t>& bytes) {
|
||||
if (vpath.empty() || !mtpack40250::ready())
|
||||
return false;
|
||||
namespace {
|
||||
|
||||
// The spellings a 40250 texture name may be registered under in the packs (CEterPackManager::Get
|
||||
// accepts "d:/ymir work/..." and "ymir work/..." alike, in any case).
|
||||
std::vector<std::string> pack_name_candidates(const std::string& vpath) {
|
||||
std::string norm = vpath;
|
||||
for (char& ch : norm)
|
||||
if (ch == '\\') ch = '/';
|
||||
@@ -141,14 +148,81 @@ bool read_live_pack_texture(const std::string& vpath, std::vector<std::uint8_t>&
|
||||
ch = static_cast<char>(ch - 'A' + 'a');
|
||||
return s;
|
||||
};
|
||||
for (const std::string& candidate : {
|
||||
norm, stripped, "d:/" + stripped,
|
||||
lower(norm), lower(stripped), lower("d:/" + stripped)}) {
|
||||
return {norm, stripped, "d:/" + stripped, lower(norm), lower(stripped), lower("d:/" + stripped)};
|
||||
}
|
||||
|
||||
// hd_ui.pak (scripts/build_hd_ui.py): "MTHD", u32 version 1, u32 count, count x {u16 name length,
|
||||
// name, u32 offset, u32 size}, then PNG payloads. Names are lowercase pack paths.
|
||||
struct HdUiPak {
|
||||
std::FILE* file = nullptr;
|
||||
std::unordered_map<std::string, std::pair<std::uint32_t, std::uint32_t>> entries;
|
||||
};
|
||||
|
||||
std::uint32_t read_u32(const std::uint8_t* p) {
|
||||
return std::uint32_t(p[0]) | (std::uint32_t(p[1]) << 8) | (std::uint32_t(p[2]) << 16) | (std::uint32_t(p[3]) << 24);
|
||||
}
|
||||
|
||||
HdUiPak* open_hd_ui_pak() {
|
||||
if (const char* env = std::getenv("MT_HD_UI"); env && std::strcmp(env, "0") == 0)
|
||||
return nullptr;
|
||||
const char* env_path = std::getenv("MT_HD_UI_PAK");
|
||||
std::FILE* f = std::fopen(env_path && *env_path ? env_path : "hd_ui.pak", "rb");
|
||||
if (!f)
|
||||
return nullptr;
|
||||
auto* pak = new HdUiPak;
|
||||
pak->file = f;
|
||||
std::uint8_t header[12];
|
||||
if (std::fread(header, 1, 12, f) != 12 || std::memcmp(header, "MTHD", 4) != 0 || read_u32(header + 4) != 1) {
|
||||
std::fprintf(stderr, "[hd_ui] hd_ui.pak: bad header, ignored\n");
|
||||
std::fclose(f);
|
||||
delete pak;
|
||||
return nullptr;
|
||||
}
|
||||
const std::uint32_t count = read_u32(header + 8);
|
||||
pak->entries.reserve(count);
|
||||
for (std::uint32_t i = 0; i < count; ++i) {
|
||||
std::uint8_t len[2];
|
||||
if (std::fread(len, 1, 2, f) != 2)
|
||||
break;
|
||||
std::string name(std::size_t(len[0] | (len[1] << 8)), '\0');
|
||||
std::uint8_t pos[8];
|
||||
if (std::fread(name.data(), 1, name.size(), f) != name.size() || std::fread(pos, 1, 8, f) != 8)
|
||||
break;
|
||||
pak->entries.emplace(std::move(name), std::make_pair(read_u32(pos), read_u32(pos + 4)));
|
||||
}
|
||||
std::fprintf(stderr, "[hd_ui] hd_ui.pak: %zu UI textures at 2x\n", pak->entries.size());
|
||||
return pak;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool read_live_pack_texture(const std::string& vpath, std::vector<std::uint8_t>& bytes) {
|
||||
if (vpath.empty() || !mtpack40250::ready())
|
||||
return false;
|
||||
for (const std::string& candidate : pack_name_candidates(vpath)) {
|
||||
if (mtpack40250::read(candidate, bytes) && !bytes.empty())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_hd_ui_texture(const std::string& vpath, std::vector<std::uint8_t>& bytes) {
|
||||
// Opened on first use, after live_client has made the client directory the working directory.
|
||||
static HdUiPak* pak = open_hd_ui_pak();
|
||||
static std::mutex mutex;
|
||||
if (!pak || vpath.empty())
|
||||
return false;
|
||||
for (const std::string& candidate : pack_name_candidates(vpath)) {
|
||||
auto it = pak->entries.find(candidate);
|
||||
if (it == pak->entries.end())
|
||||
continue;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
bytes.resize(it->second.second);
|
||||
return std::fseek(pak->file, long(it->second.first), SEEK_SET) == 0 &&
|
||||
std::fread(bytes.data(), 1, bytes.size(), pak->file) == bytes.size();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mt_host
|
||||
|
||||
@@ -13,6 +13,9 @@ mtimage::Image decode_tga(const std::uint8_t* data, std::size_t size);
|
||||
mtimage::Image decode_texture_bytes(const std::uint8_t* data, std::size_t size);
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
bool read_live_pack_texture(const std::string& vpath, std::vector<std::uint8_t>& bytes);
|
||||
// The 2x replacement of a UI texture from hd_ui.pak in the client directory (MT_HD_UI_PAK overrides
|
||||
// the path, MT_HD_UI=0 disables it); false when there is none.
|
||||
bool read_hd_ui_texture(const std::string& vpath, std::vector<std::uint8_t>& bytes);
|
||||
#endif
|
||||
|
||||
} // namespace mt_host
|
||||
|
||||
@@ -105,8 +105,14 @@ VulkanWindow::GpuTexture& VulkanWindow::get_gpu_texture(
|
||||
decoded = decode_texture_bytes(mtra.data(), mtra.size());
|
||||
}
|
||||
} else {
|
||||
// PORT: the phone magnifies the 600-high 40250 UI about 1.8x, so the 1x window art and icons
|
||||
// look soft. A UI texture found in hd_ui.pak (scripts/build_hd_ui.py) is uploaded at 2x in
|
||||
// place of the pack file. The engine keeps the 1x size from its own decode for layout and
|
||||
// quads, and the UVs are normalized, so only the texel density changes.
|
||||
std::vector<std::uint8_t> pack_bytes;
|
||||
if (read_live_pack_texture(texture_name, pack_bytes))
|
||||
if (read_hd_ui_texture(texture_name, pack_bytes))
|
||||
decoded = decode_texture_bytes(pack_bytes.data(), pack_bytes.size());
|
||||
if (!decoded.ok() && read_live_pack_texture(texture_name, pack_bytes))
|
||||
decoded = decode_texture_bytes(pack_bytes.data(), pack_bytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user