- 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 注释更正。
621 lines
25 KiB
C
621 lines
25 KiB
C
/* 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;
|
||
}
|