Files
MeowOS-SDK/tuku/test/test_reader_perf.c
T
AskaEth f647a3c498 图库: 修 M32 评审 P1(plan_decode_long 仍按 4B/px)+ 补 480×853 回归(r2)
- P1:plan_decode_long 的判据从 l*s*4 改成 l*s*2(MR_PAGE_BYTES_CAP 已是 565 口径)。
  改前真机 480×853 页被压到 359×639(448KB)且每帧重采样;改后原尺寸落槽 799KB、
  绘制走原生 blit(scaled 增量 0)。
- 测试:新增 perf P13 —— 480×853 单页册,断言 slot_kb==480*853*2/1024、resident 相等、
  draw 后 blit 增长且 scaled 增量为 0;现有夹具(480×640/600×960)都在阈值以下,抓不到。
- P2:decode 日志 ok/fail 也打 peak+convert(与 skip 同口径);CBZ 500 页的「前缀字节」
  改成 mz_read_prefix 实测(不再用 500×4096 硬算),并说明 zlib 窗口不计入 --wrap 峰值;
  565 页图 alpha 忽略的行为差异写进 meow_ui_draw.h / gfx 注释;MR_ZOOM_TEMP_CAP 注释更正。
2026-11-06 19:01:16 +08:00

621 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_reader_perf.c — 阅读页绘页热路径回归(需要 SDK 源码树,宿主跑真实 view/anim/img)。
*
* 用户反馈:「翻页很卡,横翻竖翻都卡」「40 页也卡」。真因是每帧一次 meow_ui_img_scaled:
* 解码尺寸 ≠ 排版要的显示尺寸 → 每帧重采样 + 每帧 1MB 级临时缓冲 malloc/free。
* 修法:解码期直接按**排版要的几何**落地(翻页=fit/zoom,条漫=strip 排版高度),翻页只 blit。
*
* 夹具刻意用 **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);
* P12(M32)颜色正确性:页槽改 RGB565 后,
* a) 565 blit 与 RGBA blit 在 565 屏上**逐像素一致**(同一套量化 → 不引入色偏);
* b) 565 量化误差在理论上界内(R/B ≤7、G ≤3),并给出 RMSE 与代表性采样;
* c) 8bit 灰阶梯/肤色渐变单调、无新增色带(与 RGBA 路径的阶梯数完全相同)。
* P13(M32 评审 P1):真机主力几何 480×853 —— 单页按 565 原尺寸落槽(~800KB)、
* 绘制走原生 blit、零重采样(plan_decode_long 曾仍按 4B/px 把它压到 359×639)。
*
* 编译: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 <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
#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);
}
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;
}
/* 颜色测试夹具:mode 0 = 8bit 灰阶梯(x 方向 0→255);mode 1 = 肤色渐变(R/G/B 同向缓降)。 */
static int write_bmp24_grad(const char *path, int w, int h, int mode)
{
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++) {
int r, g, b;
if (mode == 0) {
int v = w > 1 ? x * 255 / (w - 1) : 0;
r = g = b = v;
} else {
r = 235 - (x * 65) / (w > 1 ? w - 1 : 1);
g = 175 - (x * 60) / (w > 1 ? w - 1 : 1);
b = 130 - (x * 45) / (w > 1 ? w - 1 : 1);
}
row[x * 3 + 0] = (unsigned char)b; /* BMP 是 BGR */
row[x * 3 + 1] = (unsigned char)g;
row[x * 3 + 2] = (unsigned char)r;
}
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);
}
/* 真双击:两次点击间隔 < 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);
}
/* 直接切模式(绕过顶栏 tap 的命中时序):本文件只关心绘制/解码,不验证 chrome 命中;
* 顶栏动作本身的回归在 test_mode_switch_host 覆盖。M18 评审:负载下该用例曾偶发。 */
static void mode_toggle(void)
{
mr_reader_test_toggle_mode();
pump_ms(100);
}
static void draw_once(void) { meow_ui_paint_full(); }
/* ---- P12(M32):RGB565 页槽颜色正确性 ----
* 同一张图用两条路径各画一次,逐像素比较 back_buf(565 屏):
* a) RGBA 路径 meow_ui_img → put_px → rgb565()(今天的上屏方式)
* b) 565 路径 meow_gfx_blit_rgb565(页槽新路径)
* 两条路径本该逐像素相同(同一个量化函数);再量 565 相对 8bit 源的量化误差,
* 确认不出界、灰阶梯单调(不新增色带)。 */
static void capture565(int x, int y, int w, int h, uint16_t *out)
{
int pitch = meow_ui_back_buf_pitch();
unsigned char *bb = meow_ui_back_buf();
for (int yy = 0; yy < h; yy++)
for (int xx = 0; xx < w; xx++)
out[(size_t)yy * w + xx] =
*(const uint16_t *)(bb + (size_t)(y + yy) * pitch + (size_t)(x + xx) * 2);
}
static int ex5i(int v) { return (v << 3) | (v >> 2); }
static int ex6i(int v) { return (v << 2) | (v >> 4); }
static void color_check(void)
{
const char *dir = getenv("MEOW_DATA_DIR");
if (!dir || !dir[0]) dir = ".";
if (meow_ui_back_buf_bpp() != 16) {
printf(" SKIP color check: host back_buf is %d bpp (only 565 screens are comparable)\n",
meow_ui_back_buf_bpp());
return;
}
struct { const char *name; int w, h, mode; const char *label; } fx[2] = {
{ "color_gray.bmp", 256, 64, 0, "8bit 灰阶梯 256→0..255" },
{ "color_skin.bmp", 480, 360, 1, "肤色渐变 235,175,130 → 170,115,85" },
};
for (size_t k = 0; k < sizeof fx / sizeof fx[0]; k++) {
char path[MR_PATH_MAX];
snprintf(path, sizeof path, "%s/%s", dir, fx[k].name);
if (write_bmp24_grad(path, fx[k].w, fx[k].h, fx[k].mode) != 0) {
CHECK(0, "颜色夹具写入 %s", fx[k].name);
continue;
}
meow_img *im = meow_img_load_file(path);
if (!im) { CHECK(0, "颜色夹具解码 %s", fx[k].name); continue; }
int w = meow_img_w(im), h = meow_img_h(im);
size_t n = (size_t)w * h;
uint16_t *px = (uint16_t *)malloc(n * 2);
uint16_t *ref = (uint16_t *)malloc(n * 2);
uint16_t *got = (uint16_t *)malloc(n * 2);
if (!px || !ref || !got) {
CHECK(0, "颜色夹具缓冲分配");
free(px); free(ref); free(got); meow_img_free(im); continue;
}
meow_gfx_rgba_to_rgb565(meow_img_pixels(im), px, (int)n);
meow_ui_clear(0x000000);
meow_ui_img(0, 0, im); /* 旧路径:RGBA blit */
capture565(0, 0, w, h, ref);
meow_ui_clear(0x000000);
meow_gfx_blit_rgb565(0, 0, w, h, px); /* 新路径:565 blit(页槽) */
capture565(0, 0, w, h, got);
size_t diff = 0;
for (size_t i = 0; i < n; i++) if (ref[i] != got[i]) diff++;
/* 量化误差:565 展开回 8bit 与源像素比。 */
const unsigned char *rgba = meow_img_pixels(im);
long long se = 0;
int maxrb = 0, maxg = 0;
for (size_t i = 0; i < n; i++) {
int r5 = (got[i] >> 11) & 0x1F, g6 = (got[i] >> 5) & 0x3F, b5 = got[i] & 0x1F;
int er = ex5i(r5) - rgba[i * 4 + 0];
int eg = ex6i(g6) - rgba[i * 4 + 1];
int eb = ex5i(b5) - rgba[i * 4 + 2];
if (abs(er) > maxrb) maxrb = abs(er);
if (abs(eb) > maxrb) maxrb = abs(eb);
if (abs(eg) > maxg) maxg = abs(eg);
se += (long long)er * er + (long long)eg * eg + (long long)eb * eb;
}
double rmse = sqrt((double)se / (double)(n * 3));
/* 灰阶梯:输出必须单调不减,且阶梯数与 RGBA 路径完全一致(没有新增色带)。 */
int mono = 1, lv = 1, lv_ref = 1;
for (int x = 1; x < w; x++) {
uint16_t a = got[(size_t)(h / 2) * w + x], b = got[(size_t)(h / 2) * w + x - 1];
uint16_t ar = ref[(size_t)(h / 2) * w + x], br = ref[(size_t)(h / 2) * w + x - 1];
if (a < b) mono = 0;
if (a != b) lv++;
if (ar != br) lv_ref++;
}
printf(" [P12] %-42s 差异像素=%zu/%zu 误差 max R/B=%d G=%d RMSE=%.2f 阶梯=%d\n",
fx[k].label, diff, n, maxrb, maxg, rmse, lv);
if (fx[k].mode == 0) {
for (int x = 0; x < w; x += w / 8) {
int i = (h / 2) * w + x;
printf(" x=%3d 源灰度=%3d → RGBA路径=0x%04X 565路径=0x%04X\n",
x, rgba[(size_t)i * 4], ref[i], got[i]);
}
} else {
for (int x = 0; x < w; x += w / 4) {
int i = (h / 2) * w + x;
printf(" x=%3d 源 RGB=(%3d,%3d,%3d) → RGBA路径=0x%04X 565路径=0x%04X\n",
x, rgba[(size_t)i * 4], rgba[(size_t)i * 4 + 1], rgba[(size_t)i * 4 + 2],
ref[i], got[i]);
}
}
CHECK(diff == 0, "%s:565 blit 与 RGBA blit 逐像素一致(差异 %zu)", fx[k].label, diff);
CHECK(maxrb <= 7 && maxg <= 3,
"%s:量化误差在 565 理论界内(R/B %d ≤7,G %d ≤3)", fx[k].label, maxrb, maxg);
CHECK(rmse <= 3.5, "%s:RMSE %.2f ≤3.5", fx[k].label, rmse);
if (fx[k].mode == 0) {
CHECK(mono, "%s:灰阶梯输出单调(无色带跳变/反转)", fx[k].label);
CHECK(lv >= 30 && lv == lv_ref,
"%s:阶梯数 %d ≥30 且与 RGBA 路径一致(%d)", fx[k].label, lv, lv_ref);
}
free(px); free(ref); free(got);
meow_img_free(im);
}
}
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, 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 };
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 mode_x = w - 10 - 48 * 3 + 24; /* 顶栏动作 0 = 模式 */
int fit_x = mode_x + 48 * 2; /* 动作 2 = fit */
int bar_y = 28;
/* ---- P1 进入(适宽):解码 480x768 == 显示框 → 纯 blit ---- */
mr_reader_open_dir(book);
CHECK(mr_reader_ui_count() == 4, "book pages=%d", mr_reader_ui_count());
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, "paged: page drawn with native blit");
CHECK(mr_reader_ui_scaled_draws() == s0,
"paged fit-width: zero resample (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();
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();
}
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 翻页模式跟手拖动(评审实测的 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();
}
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 条漫模式:切换后滚动,长宽比 1.6 页也必须零重采样 ---- */
mode_toggle();
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 恒按屏宽排版,解码必须跟随 ---- */
mode_toggle(); /* 回翻页 */
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);
mode_toggle(); /* 适高档 → 条漫 */
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:缩回后必须按新几何重解)---- */
mode_toggle(); /* 回翻页 */
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: 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);
/* ---- P12(M32):565 页槽的颜色正确性(需要 565 屏的 back_buf)---- */
color_check();
/* ---- P13(M32 评审 P1):真机主力几何 480×853 —— 565 下必须**原尺寸落槽 + 单次 blit** ----
* 回归点:plan_decode_long 曾仍按 4B/px 判单页上限,把 480×853 压到 359×639(448KB),
* 于是每一帧都走 meow_gfx_blit_rgb565_scaled(重采样)。断言同时看字节与绘制路径,
* 光校验常量(page_px_cap()==786432)抓不到这个执行侧回归。 */
{
char book4[MR_PATH_MAX], p4[MR_PATH_MAX];
snprintf(book4, sizeof book4, "./mr_perf_books_%ld/v4", (long)getpid());
snprintf(p4, sizeof p4, "%s/t853.bmp", book4);
int ok4 = mkdir_p(book4) == 0 && write_bmp24(p4, 480, 853, 11) == 0;
if (!ok4) {
printf(" SKIP: 480x853 fixture write failed\n");
} else {
meow_view_page_pop(); /* 关掉 600×960 那册 */
mr_reader_open_dir(book4);
pump_ms(900);
draw_once();
int p13 = mr_reader_ui_page();
long slot = mr_reader_ui_slot_kb(p13);
/* 480×853 = 409,440px × 2B = 818,880B = 799.6KB(截断 799;真机口径 ~800KB)。
* 按 4B/px 误算时会被压到 359×639 = 448KB。 */
printf(" info: 480x853 resident=%ldKB slot=%ldKB blit=%d scaled=%d\n",
mr_reader_ui_resident_kb(), slot,
mr_reader_ui_blit_draws(), mr_reader_ui_scaled_draws());
CHECK(mr_reader_ui_count() == 1, "480x853 单页册 count=%d", mr_reader_ui_count());
CHECK(slot == 480 * 853 * 2 / 1024,
"480×853 按 565 原尺寸落槽:%ldKB(约 800KB;误按 4B/px 时只有 448KB)", slot);
CHECK(mr_reader_ui_resident_kb() == slot,
"单页册常驻 = 槽内字节(%ldKB)", mr_reader_ui_resident_kb());
int bl13 = mr_reader_ui_blit_draws(), sc13 = mr_reader_ui_scaled_draws();
draw_once();
CHECK(mr_reader_ui_blit_draws() > bl13,
"480×853 页绘制走原生 565 blit(+%d)",
mr_reader_ui_blit_draws() - bl13);
CHECK(mr_reader_ui_scaled_draws() == sc13,
"480×853 页零重采样(scaled 增量 %d)",
mr_reader_ui_scaled_draws() - sc13);
}
}
/* ---- 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);
mode_toggle(); /* 条漫 */
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);
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;
}