图库: 阅读器页槽改 RGB565 常驻,页图内存减半(M32)
- 页槽从 RGBA 改成「已落框的 RGB565 位图」(2B/px):真机 480×853 页 1599KB → 800KB, 两页常驻 3.2MB → 1.6MB。解码仍在 worker 里产出 RGBA,当场转 565 并释放 RGBA。 - SDK 新增 565 原语(meow_ui_draw.h / meow_ui_gfx.c):meow_gfx_blit_rgb565 命中页每行 一次 memcpy(单次拷贝、无每帧重采样),缩放兜底走 565 最近邻。量化与 back_buf 用同一个 rgb565(),565 屏上落下的像素与旧 RGBA 路径逐像素一致(不引入色偏/新增色带)。 - 预算按 565 重算:单页像素上限沿用旧 3MB@RGBA 的同几何口径(=1.5MB@565), MR_RESIDENT_CAP = 3 槽 + 1 张在途 = 3.2MB;reader_estimate_peak 新增 convert_bytes (落槽暂存 2B/px)与 peak_bytes 一起参与门控,避免实测超预算。 - 结果队列(已解码、未落槽)字节计入总预算(pending_bytes),不再「偷吃」最多 2 页。 - CBZ meta() 改读条目 4KB 前缀(mz_read_prefix;deflate 解到 4KB 即停,输入缓冲走栈), 500 页排版不再触发 500 次全量解压。 - 测试:B3/B8 的 565 常驻与在途预算断言、CBZ 500 页 stored/deflate 前后对比、 P12 565 与 RGBA 路径的逐像素颜色对比(量化误差/RMSE/灰阶梯)。
This commit is contained in:
1 parent
bbc73635a6
commit
7036020005
11 files changed
+910
-111
No files matched your search
+184
-61
@@ -15,8 +15,12 @@
|
||||
* 宽恒为视口宽),不是屏长边,所以翻页/滚动热路径只有原生 blit:不做 meow_ui_img_scaled
|
||||
* 的每帧重采样,也不再每帧 malloc 1MB 级临时缓冲。几何变化(fit/缩放/切模式)时按新几何
|
||||
* 重解一次;拖动/吸附帧只 invalidate 内容区(顶栏静态不重绘)。
|
||||
* 内存(M17 起;M22 按真机实测重定;M27 接真缩解后端):单页显示缓存 ≤3MB、整窗常驻
|
||||
* ≤3MB(3 槽,超限淘汰最远页)。解码路径与峰值预算由 page_source 的 reader_estimate_peak()
|
||||
* 内存(M17 起;M22 按真机实测重定;M27 接真缩解后端;M32 页槽改 RGB565):
|
||||
* 槽里常驻的是**已落框的 565 位图**(2B/px),单页上限 1.5MB(旧 RGBA 3MB 的同几何口径)、
|
||||
* 整窗常驻上限 3.2MB(3 槽 + 1 张在途,超限淘汰最远页)。真机 480×853 页 1599KB → 800KB,
|
||||
* 两页常驻 3.2MB → 1.6MB。解码仍产出 RGBA,worker 当场转 565 再释放 RGBA;这笔暂存
|
||||
* (2B/px)由 reader_estimate_peak 的 convert_bytes 计入预算。解码路径与峰值预算由
|
||||
* page_source 的 reader_estimate_peak()
|
||||
* 统一判定(唯一实现,本文件不再另写模型):
|
||||
* - 真缩解后端(M23)能接住的页:峰值只与目标尺寸相关(≈1.3–2.5MB,与源分辨率无关),
|
||||
* 直接放行,不做内存门控;
|
||||
@@ -77,16 +81,25 @@
|
||||
#define MR_JUMP_TIMEOUT_MS 2500 /* 目标页迟迟解不出来 → 降级落位并提示 */
|
||||
#define MR_CONT_SAVE_MS 1000 /* 续读状态落盘节流 */
|
||||
#define MR_TOAST_MS 1400
|
||||
/* ---- 内存预算(M22 校准;M27 起模型与门控只在 page_source.c 的 reader_estimate_peak 里)----
|
||||
* 真机参考:MemTotal 56872kB、空闲 RSS 6044kB、杀点 RSS≈15.6MB / MemFree 仅 1.4MB。 */
|
||||
/* ---- 内存预算(M22 校准;M27 起模型与门控只在 page_source.c 的 reader_estimate_peak 里;
|
||||
* M32 页槽改 RGB565,2 字节/像素,所有 RGBA 时代的字节常量在这里按 565 重算)----
|
||||
* 真机参考:MemTotal 56872kB、空闲 RSS 6044kB、杀点 RSS≈15.6MB / MemFree 仅 1.4MB。
|
||||
* 页图口径:真机页 480×853 = 409,440px;RGBA 1599KB/页 → RGB565 800KB/页。 */
|
||||
#define MR_PREFETCH_MIN_KB (12L * 1024) /* 预取邻居要求的最低 MemAvailable */
|
||||
#define MR_SKIP_RETRY_MS 1500 /* 被预算跳过的页:焦点页冷却这么久后自动重试 */
|
||||
#define MR_PAGE_BYTES_CAP (3L * 1024 * 1024) /* 单页「显示尺寸」缓存字节上限 */
|
||||
#define MR_RESIDENT_CAP (3L * 1024 * 1024) /* 页图缓存总字节上限(M22 由 9MB 校准为 3MB) */
|
||||
/* 单页缓存上限:旧口径是 3MB@RGBA = 786432px(480×1638);565 下同几何 = 1.5MB。
|
||||
* 保持像素上限不变 → 解码几何/显示清晰度与以前完全一致,只是每像素省 2 字节。 */
|
||||
#define MR_PAGE_PX_CAP (786432L)
|
||||
#define MR_PAGE_BYTES_CAP (MR_PAGE_PX_CAP * 2) /* 1.5MB:单页 565 缓存上限 */
|
||||
#define MR_SLOT_BYTES (800L * 1024) /* 真机典型页(480×853)的 565 字节 */
|
||||
/* 常驻总上限 = 3 槽 + 1 张「已解码未落槽」的在途结果 = 4 × 800KB = 3.2MB。
|
||||
* 旧口径 3MB 只算槽内 RGBA(3 槽最坏 4.8MB,且在途结果完全没算);565 后把在途结果
|
||||
* 一起算进来,真实上限反而收紧。典型稳态(2 页)1.6MB,只有上限的一半。 */
|
||||
#define MR_RESIDENT_CAP (4 * MR_SLOT_BYTES)
|
||||
#define MR_SLOT_TOL 2 /* 缓存图与显示框允许的像素偏差(≤2 走原生 blit,不重采样) */
|
||||
|
||||
#define MR_REQ_MAX 8
|
||||
#define MR_RES_MAX 2 /* 结果队列上限(M22:4→2;未取走的已解码图不计入常驻预算) */
|
||||
#define MR_RES_MAX 2 /* 结果队列上限(M22:4→2);在途结果的字节由 pending_bytes 计入预算 */
|
||||
|
||||
/* view on_event 的事件号不在公共头(sdk/meow_ui_internal.h §6.4);本页只用到 SWIPE=3。 */
|
||||
#define MR_EV_SWIPE 3
|
||||
@@ -146,12 +159,19 @@ static int g_win_idx[MR_SLOTS];
|
||||
static int g_win_n;
|
||||
static int g_fail_paint; /* 窗口内出现解码失败页 → tick 重绘错误占位 */
|
||||
|
||||
/* ---- 页图缓存(≤4 张 downscale 后的图)---- */
|
||||
/* ---- 页图缓存(≤3 槽,RGB565 常驻)----
|
||||
* 槽里存的是**已落框**的 565 位图(2B/px),不是解码器的 RGBA:真机 480×853 页
|
||||
* 1599KB → 800KB,两页常驻 3.2MB → 1.6MB。命中页绘制 = 每行一次 memcpy(M32)。 */
|
||||
typedef struct page_bmp {
|
||||
int w, h;
|
||||
uint16_t *px; /* w*h,行优先,RGB565(不透明) */
|
||||
} page_bmp;
|
||||
|
||||
typedef struct page_slot {
|
||||
int idx; /* -1=空 */
|
||||
meow_img *img; /* 已 downscale 的页图 */
|
||||
int src_w, src_h;
|
||||
int want_w, want_h; /* 本次解码按哪个显示框落的(几何判据,见 slot_ready) */
|
||||
int idx; /* -1=空 */
|
||||
page_bmp *bmp; /* 已 downscale/落框的 565 页图 */
|
||||
int src_w, src_h; /* = bmp->w/h(绘制与观测的几何口径) */
|
||||
int want_w, want_h; /* 本次解码按哪个显示框落的(几何判据,见 slot_ready) */
|
||||
} page_slot;
|
||||
static page_slot g_slot[MR_SLOTS];
|
||||
|
||||
@@ -185,6 +205,7 @@ static volatile int g_decode_count; /* 累计解码次数(观察重解
|
||||
#define MR_SKIP_NONE 0 /* 解出来了 */
|
||||
#define MR_SKIP_LOWMEM 1 /* 内存门控拦下(MemAvailable 不足) */
|
||||
#define MR_SKIP_PEAK 2 /* 真实峰值超预算,且没有余量放行 */
|
||||
#define MR_SKIP_CONVERT 3 /* 565 落槽缓冲分配失败(可重试,不是坏图) */
|
||||
|
||||
static int page_failed(int idx)
|
||||
{
|
||||
@@ -242,7 +263,8 @@ typedef struct page_req {
|
||||
typedef struct page_res {
|
||||
int idx;
|
||||
unsigned gen;
|
||||
meow_img *img;
|
||||
page_bmp *bmp; /* 已转 565 的页图(NULL = 失败/跳过) */
|
||||
long bytes; /* bmp 的字节数:在途结果的预算口径(M32) */
|
||||
int fail;
|
||||
int skip; /* MR_SKIP_*:非 0 = 预算跳过(可重试,不是坏图) */
|
||||
int want_w, want_h;
|
||||
@@ -267,6 +289,10 @@ static int g_slide_delta; /* 翻页目标增量(动画中) */
|
||||
static int g_slide_from; /* 进场页起点(px) */
|
||||
static int g_anim_v; /* 进场页当前位移(px) */
|
||||
static uint64_t g_flip_t0;
|
||||
/* 翻页命中/未命中(观测口,M32 用来验证「命中率不退化」;非产品逻辑):
|
||||
* 目标页已在槽 = 预取生效,翻页不用等解码。与 M31 的 tier 机制无关。 */
|
||||
static volatile int g_flip_hits;
|
||||
static volatile int g_flip_misses;
|
||||
static uint64_t g_last_tick;
|
||||
static uint64_t g_last_paint;
|
||||
static int g_quitting; /* on_leave 期间抑制回调 */
|
||||
@@ -352,11 +378,51 @@ static long mem_avail_kb(void)
|
||||
return mem_kb("/proc/meminfo", "MemAvailable:");
|
||||
}
|
||||
|
||||
/* ---- 565 页图:转换 / 释放 / 字节口径(M32) ----
|
||||
* 解码后立刻转成 565 并释放 RGBA:转换期间 RGBA(4B/px)+ 565(2B/px)同时存在,
|
||||
* 这笔暂存由 reader_estimate_peak 的 convert_bytes 计入预算(见 page_source.h)。
|
||||
* 失败时也会释放 RGBA(调用方不必再 free),返回 NULL 按「可重试的预算跳过」处理。 */
|
||||
static page_bmp *page_bmp_from_img(meow_img *im)
|
||||
{
|
||||
if (!im) return NULL;
|
||||
int w = meow_img_w(im), h = meow_img_h(im);
|
||||
const unsigned char *rgba = meow_img_pixels(im);
|
||||
page_bmp *b = NULL;
|
||||
if (rgba && w > 0 && h > 0) {
|
||||
b = (page_bmp *)malloc(sizeof *b);
|
||||
uint16_t *px = b ? (uint16_t *)malloc((size_t)w * (size_t)h * sizeof *px) : NULL;
|
||||
if (b && px) {
|
||||
meow_gfx_rgba_to_rgb565(rgba, px, w * h);
|
||||
b->w = w;
|
||||
b->h = h;
|
||||
b->px = px;
|
||||
} else {
|
||||
free(px);
|
||||
free(b);
|
||||
b = NULL;
|
||||
}
|
||||
}
|
||||
meow_img_free(im);
|
||||
return b;
|
||||
}
|
||||
|
||||
static void page_bmp_free(page_bmp *b)
|
||||
{
|
||||
if (!b) return;
|
||||
free(b->px);
|
||||
free(b);
|
||||
}
|
||||
|
||||
static long page_bmp_bytes(const page_bmp *b)
|
||||
{
|
||||
return b ? (long)b->w * b->h * 2 : 0;
|
||||
}
|
||||
|
||||
static int resident_pages(void)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < MR_SLOTS; i++)
|
||||
if (g_slot[i].img) n++;
|
||||
if (g_slot[i].bmp) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -364,18 +430,30 @@ static long resident_bytes(void)
|
||||
{
|
||||
long b = 0;
|
||||
for (int i = 0; i < MR_SLOTS; i++)
|
||||
if (g_slot[i].img) b += (long)g_slot[i].src_w * g_slot[i].src_h * 4;
|
||||
b += page_bmp_bytes(g_slot[i].bmp);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* 结果队列里「已解码、还没落槽」的 565 字节(M32:这些图也是真实内存)。
|
||||
* 只读主线程路径用它做预算;worker 在 g_lock 内写队列,故取快照也在锁内。 */
|
||||
static long pending_bytes(void)
|
||||
{
|
||||
long b = 0;
|
||||
pthread_mutex_lock(&g_lock);
|
||||
for (int i = 0; i < g_res_n; i++) b += g_res[i].bytes;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* 进出阅读器各打一行(任务 5):RSS 看当前占用,VmHWM 看本次进入的峰值——
|
||||
* 验收就是「离开时的 hwm 不超过预算」。 */
|
||||
* 验收就是「离开时的 hwm 不超过预算」。img 列 = 槽内 565 字节;pending 未落槽的在途图。 */
|
||||
static void log_mem(const char *tag)
|
||||
{
|
||||
meow_log(MEOW_LOG_INFO, "reader mem[%s]: rss=%ldkB hwm=%ldkB memfree=%ldkB memavail=%ldkB pages=%d img=%ldkB",
|
||||
meow_log(MEOW_LOG_INFO,
|
||||
"reader mem[%s]: rss=%ldkB hwm=%ldkB memfree=%ldkB memavail=%ldkB pages=%d img=%ldkB pending=%ldkB",
|
||||
tag, mem_kb("/proc/self/status", "VmRSS:"),
|
||||
mem_kb("/proc/self/status", "VmHWM:"), mem_free_kb(), mem_avail_kb(),
|
||||
resident_pages(), resident_bytes() / 1024);
|
||||
resident_pages(), resident_bytes() / 1024, pending_bytes() / 1024);
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
@@ -481,7 +559,7 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||
}
|
||||
if (dec.skip != MR_DECODE_OK) {
|
||||
g_decode_skip = dec.skip == MR_DECODE_SKIP_LOWMEM ? MR_SKIP_LOWMEM : MR_SKIP_PEAK;
|
||||
log_decode(page, in.w, in.h, dec.peak_bytes, mfree, mavail,
|
||||
log_decode(page, in.w, in.h, dec.peak_bytes + dec.convert_bytes, mfree, mavail,
|
||||
dec.skip == MR_DECODE_SKIP_LOWMEM ? "skip=lowmem" : "skip=peak",
|
||||
dec.step, dec.path == MR_DECODE_PATH_BACKEND,
|
||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||
@@ -668,15 +746,15 @@ static page_slot *slot_find(int idx)
|
||||
{
|
||||
if (idx < 0) return NULL;
|
||||
for (int i = 0; i < MR_SLOTS; i++)
|
||||
if (g_slot[i].idx == idx && g_slot[i].img) return &g_slot[i];
|
||||
if (g_slot[i].idx == idx && g_slot[i].bmp) return &g_slot[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void slot_clear(int i)
|
||||
{
|
||||
if (i < 0 || i >= MR_SLOTS) return;
|
||||
if (g_slot[i].img) meow_img_free(g_slot[i].img);
|
||||
g_slot[i].img = NULL;
|
||||
page_bmp_free(g_slot[i].bmp);
|
||||
g_slot[i].bmp = NULL;
|
||||
g_slot[i].idx = -1;
|
||||
g_slot[i].src_w = g_slot[i].src_h = 0;
|
||||
g_slot[i].want_w = g_slot[i].want_h = 0;
|
||||
@@ -711,7 +789,7 @@ static int slot_evict_far(int except)
|
||||
int focus = focus_page();
|
||||
int far = -1, far_d = -1;
|
||||
for (int i = 0; i < MR_SLOTS; i++) {
|
||||
if (i == except || !g_slot[i].img) continue;
|
||||
if (i == except || !g_slot[i].bmp) continue;
|
||||
if (g_slot[i].idx == focus) continue;
|
||||
if (page_visible(g_slot[i].idx)) continue;
|
||||
int d = abs(g_slot[i].idx - focus);
|
||||
@@ -723,24 +801,29 @@ static int slot_evict_far(int except)
|
||||
|
||||
/* 开新页前先腾地方(任务 3):按「即将落地的单页字节」预判,先淘汰非可见/非焦点的旧图,
|
||||
* 让解码缓冲不与陈旧常驻叠加。只在逼近上限时才动手,避免把预取邻居立刻扔掉
|
||||
*(那样翻页会从「命中」退化成「重新解码」,反而增加峰值次数)。 */
|
||||
*(那样翻页会从「命中」退化成「重新解码」,反而增加峰值次数)。
|
||||
* M32:已解码未落槽的在途结果(pending_bytes)也是真实内存,一起算进常驻上限。 */
|
||||
static void slots_reserve(long need_bytes)
|
||||
{
|
||||
if (need_bytes <= 0) return;
|
||||
while (resident_bytes() + need_bytes > MR_RESIDENT_CAP) {
|
||||
long pending = pending_bytes();
|
||||
while (resident_bytes() + pending + need_bytes > MR_RESIDENT_CAP) {
|
||||
long before = resident_bytes();
|
||||
if (slot_evict_far(-1) < 0) return; /* 只剩可见页:放行,下一次 slot_put 再收敛 */
|
||||
if (resident_bytes() >= before) return; /* 没腾出东西(无可淘汰页):别死循环 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 放进缓存;同页旧图直接替换(fit/zoom/模式变了要换尺寸)。返回 1=已放入。
|
||||
* want_w/want_h = 这次解码按哪个显示框落的,交给 slot_ready 做几何判据。 */
|
||||
static int slot_put(int idx, meow_img *img, int want_w, int want_h)
|
||||
* want_w/want_h = 这次解码按哪个显示框落的,交给 slot_ready 做几何判据。
|
||||
* bmp 的所有权移交本槽(失败时在这里释放)。 */
|
||||
static int slot_put(int idx, page_bmp *bmp, int want_w, int want_h)
|
||||
{
|
||||
if (!img || idx < 0) return 0;
|
||||
if (!bmp || idx < 0) { page_bmp_free(bmp); return 0; }
|
||||
int i = -1, empty = -1;
|
||||
for (int k = 0; k < MR_SLOTS; k++) {
|
||||
if (g_slot[k].idx == idx && g_slot[k].img) { i = k; break; }
|
||||
if (!g_slot[k].img && empty < 0) empty = k;
|
||||
if (g_slot[k].idx == idx && g_slot[k].bmp) { i = k; break; }
|
||||
if (!g_slot[k].bmp && empty < 0) empty = k;
|
||||
}
|
||||
if (i < 0) {
|
||||
if (empty >= 0) {
|
||||
@@ -752,25 +835,28 @@ static int slot_put(int idx, meow_img *img, int want_w, int want_h)
|
||||
int d = abs(g_slot[k].idx - focus);
|
||||
if (d > far_d) { far_d = d; i = k; }
|
||||
}
|
||||
if (i < 0) i = g_slot[0].img ? 0 : -1; /* 全可见:只能挤掉第一槽(极端条漫页) */
|
||||
if (i < 0) i = g_slot[0].bmp ? 0 : -1; /* 全可见:只能挤掉第一槽(极端条漫页) */
|
||||
}
|
||||
}
|
||||
if (i < 0) { meow_img_free(img); return 0; }
|
||||
if (i < 0) { page_bmp_free(bmp); return 0; }
|
||||
|
||||
/* 总字节上限:新图放进去前先淘汰最远页,保证常驻图不超过预算。 */
|
||||
long need = (long)meow_img_w(img) * meow_img_h(img) * 4;
|
||||
/* 总字节上限:新图放进去前先淘汰最远页,保证常驻 + 在途不超过预算。 */
|
||||
long need = page_bmp_bytes(bmp);
|
||||
long pending = pending_bytes();
|
||||
for (;;) {
|
||||
long held = resident_bytes();
|
||||
if (g_slot[i].img) held -= (long)g_slot[i].src_w * g_slot[i].src_h * 4;
|
||||
long held = resident_bytes() + pending;
|
||||
if (g_slot[i].bmp) held -= page_bmp_bytes(g_slot[i].bmp);
|
||||
if (held + need <= MR_RESIDENT_CAP) break;
|
||||
long before = resident_bytes();
|
||||
if (slot_evict_far(i) < 0) break; /* 只剩目标槽:这一页必须放进来 */
|
||||
if (resident_bytes() >= before) break; /* 没腾出东西:收手,别死循环 */
|
||||
}
|
||||
|
||||
slot_clear(i);
|
||||
g_slot[i].idx = idx;
|
||||
g_slot[i].img = img;
|
||||
g_slot[i].src_w = meow_img_w(img);
|
||||
g_slot[i].src_h = meow_img_h(img);
|
||||
g_slot[i].bmp = bmp;
|
||||
g_slot[i].src_w = bmp->w;
|
||||
g_slot[i].src_h = bmp->h;
|
||||
g_slot[i].want_w = want_w;
|
||||
g_slot[i].want_h = want_h;
|
||||
return 1;
|
||||
@@ -856,8 +942,9 @@ static void queue_page(int idx)
|
||||
if (want_w <= 0 || want_h <= 0) { want_w = g_vw; want_h = g_vh; }
|
||||
int box_w = 0, box_h = 0;
|
||||
plan_box_size(want_w, want_h, &box_w, &box_h);
|
||||
/* 先释放再解码:按这一页的落地字节先淘汰不可见旧图,别让常驻与解码缓冲叠加。 */
|
||||
slots_reserve((long)box_w * box_h * 4);
|
||||
/* 先释放再解码:按这一页的落地字节(565)先淘汰不可见旧图,别让常驻与解码缓冲叠加。
|
||||
* slots_reserve 内部按「槽内 + 在途结果」对新页做预算(M32:在途结果也算内存)。 */
|
||||
slots_reserve((long)box_w * box_h * 2);
|
||||
pthread_mutex_lock(&g_lock);
|
||||
if (!g_stop && !req_queued(idx) && g_req_n < MR_REQ_MAX) {
|
||||
page_req *r = &g_req[g_req_tail];
|
||||
@@ -906,22 +993,31 @@ static void *reader_worker(void *arg)
|
||||
g_decode_skip = MR_SKIP_NONE;
|
||||
meow_img *img = mr_book_open(&g_book, req.idx);
|
||||
g_decode_count++;
|
||||
int skip = img ? MR_SKIP_NONE : g_decode_skip;
|
||||
/* 565 落槽(M32):解码结果只用来生成槽里的 565 位图,RGBA 当场释放。
|
||||
* 放在 worker 里做——主线程只收一张已经能直接 blit 的图,翻页不等转换。 */
|
||||
int had_img = img != NULL;
|
||||
page_bmp *bmp = had_img ? page_bmp_from_img(img) : NULL; /* 接管并释放 RGBA */
|
||||
img = NULL;
|
||||
if (had_img && !bmp) {
|
||||
meow_log(MEOW_LOG_WARN, "reader decode page=%d 565-convert failed (oom)", req.idx);
|
||||
}
|
||||
int skip = bmp ? MR_SKIP_NONE : (had_img ? MR_SKIP_CONVERT : g_decode_skip);
|
||||
|
||||
pthread_mutex_lock(&g_lock);
|
||||
if (!g_stop && g_res_n < MR_RES_MAX) {
|
||||
g_res[g_res_n].idx = req.idx;
|
||||
g_res[g_res_n].gen = req.gen;
|
||||
g_res[g_res_n].img = img;
|
||||
g_res[g_res_n].fail = (img || skip) ? 0 : 1;
|
||||
g_res[g_res_n].bmp = bmp;
|
||||
g_res[g_res_n].bytes = page_bmp_bytes(bmp);
|
||||
g_res[g_res_n].fail = (bmp || skip) ? 0 : 1;
|
||||
g_res[g_res_n].skip = skip;
|
||||
g_res[g_res_n].want_w = req.want_w;
|
||||
g_res[g_res_n].want_h = req.want_h;
|
||||
g_res_n++;
|
||||
img = NULL; /* 所有权已移交(失败/跳过时本就是 NULL) */
|
||||
bmp = NULL; /* 所有权已移交(失败/跳过时本就是 NULL) */
|
||||
}
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
if (img) meow_img_free(img);
|
||||
page_bmp_free(bmp);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -944,7 +1040,7 @@ static void worker_stop(void)
|
||||
g_worker_on = 0;
|
||||
|
||||
pthread_mutex_lock(&g_lock);
|
||||
for (int i = 0; i < g_res_n; i++) meow_img_free(g_res[i].img);
|
||||
for (int i = 0; i < g_res_n; i++) page_bmp_free(g_res[i].bmp);
|
||||
g_res_n = 0;
|
||||
g_req_n = 0;
|
||||
g_req_head = g_req_tail = 0;
|
||||
@@ -987,9 +1083,9 @@ static int results_drain(void)
|
||||
|
||||
int changed = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (got[i].gen != g_gen) { meow_img_free(got[i].img); continue; }
|
||||
if (got[i].gen != g_gen) { page_bmp_free(got[i].bmp); continue; }
|
||||
if (got[i].skip) {
|
||||
/* 预算跳过(内存不足/峰值超限):可重试,不写永久失败位。 */
|
||||
/* 预算跳过(内存不足/峰值超限/565 转换失败):可重试,不写永久失败位。 */
|
||||
page_mark_skipped(got[i].idx);
|
||||
if (page_visible(got[i].idx)) g_fail_paint = 1;
|
||||
continue;
|
||||
@@ -1000,11 +1096,11 @@ static int results_drain(void)
|
||||
continue;
|
||||
}
|
||||
if (in_window(got[i].idx)) {
|
||||
if (slot_put(got[i].idx, got[i].img, got[i].want_w, got[i].want_h) &&
|
||||
if (slot_put(got[i].idx, got[i].bmp, got[i].want_w, got[i].want_h) &&
|
||||
page_visible(got[i].idx)) changed = 1;
|
||||
page_skip_clear(got[i].idx); /* 解出来了:撤销跳过标记 */
|
||||
} else {
|
||||
meow_img_free(got[i].img);
|
||||
page_bmp_free(got[i].bmp);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
@@ -1176,8 +1272,8 @@ static int pan_mode(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 画一页(带水平位移);解码尺寸==显示尺寸时走原生 blit(翻页热路径零重采样),
|
||||
* 只在 fit/zoom 变化或极端长页(超单页缓存上限)时才退回最近邻缩放。 */
|
||||
/* 画一页(带水平位移);页图尺寸==显示尺寸时走原生 565 blit(翻页热路径零重采样、
|
||||
* 每行一次 memcpy),只在 fit/zoom 变化或极端长页(超单页缓存上限)时才退回最近邻缩放。 */
|
||||
static void draw_page_at(int idx, int off_x, int off_y)
|
||||
{
|
||||
page_slot *s = slot_find(idx);
|
||||
@@ -1188,16 +1284,17 @@ static void draw_page_at(int idx, int off_x, int off_y)
|
||||
int x = (g_vw - w) / 2 + g_pan_x + off_x;
|
||||
int y = (g_vh - h) / 2 + g_pan_y + off_y;
|
||||
if (abs(w - s->src_w) <= MR_SLOT_TOL && abs(h - s->src_h) <= MR_SLOT_TOL) {
|
||||
meow_ui_img(x + (w - s->src_w) / 2, y + (h - s->src_h) / 2, s->img);
|
||||
meow_gfx_blit_rgb565(x + (w - s->src_w) / 2, y + (h - s->src_h) / 2,
|
||||
s->src_w, s->src_h, s->bmp->px);
|
||||
NOTE_BLIT();
|
||||
} else {
|
||||
meow_ui_img_scaled(x, y, w, h, s->img);
|
||||
meow_gfx_blit_rgb565_scaled(x, y, w, h, s->src_w, s->src_h, s->bmp->px);
|
||||
NOTE_SCALED();
|
||||
}
|
||||
}
|
||||
|
||||
/* 条漫:按滚动偏移画可见页。典型 2:3 页的解码尺寸与显示尺寸相等 → 原生 blit;
|
||||
* 其余按最近邻缩放(缩放在 SDK 内部申请目标尺寸临时缓冲,单页峰值受控)。 */
|
||||
/* 条漫:按滚动偏移画可见页。典型 2:3 页的页图尺寸与显示尺寸相等 → 原生 565 blit;
|
||||
* 其余按最近邻缩放(gfx 内直接采样,不再申请目标尺寸临时缓冲)。 */
|
||||
static void draw_strip(const meow_theme *th)
|
||||
{
|
||||
if (!g_strip_ok) {
|
||||
@@ -1212,13 +1309,13 @@ static void draw_strip(const meow_theme *th)
|
||||
if (h <= 0) continue;
|
||||
page_slot *s = slot_find(i);
|
||||
if (s) {
|
||||
/* 解码=显示尺寸时原生 blit:gfx 只处理屏幕上可见的行,
|
||||
/* 页图=显示尺寸时原生 blit:gfx 只处理屏幕上可见的行,
|
||||
* 长条页只露一截时不再整页重采样(M7 finding 的子矩形 blit 诉求)。 */
|
||||
if (abs(s->src_w - g_vw) <= MR_SLOT_TOL && abs(s->src_h - h) <= MR_SLOT_TOL) {
|
||||
meow_ui_img((g_vw - s->src_w) / 2, y, s->img);
|
||||
meow_gfx_blit_rgb565((g_vw - s->src_w) / 2, y, s->src_w, s->src_h, s->bmp->px);
|
||||
NOTE_BLIT();
|
||||
} else {
|
||||
meow_ui_img_scaled(0, y, g_vw, h, s->img);
|
||||
meow_gfx_blit_rgb565_scaled(0, y, g_vw, h, s->src_w, s->src_h, s->bmp->px);
|
||||
NOTE_SCALED();
|
||||
}
|
||||
continue;
|
||||
@@ -1507,11 +1604,13 @@ static void page_flip(int delta)
|
||||
|
||||
g_flip_t0 = meow_ui_time_ms();
|
||||
if (slot_find(target)) {
|
||||
g_flip_hits++;
|
||||
if (start_slide(delta, 0))
|
||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit", g_page + 1, target + 1);
|
||||
else
|
||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit (no anim slot)", g_page + 1, target + 1);
|
||||
} else {
|
||||
g_flip_misses++;
|
||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d miss (decode pending)", g_page + 1, target + 1);
|
||||
commit_page(target);
|
||||
}
|
||||
@@ -1762,7 +1861,7 @@ static int open_book(const char *dir, int start_page, int mode)
|
||||
g_plan.box_h = bh;
|
||||
g_plan_page = g_page;
|
||||
meow_img *first = mr_book_open(&g_book, g_page);
|
||||
if (first) slot_put(g_page, first, ww, wh);
|
||||
if (first) slot_put(g_page, page_bmp_from_img(first), ww, wh);
|
||||
meow_log(MEOW_LOG_WARN, "reader worker unavailable, sync first page");
|
||||
}
|
||||
slider_build();
|
||||
@@ -1845,8 +1944,10 @@ static void drag_finish(void)
|
||||
}
|
||||
g_flip_t0 = meow_ui_time_ms();
|
||||
if (slot_find(target) && start_slide(delta, dx)) {
|
||||
g_flip_hits++;
|
||||
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d hit", g_page + 1, target + 1);
|
||||
} else {
|
||||
g_flip_misses++;
|
||||
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d miss (decode pending)",
|
||||
g_page + 1, target + 1);
|
||||
commit_page(target);
|
||||
@@ -2519,6 +2620,14 @@ long long mr_reader_ui_decode_peak_kb(const char *path, int step, int box_w, int
|
||||
reader_estimate_peak(&in, step, box_w, box_h, -1, -1, &dec);
|
||||
return dec.peak_bytes / 1024;
|
||||
}
|
||||
long long mr_reader_ui_decode_convert_kb(const char *path, int step, int box_w, int box_h)
|
||||
{
|
||||
mr_img_info in;
|
||||
mr_decode_decision dec;
|
||||
if (!path || !mr_probe_info(path, &in)) return 0;
|
||||
reader_estimate_peak(&in, step, box_w, box_h, -1, -1, &dec);
|
||||
return dec.convert_bytes / 1024;
|
||||
}
|
||||
long mr_reader_ui_peak_cap_kb(void) { return (long)(reader_peak_budget_bytes(-1) / 1024); }
|
||||
long mr_reader_ui_peak_budget_kb(void) { return (long)(reader_peak_budget_bytes(mem_free_kb()) / 1024); }
|
||||
long mr_reader_ui_resident_cap_kb(void) { return (long)(MR_RESIDENT_CAP / 1024); }
|
||||
@@ -2527,6 +2636,20 @@ int mr_reader_ui_scaled_draws(void) { return g_scaled_draws; }
|
||||
int mr_reader_ui_decode_count(void) { return g_decode_count; }
|
||||
int mr_reader_ui_fit(void) { return g_fit; }
|
||||
int mr_reader_ui_chrome(void) { return g_chrome; }
|
||||
/* 指定槽内页图的常驻字节(KB,RGB565 2B/px);0=不在槽内(M32 观测口)。 */
|
||||
long mr_reader_ui_slot_kb(int idx)
|
||||
{
|
||||
page_slot *s = slot_find(idx);
|
||||
if (!s) return 0;
|
||||
return page_bmp_bytes(s->bmp) / 1024;
|
||||
}
|
||||
/* M32:已解码、还没落槽的在途结果字节(预算口径,供测试断言它也被计入总预算)。 */
|
||||
long mr_reader_ui_pending_kb(void) { return pending_bytes() / 1024; }
|
||||
long mr_reader_ui_page_px_cap(void) { return (long)MR_PAGE_PX_CAP; }
|
||||
/* 翻页命中/未命中(观测口,非产品逻辑):目标页已在槽 = 预取生效,翻页不用等解码。
|
||||
* M31 回滚时整段观测口被删;这两项与 tier 无关,M32 用它验证「命中率不退化」。 */
|
||||
int mr_reader_ui_flip_hits(void) { return g_flip_hits; }
|
||||
int mr_reader_ui_flip_misses(void) { return g_flip_misses; }
|
||||
/* 直接切模式(避开顶栏 tap 命中时序):与顶栏动作走同一个 mode_set。 */
|
||||
void mr_reader_test_toggle_mode(void)
|
||||
{
|
||||
|
||||
+12
-2
@@ -37,7 +37,7 @@ int mr_reader_ui_page(void); /* 当前页(条漫=视口顶端所在页
|
||||
int mr_reader_ui_count(void); /* 当前册页数 */
|
||||
int mr_reader_ui_jump_open(void); /* 1=跳页滑块已展开 */
|
||||
int mr_reader_ui_jump_pending(void);/* 1=跳页目标等待解码 */
|
||||
long mr_reader_ui_resident_kb(void);/* 驻留页图字节(KB) */
|
||||
long mr_reader_ui_resident_kb(void);/* 槽内常驻页图字节(KB,RGB565:2 字节/像素) */
|
||||
int mr_reader_ui_failed_pages(void);/* 解码失败(坏图)页数 */
|
||||
int mr_reader_ui_skipped_pages(void);/* 被内存预算跳过(可重试)的页数 */
|
||||
/* 注入 /proc/meminfo 读数(kB);-1 = 恢复真实读取。用于确定性验证内存门控与放宽档。 */
|
||||
@@ -45,9 +45,19 @@ void mr_reader_ui_test_set_mem(long free_kb, long avail_kb);
|
||||
/* 峰值模型自检:用产品代码里的同一个模型算预估峰值(kB),供宿主测试与 --wrap malloc
|
||||
* 实测对照。0=探测失败。 */
|
||||
long long mr_reader_ui_decode_peak_kb(const char *path, int step, int box_w, int box_h);
|
||||
/* M32:同一次取页里的 565 落槽暂存(kB,= 2×tw×th);与 decode_peak 分开报,便于
|
||||
* 「解码峰值」与「落槽暂存」各自对照实测。 */
|
||||
long long mr_reader_ui_decode_convert_kb(const char *path, int step, int box_w, int box_h);
|
||||
long mr_reader_ui_peak_cap_kb(void); /* 常态峰值上限(kB) */
|
||||
long mr_reader_ui_peak_budget_kb(void); /* 当前 MemFree 下的实际预算(kB) */
|
||||
long mr_reader_ui_resident_cap_kb(void); /* 常驻上限(kB) */
|
||||
long mr_reader_ui_resident_cap_kb(void); /* 常驻上限(kB;含在途结果) */
|
||||
/* ---- M32 RGB565 页槽预算口径 ---- */
|
||||
long mr_reader_ui_pending_kb(void); /* 已解码、还没落槽的在途结果字节(KB) */
|
||||
long mr_reader_ui_page_px_cap(void); /* 单页缓存像素上限(旧 RGBA 3MB 的同几何口径) */
|
||||
long mr_reader_ui_slot_kb(int idx); /* 指定槽内页图的常驻字节(KB,565);0=不在槽内 */
|
||||
/* 翻页命中/未命中(观测口,用于验证「命中率不退化」):目标页已在槽 = 预取生效。 */
|
||||
int mr_reader_ui_flip_hits(void);
|
||||
int mr_reader_ui_flip_misses(void);
|
||||
int mr_reader_ui_blit_draws(void); /* 累计原生 blit 绘页次数(热路径应为纯 blit) */
|
||||
int mr_reader_ui_scaled_draws(void);/* 累计重采样绘页次数(每帧重采样=卡顿) */
|
||||
int mr_reader_ui_decode_count(void);/* 累计解码次数(查重解抖动/活锁) */
|
||||
|
||||
Reference in new issue
Block a user