/* test_reader_perf.c — 阅读页翻页热路径回归(需要 SDK 源码树,宿主跑真实 view/anim/img)。 * * 用户反馈:「翻页很卡,横翻竖翻都卡」「40 页也卡」。真因是每帧一次 meow_ui_img_scaled: * 解码尺寸(maxdim=屏长边) ≠ 显示尺寸(适宽/适高) → 每帧重采样 + 每帧 1.2MB 临时缓冲 * malloc/free。修法:解码期直接按显示尺寸落地,翻页只做原生 blit。 * * 本测试用 600x800(3:4)合成 BMP 当页图,断言: * P1 适宽:解码 480x640 == 显示框 → 绘页零重采样(scaled_draws 不增长); * P2 连续翻页 10 次(预取窗口命中):每帧仍零重采样,且确实画了页(blit 增长); * P3 切「适高」:重解到 540x720 后同样零重采样(fit 变化会按新几何重解,不残留缩放); * P4 驻留内存有上界(<= 9MB:3~4 张显示尺寸页)。 * * 编译: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 #include #include #include #include #include void mr_nav_report(void) {} static int g_fail; #define CHECK(cond, ...) do { \ if (cond) { printf(" PASS\n"); } \ else { g_fail++; printf(" FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ } while (0) static void put16(unsigned char *p, unsigned v) { p[0] = (unsigned char)v; p[1] = (unsigned char)(v >> 8); } static void put32(unsigned char *p, unsigned v) { p[0] = (unsigned char)v; p[1] = (unsigned char)(v >> 8); 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; int imgsz = stride * h; unsigned char fh[14] = { 0 }, ih[40] = { 0 }; fh[0] = 'B'; fh[1] = 'M'; put32(fh + 2, (unsigned)(54 + imgsz)); put32(fh + 10, 54); put32(ih + 0, 40); put32(ih + 4, (unsigned)w); put32(ih + 8, (unsigned)h); put16(ih + 12, 1); put16(ih + 14, 24); put32(ih + 20, (unsigned)imgsz); FILE *f = fopen(path, "wb"); if (!f) return -1; int ok = fwrite(fh, 1, sizeof fh, f) == sizeof fh && fwrite(ih, 1, sizeof ih, f) == sizeof ih; unsigned char *row = (unsigned char *)calloc(1, (size_t)stride); if (!row) ok = 0; for (int y = 0; ok && y < h; y++) { for (int x = 0; x < w; x++) { row[x * 3 + 0] = (unsigned char)((x + seed) & 0xFF); row[x * 3 + 1] = (unsigned char)((y * 3 + seed) & 0xFF); row[x * 3 + 2] = (unsigned char)((x ^ y) & 0xFF); } if (fwrite(row, 1, (size_t)stride, f) != (size_t)stride) ok = 0; } free(row); if (fclose(f) != 0) ok = 0; return ok ? 0 : -1; } static int mkdir_p(const char *path) { char tmp[MR_PATH_MAX]; snprintf(tmp, sizeof tmp, "%s", path); for (size_t i = 1; i <= strlen(tmp); i++) { if (tmp[i] == '/' || tmp[i] == '\0') { char ch = tmp[i]; tmp[i] = '\0'; if (mkdir(tmp, 0755) != 0) { struct stat st; if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)) return -1; } tmp[i] = ch; } } return 0; } static int rm_rf(const char *path) { struct stat st; if (lstat(path, &st) != 0) return -1; if (!S_ISDIR(st.st_mode)) return unlink(path); DIR *d = opendir(path); if (!d) return -1; struct dirent *de; while ((de = readdir(d)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; char full[MR_PATH_MAX]; snprintf(full, sizeof full, "%s/%s", path, de->d_name); rm_rf(full); } closedir(d); return rmdir(path); } static void pump_ms(int ms) { for (int i = 0; i < ms / 10; i++) { usleep(10 * 1000); meow_anim_tick(); } } static void tap(int x, int y) { reader_on_touch_down(x, y); reader_on_tap(x, y); pump_ms(300); /* 越过双击判定窗口,让单击生效 */ } static void draw_once(void) { meow_ui_paint_full(); } int main(void) { const char *base = getenv("TMPDIR"); if (!base || !base[0]) base = "."; char tmpl[512]; snprintf(tmpl, sizeof tmpl, "%s/mr_perf_XXXXXX", base); char *data = mkdtemp(tmpl); if (!data) { printf("SKIP: mkdtemp failed\n"); return 0; } setenv("MEOW_DATA_DIR", data, 1); char book[MR_PATH_MAX]; snprintf(book, sizeof book, "./mr_perf_books_%ld/v1", (long)getpid()); if (mkdir_p(book) != 0) { printf("SKIP: mkdir failed\n"); return 0; } 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; } } meow_ui_opts o = { 480, 720 }; if (meow_ui_init(&o) != 0) { printf("SKIP: meow_ui_init failed (no host backend)\n"); return 0; } static const meow_view_hooks lh = { 0 }; meow_view *lib = meow_view_create(ACT_LIBRARY, MEOW_VIEW_ACTIVITY, &lh, NULL); meow_view *reader = reader_view_create(); meow_view_page_push(ACT_LIBRARY); int w = meow_ui_width(); int fit_x = w - 10 - 48 * 3 + 24 + 48 * 2; /* 顶栏第 3 个动作 = fit */ int bar_y = 28; /* ---- P1 进入:适宽 480x640 == 解码尺寸 → 纯 blit ---- */ mr_reader_open_dir(book); CHECK(mr_reader_ui_count() == 4, "book pages=%d", mr_reader_ui_count()); pump_ms(700); 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_scaled_draws() == s0, "fit-width: zero resample per frame (scaled %d -> %d)", s0, mr_reader_ui_scaled_draws()); /* ---- 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); pump_ms(350); if (mr_reader_ui_page() == before) pages_ok = 0; draw_once(); } CHECK(pages_ok, "10 flips advanced the page each time"); CHECK(mr_reader_ui_blit_draws() - bt >= 10, "flips drew pages (%d blits)", mr_reader_ui_blit_draws() - bt); 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); 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()); /* ---- P4 驻留内存上界 ---- */ 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()); CHECK(kb <= 9 * 1024, "resident page images bounded (got %ldKB)", kb); meow_view_page_pop(); meow_view_free(reader); meow_view_free(lib); meow_ui_shutdown(); rm_rf(book); char rootdir[MR_PATH_MAX]; snprintf(rootdir, sizeof rootdir, "./mr_perf_books_%ld", (long)getpid()); rm_rf(rootdir); rm_rf(data); printf("%s: %s\n", g_fail ? "FAIL" : "PASS", g_fail ? "see failures above" : "reader flip hot-path checks passed"); return g_fail ? 1 : 0; }