图库: 修条漫长页/竖翻每帧重采样(解码几何对齐排版)+ 判据与活锁(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:
AskaEth committed 2026-11-05 18:28:52 +08:00
1 parent 462cfd5bc0
commit ceb65dfd01
4 files changed
+379 -101

No files matched your search

+14
View File
@@ -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
View File
@@ -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);