图库: 修条漫长页/竖翻每帧重采样(解码几何对齐排版)+ 判据与活锁(M17 评审 P1-1/P2)
评审用真实 UI 链路实测:600x960、800x1280(长宽比 1.6)在翻页拖动与条漫模式 都是 blit+0 / scaled+5;「先适高再切条漫」scaled+10。根因是解码几何来源与排版几何 不同源:decode_long_side() 把长边钳到屏长边 720(600x960 解成 450x720),而 strip_page_height() 按屏宽算出 480x768;mode_set() 又不动 g_fit,适高/原始进条漫 100% 重采样。 修复: - 解码目标 = 排版真正要的显示框:新增 page_geometry_at()(条漫=strip 高度、宽=视口宽; 翻页=fit/zoom),解码计划与绘页/就绪判据同源。 - plan_decode_long() 去掉屏长边钳制,只保留 MR_PAGE_BYTES_CAP(3MB) 硬上界, 超长条漫按比例收缩(不再因为「高度 > 屏长边」就永远对不上)。 - slot 记录本次解码的 want 几何;slot_ready() 改为几何比较: fit/缩放/模式变化 → 重解一次(放大再缩回、适高→条漫回到纯 blit); 被 3MB 钳住的解码 want 不变 → 不再反复重解(大屏活锁)。 - 解码几何随 page_req 下发、随 page_res 返回:worker 不再读 g_fit/g_zoom/g_vw/g_vh。 - 峰值估算把 downscale/缩解输出一并计入(P2-3)。 - 适宽长图(竖向单独溢出)不再算平移模式:1.6 比例页原先左右 tap/横拖会变成 「水平平移 0px」的空操作,页面根本翻不动;现在仍按翻页处理(原始档/横向溢出不变)。 - 窗口不再主动清窗口外的页图:0→1→0 来回翻不再反复重解同一页(140 页测试 decode 次数 21→14);容量仍由 MR_SLOTS + 字节淘汰兜底。 - state_reset 复位 g_meta/g_start_at(起始页在 on_enter 入口先取走),删重复注释。 测试:test_reader_perf 夹具改成 600x960(1.6)并补 800x1280、翻页拖动、条漫、 「适高→条漫」、双击放大缩回、静止无重解、大屏 1080x1920 无活锁;test_reader_many_pages 补 meow_ui_paint_full(绘制路径覆盖,P2-5)。
This commit is contained in:
1 parent
462cfd5bc0
commit
ceb65dfd01
4 files changed
+379
-101
No files matched your search
@@ -208,6 +208,10 @@ int main(void)
|
||||
mr_reader_ui_count(), BOOK_PAGES);
|
||||
CHECK(mr_reader_ui_page() == 0, "start at page 0 (got %d)", mr_reader_ui_page());
|
||||
pump_ms(600);
|
||||
int draws0 = mr_reader_ui_blit_draws() + mr_reader_ui_scaled_draws();
|
||||
meow_ui_paint_full();
|
||||
CHECK(mr_reader_ui_blit_draws() + mr_reader_ui_scaled_draws() > draws0,
|
||||
"draw path executed (paged, 140p book)");
|
||||
|
||||
/* ---- M2 从第 N 张开始 / 越界钳制 ---- */
|
||||
meow_view_page_pop();
|
||||
@@ -246,8 +250,11 @@ int main(void)
|
||||
int before = mr_reader_ui_page();
|
||||
tap(40, 300);
|
||||
pump_ms(260);
|
||||
meow_ui_paint_full(); /* 绘制路径覆盖(P2-5) */
|
||||
if (mr_reader_ui_page() == before) ok_move = 0;
|
||||
}
|
||||
CHECK(mr_reader_ui_blit_draws() + mr_reader_ui_scaled_draws() > draws0,
|
||||
"draw path executed across flips");
|
||||
CHECK(ok_move, "12 consecutive flips advanced the page");
|
||||
CHECK(mr_reader_ui_resident_kb() <= 9 * 1024, "resident bounded on 140p book (%ldKB)",
|
||||
mr_reader_ui_resident_kb());
|
||||
@@ -285,6 +292,13 @@ int main(void)
|
||||
reader_on_tap(sx + sw, slider_y);
|
||||
pump_ms(900);
|
||||
CHECK(mr_reader_ui_jump_pending() == 0, "jump landed in webtoon");
|
||||
{
|
||||
int d0 = mr_reader_ui_blit_draws() + mr_reader_ui_scaled_draws();
|
||||
meow_ui_paint_full();
|
||||
meow_ui_paint_full(); /* 条漫绘制路径覆盖(P2-5) */
|
||||
CHECK(mr_reader_ui_blit_draws() + mr_reader_ui_scaled_draws() > d0,
|
||||
"draw path executed (webtoon)");
|
||||
}
|
||||
CHECK(mr_reader_ui_page() >= BOOK_PAGES - 3, "jumped near last page (got %d)",
|
||||
mr_reader_ui_page());
|
||||
CHECK(mr_reader_ui_resident_kb() <= 9 * 1024, "resident bounded in webtoon (%ldKB)",
|
||||
|
||||
+211
-33
@@ -1,20 +1,28 @@
|
||||
/* test_reader_perf.c — 阅读页翻页热路径回归(需要 SDK 源码树,宿主跑真实 view/anim/img)。
|
||||
/* test_reader_perf.c — 阅读页绘页热路径回归(需要 SDK 源码树,宿主跑真实 view/anim/img)。
|
||||
*
|
||||
* 用户反馈:「翻页很卡,横翻竖翻都卡」「40 页也卡」。真因是每帧一次 meow_ui_img_scaled:
|
||||
* 解码尺寸(maxdim=屏长边) ≠ 显示尺寸(适宽/适高) → 每帧重采样 + 每帧 1.2MB 临时缓冲
|
||||
* malloc/free。修法:解码期直接按显示尺寸落地,翻页只做原生 blit。
|
||||
* 解码尺寸 ≠ 排版要的显示尺寸 → 每帧重采样 + 每帧 1MB 级临时缓冲 malloc/free。
|
||||
* 修法:解码期直接按**排版要的几何**落地(翻页=fit/zoom,条漫=strip 排版高度),翻页只 blit。
|
||||
*
|
||||
* 本测试用 600x800(3:4)合成 BMP 当页图,断言:
|
||||
* P1 适宽:解码 480x640 == 显示框 → 绘页零重采样(scaled_draws 不增长);
|
||||
* P2 连续翻页 10 次(预取窗口命中):每帧仍零重采样,且确实画了页(blit 增长);
|
||||
* P3 切「适高」:重解到 540x720 后同样零重采样(fit 变化会按新几何重解,不残留缩放);
|
||||
* P4 驻留内存有上界(<= 9MB:3~4 张显示尺寸页)。
|
||||
* 夹具刻意用 **600x960(长宽比 1.6)**:条漫常见比例,显示高度 768 > 屏长边 720——
|
||||
* 早期夹具用 600x800(1.33)恰好绕过「钳到屏长边」的边界,给过过于乐观的信号(评审 P1-1)。
|
||||
*
|
||||
* 断言(每项都看 blit/scaled 计数增量):
|
||||
* P1 适宽进入:blit > 0 / scaled = 0;
|
||||
* P2 连续翻页(点击来回 10 次):scaled 不增长;
|
||||
* P3 翻页模式**跟手拖动**:scaled 不增长且确实画了页;
|
||||
* P4 条漫模式(切换后滚动):scaled 不增长、blit 增长;
|
||||
* P5 「先点适高,再切条漫」:scaled 不增长(评审点名的第二入口);
|
||||
* P6 双击放大再双击缩回:缩回后按新几何重解 → scaled 不增长(P2-1);
|
||||
* P7 静止若干帧不产生新解码(无重解抖动/活锁,P2-2);
|
||||
* P8 驻留页图有字节上界(<= 9MB)。
|
||||
*
|
||||
* 编译:test/run_tests.sh(与 test_open_book_host 同口径;只发布 meow_ui.a 时 SKIP)。
|
||||
*/
|
||||
#include "tuku_internal.h"
|
||||
#include "ui/reader/reader.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include "logic/reader/nav.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
@@ -23,6 +31,9 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PAGE_W 600
|
||||
#define PAGE_H 960 /* 长宽比 1.6:适宽显示 480x768(高 > 屏长边 720) */
|
||||
|
||||
void mr_nav_report(void) {}
|
||||
|
||||
static int g_fail;
|
||||
@@ -38,7 +49,6 @@ static void put32(unsigned char *p, unsigned v)
|
||||
p[2] = (unsigned char)(v >> 16); p[3] = (unsigned char)(v >> 24);
|
||||
}
|
||||
|
||||
/* 合成 BMP24(stb 可解);填一点非零像素,避免全 0 触发任何「透明」特判。 */
|
||||
static int write_bmp24(const char *path, int w, int h, unsigned seed)
|
||||
{
|
||||
int stride = ((w * 3 + 3) / 4) * 4;
|
||||
@@ -117,11 +127,23 @@ static void pump_ms(int ms)
|
||||
}
|
||||
}
|
||||
|
||||
/* 单击(越过双击判定窗口,确保单击动作生效)。 */
|
||||
static void tap(int x, int y)
|
||||
{
|
||||
reader_on_touch_down(x, y);
|
||||
reader_on_tap(x, y);
|
||||
pump_ms(300); /* 越过双击判定窗口,让单击生效 */
|
||||
pump_ms(300);
|
||||
}
|
||||
|
||||
/* 真双击:两次点击间隔 < MR_DOUBLE_MS(240ms)。 */
|
||||
static void double_tap(int x, int y)
|
||||
{
|
||||
reader_on_touch_down(x, y);
|
||||
reader_on_tap(x, y);
|
||||
pump_ms(60);
|
||||
reader_on_touch_down(x, y);
|
||||
reader_on_tap(x, y);
|
||||
pump_ms(120);
|
||||
}
|
||||
|
||||
static void draw_once(void) { meow_ui_paint_full(); }
|
||||
@@ -142,7 +164,21 @@ int main(void)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char p[MR_PATH_MAX];
|
||||
snprintf(p, sizeof p, "%s/p%d.bmp", book, i + 1);
|
||||
if (write_bmp24(p, 600, 800, (unsigned)i) != 0) { printf("SKIP: bmp write failed\n"); return 0; }
|
||||
if (write_bmp24(p, PAGE_W, PAGE_H, (unsigned)i) != 0) {
|
||||
printf("SKIP: bmp write failed\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
char book2[MR_PATH_MAX];
|
||||
snprintf(book2, sizeof book2, "./mr_perf_books_%ld/v2", (long)getpid());
|
||||
if (mkdir_p(book2) != 0) { printf("SKIP: mkdir failed\n"); return 0; }
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char p[MR_PATH_MAX];
|
||||
snprintf(p, sizeof p, "%s/q%d.bmp", book2, i + 1);
|
||||
if (write_bmp24(p, 800, 1280, (unsigned)(i + 7)) != 0) {
|
||||
printf("SKIP: bmp write failed\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
@@ -154,28 +190,28 @@ int main(void)
|
||||
meow_view_page_push(ACT_LIBRARY);
|
||||
|
||||
int w = meow_ui_width();
|
||||
int fit_x = w - 10 - 48 * 3 + 24 + 48 * 2; /* 顶栏第 3 个动作 = fit */
|
||||
int mode_x = w - 10 - 48 * 3 + 24; /* 顶栏动作 0 = 模式 */
|
||||
int fit_x = mode_x + 48 * 2; /* 动作 2 = fit */
|
||||
int bar_y = 28;
|
||||
|
||||
/* ---- P1 进入:适宽 480x640 == 解码尺寸 → 纯 blit ---- */
|
||||
/* ---- P1 进入(适宽):解码 480x768 == 显示框 → 纯 blit ---- */
|
||||
mr_reader_open_dir(book);
|
||||
CHECK(mr_reader_ui_count() == 4, "book pages=%d", mr_reader_ui_count());
|
||||
pump_ms(700);
|
||||
pump_ms(800);
|
||||
draw_once();
|
||||
int b0 = mr_reader_ui_blit_draws(), s0 = mr_reader_ui_scaled_draws();
|
||||
draw_once();
|
||||
CHECK(mr_reader_ui_blit_draws() > b0, "page drawn with native blit");
|
||||
CHECK(mr_reader_ui_blit_draws() > b0, "paged: page drawn with native blit");
|
||||
CHECK(mr_reader_ui_scaled_draws() == s0,
|
||||
"fit-width: zero resample per frame (scaled %d -> %d)",
|
||||
"paged fit-width: zero resample (scaled %d -> %d)",
|
||||
s0, mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P2 连续翻页:预取命中,仍零重采样 ---- */
|
||||
/* ---- P2 连续翻页 ---- */
|
||||
int bt = mr_reader_ui_blit_draws(), st = mr_reader_ui_scaled_draws();
|
||||
int pages_ok = 1;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int before = mr_reader_ui_page();
|
||||
/* RTL 默认:左 1/3=下一页、右 1/3=上一页;来回翻避免撞到首末页的册间衔接。 */
|
||||
tap(i % 2 ? w - 40 : 40, 300);
|
||||
tap(i % 2 == 0 ? 40 : w - 40, 300); /* RTL:左 1/3=下一页、右 1/3=上一页 */
|
||||
pump_ms(350);
|
||||
if (mr_reader_ui_page() == before) pages_ok = 0;
|
||||
draw_once();
|
||||
@@ -186,31 +222,173 @@ int main(void)
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"flip hot path: zero resample (scaled %d -> %d)", st, mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P3 切「适高」:按新几何重解后再测 ---- */
|
||||
tap(fit_x, bar_y); /* 适宽 → 适高 */
|
||||
pump_ms(600);
|
||||
draw_once();
|
||||
int s2 = mr_reader_ui_scaled_draws();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tap(i % 2 ? w - 40 : 40, 300);
|
||||
pump_ms(350);
|
||||
/* ---- P3 翻页模式跟手拖动(评审实测的 scaled 路径)---- */
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
bt = mr_reader_ui_blit_draws();
|
||||
reader_on_touch_down(400, 300);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
reader_on_touch_move(400 - i * 25, 300);
|
||||
draw_once();
|
||||
}
|
||||
CHECK(mr_reader_ui_scaled_draws() == s2,
|
||||
"fit-height: re-decoded page still zero resample (scaled %d -> %d)",
|
||||
s2, mr_reader_ui_scaled_draws());
|
||||
reader_on_tap(200, 300); /* 松手(未过阈值 → 回弹) */
|
||||
pump_ms(300);
|
||||
draw_once();
|
||||
CHECK(mr_reader_ui_blit_draws() > bt, "paged drag drew pages (%d blits)",
|
||||
mr_reader_ui_blit_draws() - bt);
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"paged drag: zero resample (scaled %d -> %d)", st, mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P4 驻留内存上界 ---- */
|
||||
/* ---- P4 条漫模式:切换后滚动,长宽比 1.6 页也必须零重采样 ---- */
|
||||
tap(mode_x, bar_y);
|
||||
CHECK(mr_reader_ui_mode() == MR_MODE_WEBTOON, "switched to webtoon");
|
||||
pump_ms(800);
|
||||
draw_once();
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
bt = mr_reader_ui_blit_draws();
|
||||
reader_on_touch_down(240, 600);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
reader_on_touch_move(240, 600 - i * 30);
|
||||
draw_once();
|
||||
}
|
||||
reader_on_tap(240, 300);
|
||||
pump_ms(400);
|
||||
draw_once();
|
||||
CHECK(mr_reader_ui_blit_draws() > bt, "webtoon scroll drew pages (%d blits)",
|
||||
mr_reader_ui_blit_draws() - bt);
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"webtoon (1.6 page): zero resample (scaled %d -> %d)", st,
|
||||
mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P5 「先适高、再切条漫」:strip 恒按屏宽排版,解码必须跟随 ---- */
|
||||
tap(mode_x, bar_y); /* 回翻页 */
|
||||
CHECK(mr_reader_ui_mode() == MR_MODE_PAGED, "back to paged");
|
||||
pump_ms(500);
|
||||
tap(fit_x, bar_y); /* 适宽 → 适高 */
|
||||
CHECK(mr_reader_ui_fit() == MR_FIT_HEIGHT, "fit-height selected (got %d)",
|
||||
mr_reader_ui_fit());
|
||||
pump_ms(700);
|
||||
tap(mode_x, bar_y); /* 适高档 → 条漫 */
|
||||
CHECK(mr_reader_ui_mode() == MR_MODE_WEBTOON, "webtoon after fit-height");
|
||||
pump_ms(900);
|
||||
draw_once();
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
bt = mr_reader_ui_blit_draws();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
reader_on_touch_down(240, 600);
|
||||
for (int k = 1; k <= 4; k++) {
|
||||
reader_on_touch_move(240, 600 - k * 30);
|
||||
draw_once();
|
||||
}
|
||||
reader_on_tap(240, 300);
|
||||
pump_ms(250);
|
||||
draw_once();
|
||||
}
|
||||
CHECK(mr_reader_ui_blit_draws() > bt, "webtoon after fit-height drew pages (%d blits)",
|
||||
mr_reader_ui_blit_draws() - bt);
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"fit-height -> webtoon: zero resample (scaled %d -> %d)", st,
|
||||
mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P6 双击放大再缩回(评审 P2-1:缩回后必须按新几何重解)---- */
|
||||
tap(mode_x, bar_y); /* 回翻页 */
|
||||
CHECK(mr_reader_ui_mode() == MR_MODE_PAGED, "paged for zoom test");
|
||||
pump_ms(600);
|
||||
draw_once();
|
||||
double_tap(240, 360); /* 放大 */
|
||||
pump_ms(700);
|
||||
draw_once();
|
||||
double_tap(240, 360); /* 缩回 */
|
||||
pump_ms(800);
|
||||
draw_once();
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
for (int i = 0; i < 5; i++) draw_once();
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"zoom out: page re-decoded at fit geometry, zero resample (scaled %d -> %d)",
|
||||
st, mr_reader_ui_scaled_draws());
|
||||
|
||||
/* ---- P7 静止不抖:无新解码(重解活锁回归,P2-2)---- */
|
||||
pump_ms(300);
|
||||
int d0 = mr_reader_ui_decode_count();
|
||||
for (int i = 0; i < 10; i++) { draw_once(); pump_ms(40); }
|
||||
CHECK(mr_reader_ui_decode_count() == d0,
|
||||
"idle: no extra decodes (%d -> %d)", d0, mr_reader_ui_decode_count());
|
||||
|
||||
/* ---- P8 驻留内存上界 ---- */
|
||||
long kb = mr_reader_ui_resident_kb();
|
||||
printf(" info: resident=%ldKB blit=%d scaled=%d\n", kb,
|
||||
mr_reader_ui_blit_draws(), mr_reader_ui_scaled_draws());
|
||||
printf(" info: 600x960 resident=%ldKB blit=%d scaled=%d decodes=%d\n", kb,
|
||||
mr_reader_ui_blit_draws(), mr_reader_ui_scaled_draws(),
|
||||
mr_reader_ui_decode_count());
|
||||
CHECK(kb <= 9 * 1024, "resident page images bounded (got %ldKB)", kb);
|
||||
|
||||
/* ---- P9 800x1280(评审表格另一档):翻页 + 条漫都必须零重采样 ---- */
|
||||
meow_view_page_pop();
|
||||
mr_reader_open_dir(book2);
|
||||
pump_ms(800);
|
||||
draw_once();
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
bt = mr_reader_ui_blit_draws();
|
||||
reader_on_touch_down(400, 300);
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
reader_on_touch_move(400 - i * 25, 300);
|
||||
draw_once();
|
||||
}
|
||||
reader_on_tap(200, 300);
|
||||
pump_ms(300);
|
||||
draw_once();
|
||||
CHECK(mr_reader_ui_blit_draws() > bt && mr_reader_ui_scaled_draws() == st,
|
||||
"800x1280 paged drag: zero resample (blit +%d, scaled +%d)",
|
||||
mr_reader_ui_blit_draws() - bt, mr_reader_ui_scaled_draws() - st);
|
||||
tap(mode_x, bar_y); /* 条漫 */
|
||||
CHECK(mr_reader_ui_mode() == MR_MODE_WEBTOON, "800x1280 webtoon");
|
||||
pump_ms(800);
|
||||
draw_once();
|
||||
st = mr_reader_ui_scaled_draws();
|
||||
bt = mr_reader_ui_blit_draws();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
reader_on_touch_down(240, 600);
|
||||
for (int k = 1; k <= 4; k++) {
|
||||
reader_on_touch_move(240, 600 - k * 30);
|
||||
draw_once();
|
||||
}
|
||||
reader_on_tap(240, 300);
|
||||
pump_ms(250);
|
||||
draw_once();
|
||||
}
|
||||
CHECK(mr_reader_ui_blit_draws() > bt,
|
||||
"800x1280 webtoon drew pages (%d blits)", mr_reader_ui_blit_draws() - bt);
|
||||
CHECK(mr_reader_ui_scaled_draws() == st,
|
||||
"800x1280 webtoon: zero resample (scaled %d -> %d)", st,
|
||||
mr_reader_ui_scaled_draws());
|
||||
|
||||
meow_view_page_pop();
|
||||
meow_view_free(reader);
|
||||
meow_view_free(lib);
|
||||
meow_ui_shutdown();
|
||||
|
||||
/* ---- P10 大屏(1080x1920):fit 目标 >3MB 被字节上限钳住,不得反复重解(P2-2)---- */
|
||||
meow_ui_opts o2 = { 1080, 1920 };
|
||||
if (meow_ui_init(&o2) == 0) {
|
||||
lib = meow_view_create(ACT_LIBRARY, MEOW_VIEW_ACTIVITY, &lh, NULL);
|
||||
reader = reader_view_create();
|
||||
meow_view_page_push(ACT_LIBRARY);
|
||||
mr_reader_open_dir(book);
|
||||
pump_ms(1200);
|
||||
meow_ui_paint_full();
|
||||
int dbig = mr_reader_ui_decode_count();
|
||||
for (int i = 0; i < 10; i++) { meow_ui_paint_full(); pump_ms(40); }
|
||||
CHECK(mr_reader_ui_decode_count() == dbig,
|
||||
"large screen: clamped decode is stable, no live-lock (%d -> %d)",
|
||||
dbig, mr_reader_ui_decode_count());
|
||||
printf(" info: large screen resident=%ldKB decodes=%d\n",
|
||||
mr_reader_ui_resident_kb(), mr_reader_ui_decode_count());
|
||||
meow_view_page_pop();
|
||||
meow_view_free(reader);
|
||||
meow_view_free(lib);
|
||||
meow_ui_shutdown();
|
||||
}
|
||||
|
||||
rm_rf(book);
|
||||
rm_rf(book2);
|
||||
char rootdir[MR_PATH_MAX];
|
||||
snprintf(rootdir, sizeof rootdir, "./mr_perf_books_%ld", (long)getpid());
|
||||
rm_rf(rootdir);
|
||||
|
||||
+151
-68
@@ -11,9 +11,10 @@
|
||||
* meta() 不解码像素;解码入口走 SDK M12 预算解码 meow_img_load_file_dim(JPEG 解码期缩解,
|
||||
* PNG/BMP 整幅解码后降采样——超大头先 meow_img_probe_file 预判,超过预算跳过并告警)。
|
||||
*
|
||||
* 性能(M17):解码目标 = **当前 fit/zoom 的显示尺寸**(不是屏长边),所以翻页/滚动热路径
|
||||
* 只有原生 blit:不做 meow_ui_img_scaled 的每帧重采样,也不再每帧 malloc 1.2MB 临时缓冲。
|
||||
* fit 变化时窗口按新几何重解一次;拖动/吸附帧只 invalidate 内容区(顶栏静态不重绘)。
|
||||
* 性能(M17):解码目标 = **排版真正要的显示框**(翻页=fit/zoom;条漫=strip 排版高度,
|
||||
* 宽恒为视口宽),不是屏长边,所以翻页/滚动热路径只有原生 blit:不做 meow_ui_img_scaled
|
||||
* 的每帧重采样,也不再每帧 malloc 1MB 级临时缓冲。几何变化(fit/缩放/切模式)时按新几何
|
||||
* 重解一次;拖动/吸附帧只 invalidate 内容区(顶栏静态不重绘)。
|
||||
* 内存:单页显示缓存 ≤3MB、整窗 ≤9MB(超限淘汰最远页),解码中间峰值 >12MB 的页直接跳过
|
||||
* 并显示占位——页数增长不会线性吃内存(常驻只与解码窗大小有关)。
|
||||
* 方向:一律读 M3 的 mr_reader_get_direction()(默认 RTL),不另立全局。
|
||||
@@ -137,6 +138,7 @@ typedef struct page_slot {
|
||||
int idx; /* -1=空 */
|
||||
meow_img *img; /* 已 downscale 的页图 */
|
||||
int src_w, src_h;
|
||||
int want_w, want_h; /* 本次解码按哪个显示框落的(几何判据,见 slot_ready) */
|
||||
} page_slot;
|
||||
static page_slot g_slot[MR_SLOTS];
|
||||
|
||||
@@ -148,8 +150,11 @@ static unsigned char g_page_fail[(MR_BOOK_MAX_PAGES + 7) / 8];
|
||||
typedef struct page_meta { int w, h; } page_meta;
|
||||
static page_meta *g_meta;
|
||||
|
||||
/* 解码预算(屏长边):超过它的显示尺寸无法靠「再解一次」变大,用于避免重解死循环。 */
|
||||
static int g_decode_budget = 720;
|
||||
/* 本次解码计划(主线程在 queue_page 里算好,经 page_req 传给 worker;worker 在
|
||||
* mr_book_open 前写入这几个静态量,解码回调只读它们 → 后台线程不再读 UI 全局几何)。 */
|
||||
static int g_plan_want_w, g_plan_want_h; /* 显示框(几何,未钳制) */
|
||||
static int g_plan_long; /* 实际解码长边(受单页字节上限钳制) */
|
||||
static volatile int g_decode_count; /* 累计解码次数(观察重解抖动/活锁) */
|
||||
|
||||
static int page_failed(int idx)
|
||||
{
|
||||
@@ -163,9 +168,20 @@ static void page_mark_failed(int idx)
|
||||
g_page_fail[idx >> 3] |= (unsigned char)(1u << (idx & 7));
|
||||
}
|
||||
|
||||
/* ---- 后台解码队列 ---- */
|
||||
typedef struct page_req { int idx; unsigned gen; } page_req;
|
||||
typedef struct page_res { int idx; unsigned gen; meow_img *img; int fail; } page_res;
|
||||
/* ---- 后台解码队列(请求携带解码几何,结果带回同一条几何) ---- */
|
||||
typedef struct page_req {
|
||||
int idx;
|
||||
unsigned gen;
|
||||
int want_w, want_h; /* 按哪个显示框解码(主线程算) */
|
||||
int long_side; /* 实际解码长边(≤ 单页字节上限) */
|
||||
} page_req;
|
||||
typedef struct page_res {
|
||||
int idx;
|
||||
unsigned gen;
|
||||
meow_img *img;
|
||||
int fail;
|
||||
int want_w, want_h;
|
||||
} page_res;
|
||||
|
||||
static pthread_t g_worker;
|
||||
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -244,22 +260,17 @@ static int path_is_jpeg(const char *path)
|
||||
return strcasecmp(dot, "jpg") == 0 || strcasecmp(dot, "jpeg") == 0;
|
||||
}
|
||||
|
||||
/* 目标解码长边:优先=当前 fit/zoom 的显示尺寸(解码后可直接 blit,翻页零重采样),
|
||||
* 再受屏长边预算与单页字节上限约束。pw/ph=原始尺寸。 */
|
||||
static int decode_long_side(int pw, int ph, int budget)
|
||||
/* 由显示框(want_w×want_h)反推解码长边:默认就解到显示框大小(解码后可直接 blit),
|
||||
* 只有单页缓存字节上限(MR_PAGE_BYTES_CAP)是硬上界——超长条漫在这里按比例收缩。
|
||||
* 不再钳到屏长边:条漫页常见长宽比 1.6~1.8,显示高度本来就 > 屏长边,钳了必然每帧重采样。 */
|
||||
static int plan_decode_long(int want_w, int want_h)
|
||||
{
|
||||
int tw = 0, th = 0;
|
||||
int long0 = pw > ph ? pw : ph;
|
||||
int short0 = pw > ph ? ph : pw;
|
||||
if (mr_nav_fit_size(g_fit, pw, ph, g_vw, g_vh, &tw, &th) != 0)
|
||||
return budget;
|
||||
if (g_zoom_pct != 100) mr_nav_zoom_size(tw, th, g_zoom_pct, &tw, &th);
|
||||
int l = tw > th ? tw : th;
|
||||
if (l < 64) l = 64;
|
||||
if (budget > 0 && l > budget) l = budget;
|
||||
/* 单页缓存上限:长边按比例收到能装下为止(高度极大的长条漫走这里)。 */
|
||||
int l0 = want_w > want_h ? want_w : want_h;
|
||||
int s0 = want_w > want_h ? want_h : want_w;
|
||||
if (l0 <= 0) return 0;
|
||||
int l = l0 < 64 ? 64 : l0;
|
||||
while (l > 64) {
|
||||
int s = (int)((long long)short0 * l / long0);
|
||||
int s = (int)((long long)s0 * l / l0);
|
||||
if ((long long)l * s * 4 <= MR_PAGE_BYTES_CAP) break;
|
||||
l = l * 3 / 4;
|
||||
}
|
||||
@@ -271,30 +282,37 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||
(void)ud;
|
||||
if (!path || !path[0]) return NULL;
|
||||
|
||||
/* 解码计划由 queue_page() 在主线程算好(want 显示框 + 实际长边),worker/主线程同步兜底
|
||||
* 路径在 mr_book_open 前写入;无计划时退回旧预算 maxdim。 */
|
||||
int want_w = g_plan_want_w, want_h = g_plan_want_h;
|
||||
int dim = g_plan_long > 0 ? g_plan_long : maxdim;
|
||||
|
||||
/* 预算预判(M12):先读文件头拿原始尺寸,不解码像素。
|
||||
* - 解码中间缓冲峰值超上限 → 跳过(页面显示占位),避免真机被 OOM 杀掉(闪退);
|
||||
* - 峰值 = 解码缓冲 + 随后的 downscale 输出(≤单页缓存上限),两者会同时驻留;
|
||||
* - 非缩解格式(PNG/BMP)原始 RGBA 超过告警线 → 记 WARN(残余风险)。 */
|
||||
int pw = 0, ph = 0, ch = 0;
|
||||
int have = meow_img_probe_file(path, &pw, &ph, &ch) && pw > 0 && ph > 0;
|
||||
int dim = maxdim;
|
||||
if (have) {
|
||||
int l0 = pw > ph ? pw : ph;
|
||||
dim = decode_long_side(pw, ph, maxdim);
|
||||
long long peak;
|
||||
long long out_bytes = MR_PAGE_BYTES_CAP; /* downscale/缩解输出上限 */
|
||||
if (want_w > 0 && want_h > 0 && (long long)want_w * want_h * 4 < out_bytes)
|
||||
out_bytes = (long long)want_w * want_h * 4;
|
||||
long long decode_bytes;
|
||||
if (path_is_jpeg(path)) {
|
||||
/* 估 stbi 缩解后的输出(与 SDK 的档位选择同式):仍超上限才跳过。 */
|
||||
int s = 1;
|
||||
while (s < 8 && (l0 / (s * 2)) >= dim) s *= 2;
|
||||
peak = (long long)(pw / s) * (ph / s) * 4;
|
||||
decode_bytes = (long long)(pw / s) * (ph / s) * 4;
|
||||
} else {
|
||||
peak = (long long)pw * ph * 4; /* 整幅解码后降采样 */
|
||||
if (peak > MR_LOAD_RAW_WARN)
|
||||
decode_bytes = (long long)pw * ph * 4; /* 整幅解码后降采样 */
|
||||
if (decode_bytes > MR_LOAD_RAW_WARN)
|
||||
meow_log(MEOW_LOG_WARN, "reader decode large non-scalable %dx%d (%lldKB): %s",
|
||||
pw, ph, peak / 1024, path);
|
||||
pw, ph, decode_bytes / 1024, path);
|
||||
}
|
||||
if (peak > MR_DECODE_PEAK_CAP) {
|
||||
if (decode_bytes + out_bytes > MR_DECODE_PEAK_CAP) {
|
||||
meow_log(MEOW_LOG_WARN, "reader decode skip %dx%d (peak %lldKB > cap): %s",
|
||||
pw, ph, peak / 1024, path);
|
||||
pw, ph, (decode_bytes + out_bytes) / 1024, path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -402,6 +420,21 @@ static void page_target_size_at(int idx, int *w, int *h)
|
||||
if (g_zoom_pct != 100) mr_nav_zoom_size(*w, *h, g_zoom_pct, w, h);
|
||||
}
|
||||
|
||||
/* 解码/绘制的目标显示框:条漫 = strip 排版几何(宽恒为视口宽、高由排版表定),
|
||||
* 翻页 = fit/zoom。解码计划与「缓存是否就绪」必须用同一个函数:否则解出来的尺寸
|
||||
* 和排版要的尺寸不同源,条漫长页(长宽比 > 屏高/屏宽)就每帧重采样(M17 评审 P1-1)。 */
|
||||
static void page_geometry_at(int idx, int *w, int *h)
|
||||
{
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
if (!w || !h) return;
|
||||
if (g_mode == MR_MODE_WEBTOON && g_strip_ok) {
|
||||
int ph = mr_strip_page_h(&g_strip, idx);
|
||||
if (ph > 0) { *w = g_vw; *h = ph; return; }
|
||||
}
|
||||
page_target_size_at(idx, w, h);
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* 续读状态(逐册 last page + mode;写入节流,避免滚动时频繁写卡) */
|
||||
/* ============================================================ */
|
||||
@@ -489,6 +522,7 @@ static void slot_clear(int i)
|
||||
g_slot[i].img = 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;
|
||||
}
|
||||
|
||||
static void slots_free_all(void)
|
||||
@@ -496,22 +530,20 @@ static void slots_free_all(void)
|
||||
for (int i = 0; i < MR_SLOTS; i++) slot_clear(i);
|
||||
}
|
||||
|
||||
/* 缓存图是否就是当前 fit/zoom 的显示尺寸(可直接 blit)。已达解码预算的图也算就绪,
|
||||
* 否则「原始/放大」档会陷入「解不出目标尺寸 → 反复重解」。 */
|
||||
/* 缓存图是否按「当前显示框」解出来的:比较 slot 记录的 want 几何与当前几何。
|
||||
* 用几何(而不是 src 像素尺寸)判定,才能同时满足三件事:
|
||||
* - fit/zoom/模式变化 → 几何变 → 重解一次(放大再缩回、适高→条漫都能回到纯 blit);
|
||||
* - 解码被单页字节上限钳住(超长条漫/大屏)→ want 不变 → 不再反复重解(无活锁);
|
||||
* - 源图本身小于显示框(SDK 不做上采样)→ want 不变 → 不会每 tick 重排。 */
|
||||
static int slot_ready(int idx)
|
||||
{
|
||||
page_slot *s = slot_find(idx);
|
||||
if (!s) return 0;
|
||||
int tw = 0, th = 0;
|
||||
page_target_size_at(idx, &tw, &th);
|
||||
if (tw <= 0 || th <= 0) return 1;
|
||||
if (abs(tw - s->src_w) <= MR_SLOT_TOL && abs(th - s->src_h) <= MR_SLOT_TOL) return 1;
|
||||
if (g_decode_budget > 0 &&
|
||||
(s->src_w >= g_decode_budget || s->src_h >= g_decode_budget)) return 1;
|
||||
/* 已是原始分辨率(小图不放大,SDK 不做上采样):这就是能拿到的最好结果。 */
|
||||
int mw = 0, mh = 0;
|
||||
if (page_meta_dims(idx, &mw, &mh) && s->src_w >= mw && s->src_h >= mh) return 1;
|
||||
return 0;
|
||||
int w = 0, h = 0;
|
||||
page_geometry_at(idx, &w, &h);
|
||||
if (w <= 0 || h <= 0) return 1;
|
||||
if (s->want_w <= 0 || s->want_h <= 0) return 0;
|
||||
return abs(w - s->want_w) <= MR_SLOT_TOL && abs(h - s->want_h) <= MR_SLOT_TOL;
|
||||
}
|
||||
|
||||
/* 淘汰离焦点页最远的一张(不淘汰 focus 与 except)。返回被清掉的槽下标,无则 -1。 */
|
||||
@@ -529,8 +561,9 @@ static int slot_evict_far(int except)
|
||||
return far;
|
||||
}
|
||||
|
||||
/* 放进缓存;同页旧图直接替换(fit/zoom 变了要换尺寸)。返回 1=已放入。 */
|
||||
static int slot_put(int idx, meow_img *img)
|
||||
/* 放进缓存;同页旧图直接替换(fit/zoom/模式变了要换尺寸)。返回 1=已放入。
|
||||
* want_w/want_h = 这次解码按哪个显示框落的,交给 slot_ready 做几何判据。 */
|
||||
static int slot_put(int idx, meow_img *img, int want_w, int want_h)
|
||||
{
|
||||
if (!img || idx < 0) return 0;
|
||||
int i = -1, empty = -1;
|
||||
@@ -565,6 +598,8 @@ static int slot_put(int idx, meow_img *img)
|
||||
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].want_w = want_w;
|
||||
g_slot[i].want_h = want_h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -630,10 +665,19 @@ static void queue_page(int idx)
|
||||
{
|
||||
if (!mr_book_valid(&g_book) || idx < 0 || idx >= g_count) return;
|
||||
if (slot_ready(idx) || page_failed(idx)) return;
|
||||
/* 几何在这里(主线程)算好塞进请求:worker 不再读 g_fit/g_zoom/g_vw/g_vh。 */
|
||||
int want_w = 0, want_h = 0;
|
||||
page_geometry_at(idx, &want_w, &want_h);
|
||||
if (want_w <= 0 || want_h <= 0) { want_w = g_vw; want_h = g_vh; }
|
||||
int long_side = plan_decode_long(want_w, want_h);
|
||||
pthread_mutex_lock(&g_lock);
|
||||
if (!g_stop && !req_queued(idx) && g_req_n < MR_REQ_MAX) {
|
||||
g_req[g_req_tail].idx = idx;
|
||||
g_req[g_req_tail].gen = g_gen;
|
||||
page_req *r = &g_req[g_req_tail];
|
||||
r->idx = idx;
|
||||
r->gen = g_gen;
|
||||
r->want_w = want_w;
|
||||
r->want_h = want_h;
|
||||
r->long_side = long_side;
|
||||
g_req_tail = (g_req_tail + 1) % MR_REQ_MAX;
|
||||
g_req_n++;
|
||||
pthread_cond_signal(&g_cv);
|
||||
@@ -654,13 +698,19 @@ static void *reader_worker(void *arg)
|
||||
g_req_n--;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
|
||||
/* 解码计划:本线程私有(单 worker),解码回调只读这三个量。 */
|
||||
g_plan_want_w = req.want_w;
|
||||
g_plan_want_h = req.want_h;
|
||||
g_plan_long = req.long_side;
|
||||
uint64_t t0 = meow_ui_time_ms();
|
||||
meow_img *img = mr_book_open(&g_book, req.idx);
|
||||
uint64_t dt = meow_ui_time_ms() - t0;
|
||||
g_decode_count++;
|
||||
int w = img ? meow_img_w(img) : 0;
|
||||
int h = img ? meow_img_h(img) : 0;
|
||||
meow_log(MEOW_LOG_INFO, "reader decode page=%d ms=%llu %dx%d %s",
|
||||
req.idx, (unsigned long long)dt, w, h, img ? "ok" : "fail");
|
||||
meow_log(MEOW_LOG_INFO, "reader decode page=%d want=%dx%d long=%d ms=%llu %dx%d %s",
|
||||
req.idx, req.want_w, req.want_h, req.long_side,
|
||||
(unsigned long long)dt, w, h, img ? "ok" : "fail");
|
||||
|
||||
pthread_mutex_lock(&g_lock);
|
||||
if (!g_stop && g_res_n < MR_RES_MAX) {
|
||||
@@ -668,6 +718,8 @@ static void *reader_worker(void *arg)
|
||||
g_res[g_res_n].gen = req.gen;
|
||||
g_res[g_res_n].img = img;
|
||||
g_res[g_res_n].fail = img ? 0 : 1;
|
||||
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) */
|
||||
}
|
||||
@@ -710,7 +762,12 @@ static void queue_prune(void)
|
||||
int n = 0;
|
||||
for (int i = 0; i < g_req_n; i++) {
|
||||
page_req r = g_req[(g_req_head + i) % MR_REQ_MAX];
|
||||
if (r.gen == g_gen && in_window(r.idx)) keep[n++] = r;
|
||||
if (r.gen != g_gen || !in_window(r.idx)) continue;
|
||||
/* 几何已变的请求作废(否则会按旧显示框解出来又被判过期)。 */
|
||||
int w = 0, h = 0;
|
||||
page_geometry_at(r.idx, &w, &h);
|
||||
if (abs(w - r.want_w) > MR_SLOT_TOL || abs(h - r.want_h) > MR_SLOT_TOL) continue;
|
||||
keep[n++] = r;
|
||||
}
|
||||
for (int i = 0; i < n; i++) g_req[i] = keep[i];
|
||||
g_req_head = 0;
|
||||
@@ -739,7 +796,8 @@ static int results_drain(void)
|
||||
continue;
|
||||
}
|
||||
if (in_window(got[i].idx)) {
|
||||
if (slot_put(got[i].idx, got[i].img) && page_visible(got[i].idx)) changed = 1;
|
||||
if (slot_put(got[i].idx, got[i].img, got[i].want_w, got[i].want_h) &&
|
||||
page_visible(got[i].idx)) changed = 1;
|
||||
} else {
|
||||
meow_img_free(got[i].img);
|
||||
}
|
||||
@@ -747,13 +805,12 @@ static int results_drain(void)
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* 窗口随视口/当前页移动:淘汰窗口外图 → 清掉窗口外请求 → 按优先级补页。 */
|
||||
/* 窗口随视口/当前页移动:淘汰窗口外图 → 清掉窗口外请求 → 按优先级补页。 */
|
||||
static void window_update(void)
|
||||
{
|
||||
window_compute();
|
||||
for (int i = 0; i < MR_SLOTS; i++)
|
||||
if (g_slot[i].idx >= 0 && !in_window(g_slot[i].idx)) slot_clear(i);
|
||||
/* 不主动清窗口外的图:多留一页「回看缓存」,避免 0→1→0 来回翻时反复重解同一页。
|
||||
* 容量仍由 MR_SLOTS 与 slot_put 的字节淘汰兜底(最多 MR_SLOTS 张)。 */
|
||||
queue_prune();
|
||||
for (int i = 0; i < g_win_n; i++) queue_page(g_win_idx[i]);
|
||||
}
|
||||
@@ -862,17 +919,30 @@ static void clamp_pan(void)
|
||||
}
|
||||
|
||||
/* 内容是否超出视口(原始档的方图/宽图即使不放大也需要平移)。 */
|
||||
static int content_overflow(void)
|
||||
/* 横向是否真的溢出(横向平移才有意义)。适宽长图只会在**竖向**溢出:那属于「页面比
|
||||
* 屏幕略高」,不能因此把左右 tap / 横拖从翻页改成平移——否则 1.5 以上长宽比的页
|
||||
* 在适宽档完全翻不动(水平平移量本就是 0,点击变空操作)。 */
|
||||
static int content_h_overflow(void)
|
||||
{
|
||||
int w, h;
|
||||
int w = 0, h = 0;
|
||||
current_geom(&w, &h);
|
||||
return w > g_vw || h > g_vh;
|
||||
return w > g_vw;
|
||||
}
|
||||
|
||||
/* 平移模式:放大,或原始档溢出。此时单指拖动=平移,左右 tap=平移步进。 */
|
||||
/* 平移模式:放大、横向溢出(原始档宽图 / 适高宽图)、或原始档任意溢出。
|
||||
* 此时单指拖动=平移,左右 tap=平移步进。
|
||||
* 适宽/适高的**竖向单独溢出**不算(适宽长图常见:1.6 比例页在 480x720 上高 768);
|
||||
* 若把它算成平移,左右 tap / 横拖会变成「水平平移 0px」的空操作 —— 页面直接翻不动。 */
|
||||
static int pan_mode(void)
|
||||
{
|
||||
return g_zoom_pct > 100 || content_overflow();
|
||||
if (g_zoom_pct > 100) return 1;
|
||||
if (content_h_overflow()) return 1;
|
||||
if (g_fit == MR_FIT_ORIGINAL) {
|
||||
int w = 0, h = 0;
|
||||
current_geom(&w, &h);
|
||||
return h > g_vh;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 画一页(带水平位移);解码尺寸==显示尺寸时走原生 blit(翻页热路径零重采样),
|
||||
@@ -1373,8 +1443,7 @@ static int open_book(const char *dir, int start_page, int mode)
|
||||
if (dir != g_dir) snprintf(g_dir, sizeof g_dir, "%s", dir);
|
||||
g_mode = mr_mode_valid(mode) ? mode : MR_MODE_PAGED;
|
||||
|
||||
int maxdim = g_vw > g_vh ? g_vw : g_vh;
|
||||
g_decode_budget = maxdim;
|
||||
int maxdim = g_vw > g_vh ? g_vw : g_vh; /* 仅作无解码计划时的兜底预算 */
|
||||
/* 目录 → 目录册;.cbz/.zip → 档案册(M11)。两者同一个 mr_book 契约,
|
||||
* 下面的翻页/条漫消费代码完全不区分 provider。 */
|
||||
int n = mr_book_open_auto(&g_book, &g_handle, g_dir, maxdim,
|
||||
@@ -1424,8 +1493,14 @@ static int open_book(const char *dir, int start_page, int mode)
|
||||
worker_start();
|
||||
if (!g_worker_on && g_count > 0) {
|
||||
/* 线程创建失败:退化为主线程同步解起始页(保证不是纯白屏)。 */
|
||||
int ww = 0, wh = 0;
|
||||
page_geometry_at(g_page, &ww, &wh);
|
||||
if (ww <= 0 || wh <= 0) { ww = g_vw; wh = g_vh; }
|
||||
g_plan_want_w = ww;
|
||||
g_plan_want_h = wh;
|
||||
g_plan_long = plan_decode_long(ww, wh);
|
||||
meow_img *first = mr_book_open(&g_book, g_page);
|
||||
if (first) slot_put(g_page, first);
|
||||
if (first) slot_put(g_page, first, ww, wh);
|
||||
meow_log(MEOW_LOG_WARN, "reader worker unavailable, sync first page");
|
||||
}
|
||||
slider_build();
|
||||
@@ -1954,6 +2029,12 @@ static void reader_state_reset(void)
|
||||
g_tap_pending = 0;
|
||||
g_last_tap_ms = 0;
|
||||
g_quitting = 0;
|
||||
/* 本册状态一律清空:g_meta 由 open_book 按新册重建(on_enter 早退也不串上一册);
|
||||
* g_start_at 已在 reader_page_enter 入口取走,这里复位防残留。 */
|
||||
free(g_meta);
|
||||
g_meta = NULL;
|
||||
g_start_at = MR_READER_START_RESUME;
|
||||
g_plan_want_w = g_plan_want_h = g_plan_long = 0;
|
||||
}
|
||||
|
||||
static void reader_page_enter(meow_view *v, void *ctx)
|
||||
@@ -1962,6 +2043,7 @@ static void reader_page_enter(meow_view *v, void *ctx)
|
||||
c->active = 1;
|
||||
g_quitting = 0;
|
||||
|
||||
int start_at = g_start_at; /* 显式起始页只在本次进入生效 */
|
||||
g_vw = meow_ui_width();
|
||||
g_vh = meow_ui_height();
|
||||
v->x = 0;
|
||||
@@ -2001,13 +2083,11 @@ static void reader_page_enter(meow_view *v, void *ctx)
|
||||
}
|
||||
|
||||
/* 打开册:显式起始页(书库文件管理器模式点第 N 张)优先于续读记录;
|
||||
* 否则恢复该册上次的模式与页码(无记录 → 翻页模式第 1 页)。 */
|
||||
* 否则恢复该册上次的模式与页码(无记录 → 翻页模式第 1 页)。
|
||||
* 显式起始页在入口处取走(start_at 局部量),state_reset 之后仍有效。 */
|
||||
int mode = mr_cont_get_mode(g_dir, MR_MODE_PAGED);
|
||||
int page = mr_cont_get_page(g_dir, 0);
|
||||
if (g_start_at != MR_READER_START_RESUME) {
|
||||
page = g_start_at;
|
||||
g_start_at = MR_READER_START_RESUME;
|
||||
}
|
||||
int page = start_at != MR_READER_START_RESUME ? start_at
|
||||
: mr_cont_get_page(g_dir, 0);
|
||||
open_book(g_dir, page, mode);
|
||||
|
||||
ticker_start();
|
||||
@@ -2113,4 +2193,7 @@ int mr_reader_ui_failed_pages(void)
|
||||
}
|
||||
int mr_reader_ui_blit_draws(void) { return g_blit_draws; }
|
||||
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; }
|
||||
#endif
|
||||
@@ -33,6 +33,9 @@ long mr_reader_ui_resident_kb(void);/* 驻留页图字节(KB) */
|
||||
int mr_reader_ui_failed_pages(void);/* 解码失败/超预算被跳过的页数 */
|
||||
int mr_reader_ui_blit_draws(void); /* 累计原生 blit 绘页次数(热路径应为纯 blit) */
|
||||
int mr_reader_ui_scaled_draws(void);/* 累计重采样绘页次数(每帧重采样=卡顿) */
|
||||
int mr_reader_ui_decode_count(void);/* 累计解码次数(查重解抖动/活锁) */
|
||||
int mr_reader_ui_fit(void); /* 当前 fit 档(MR_FIT_*) */
|
||||
int mr_reader_ui_chrome(void); /* 1=顶栏可见 */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in new issue
Block a user