From f647a3c4986dcc1b9036d856f77e03b1c00383bf Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 19:01:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E5=BA=93:=20=E4=BF=AE=20M32=20?= =?UTF-8?q?=E8=AF=84=E5=AE=A1=20P1=EF=BC=88plan=5Fdecode=5Flong=20?= =?UTF-8?q?=E4=BB=8D=E6=8C=89=204B/px=EF=BC=89+=20=E8=A1=A5=20480=C3=97853?= =?UTF-8?q?=20=E5=9B=9E=E5=BD=92=EF=BC=88r2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 注释更正。 --- sdk/include/meow_ui_draw.h | 9 +++++-- sdk/meow_ui_gfx.c | 3 ++- tuku/test/test_reader_membudget.c | 22 +++++++++++++---- tuku/test/test_reader_perf.c | 41 +++++++++++++++++++++++++++++++ tuku/ui/reader/reader.c | 25 +++++++++++++------ 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/sdk/include/meow_ui_draw.h b/sdk/include/meow_ui_draw.h index 2c7e5cf..21a2389 100644 --- a/sdk/include/meow_ui_draw.h +++ b/sdk/include/meow_ui_draw.h @@ -54,9 +54,14 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp * 页槽常驻 RGB565(2 字节/像素):真机 480×853 页 1599KB → 800KB。 * 量化与 back_buf 用同一个 565 转换(逐位截断),565 屏上最终像素与 RGBA blit * 路径逐像素一致——不引入色偏、不会比现在多出色带。 - * 命中页(页图尺寸 == 显示尺寸)走 meow_gfx_blit_rgb565:每行一次拷贝,无重采样。 */ + * 命中页(页图尺寸 == 显示尺寸)走 meow_gfx_blit_rgb565:每行一次拷贝,无重采样。 + * + * alpha:565 页图**按不透明**处理(alpha 被忽略)。漫画页解码后 alpha 恒为 255, + * 与旧路径等价;带透明像素的 PNG(调色板/tRNS/color type 6、或 32bpp BMP)会有可见 + * 差异:旧 RGBA 路径把透明像素混合到阅读器底色,565 路径直接画它的 RGB(通常是黑)。 + * 若将来要支持透明页,需要在上屏时按 alpha 混合(而不是在落槽时丢通道)。 */ uint16_t meow_gfx_rgb565(uint32_t rgb); /* 24-bit RGB → 565(与 back_buf 同式) */ -void meow_gfx_rgba_to_rgb565(const uint8_t *rgba, uint16_t *out, int npx); /* 不透明像素 */ +void meow_gfx_rgba_to_rgb565(const uint8_t *rgba, uint16_t *out, int npx); /* 不透明像素(忽略 alpha) */ /* 原生尺寸 blit(不透明):16bpp back_buf 上是每行一次 memcpy。 */ void meow_gfx_blit_rgb565(int x, int y, int w, int h, const uint16_t *px); /* 最近邻缩放 blit(几何不符时的兜底;采样点与 meow_img_resample 同式)。 */ diff --git a/sdk/meow_ui_gfx.c b/sdk/meow_ui_gfx.c index 76c1d2b..59caa86 100644 --- a/sdk/meow_ui_gfx.c +++ b/sdk/meow_ui_gfx.c @@ -861,7 +861,8 @@ void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alp * 量化与 back_buf 用同一个 rgb565()(逐位截断),所以 565 屏上落下的像素与旧的 * RGBA blit 路径**逐像素一致**——不引入色偏,也不会比现在多出色带。 * 命中页(解码/落框尺寸 == 显示尺寸)走 meow_gfx_blit_rgb565:每行一次 memcpy, - * 单次拷贝、没有每帧重采样。alpha 按不透明处理(页图没有透明通道)。 */ + * 单次拷贝、没有每帧重采样。alpha 按不透明处理(页图 alpha 恒为 255;带透明像素的 + * PNG/32bpp BMP 页会与旧 RGBA 路径不同——旧路径混合到底色,这里直接画 RGB)。 */ uint16_t meow_gfx_rgb565(uint32_t rgb) { return rgb565(rgb); diff --git a/tuku/test/test_reader_membudget.c b/tuku/test/test_reader_membudget.c index 48cafa2..b285150 100644 --- a/tuku/test/test_reader_membudget.c +++ b/tuku/test/test_reader_membudget.c @@ -850,14 +850,23 @@ int main(void) size_t new_peak = peak_since(base); uint64_t ms_new = (uint64_t)meow_ui_time_ms() - t_new; + /* 前缀字节数**实测**(zip 层 mz_read_prefix 的写出长度),不是按 4KB 硬算: + * 否则「前缀总字节 < 旧 1/4」只要页 >16KB 就恒真,抓不到前缀读变多。 + * 注意 deflate 条目的 inflate 窗口在 libz 内部(动态库,--wrap 看不到), + * 所以下面 new_peak 是「本进程侧」的堆峰值,不是 zlib 窗口。 */ + unsigned char probe[4096]; + size_t probe_len = 0; + int probe_rc = e0 ? mz_read_prefix((mz_archive *)zh.u.zip.arch, e0, + probe, sizeof probe, &probe_len) : MZ_ERR_ARG; long long bytes_old = 500LL * (long long)(e0 ? e0->uncomp_size : 0); - long long bytes_new = 500LL * 4096; /* 上限:每页最多读 4KB */ + long long bytes_new = 500LL * (long long)probe_len; INFO("CBZ 500 页[%s]:旧=整页解压 %lldkB/页·总%lldkB 峰值%zuKB 耗时%llums;" - "新=前缀 %lldkB/页·总%lldkB 峰值%zuB 耗时%llums", + "新=前缀 %lldB/页·总%lldkB 峰值%zuB 耗时%llums(zlib 窗口在库内、不计入峰值)", deflate ? "deflate" : "stored", e0 ? (long long)e0->uncomp_size / 1024 : 0LL, bytes_old / 1024, old_page_peak / 1024, (unsigned long long)ms_old, - 4LL, bytes_new / 1024, new_peak, (unsigned long long)ms_new); + (long long)probe_len, bytes_new / 1024, new_peak, + (unsigned long long)ms_new); CHECK(meta_ok, "[%s] 500 页 meta 全部探测成功", deflate ? "deflate" : "stored"); CHECK(dim_ok, "[%s] 500 页尺寸都取到 96x96", deflate ? "deflate" : "stored"); CHECK(new_peak <= 4096, @@ -866,8 +875,11 @@ int main(void) CHECK(old_page_peak >= 16 * 1024, "[%s] 对照:旧路径每页分配 ≥16KB(实测 %zuB)", deflate ? "deflate" : "stored", old_page_peak); - CHECK(bytes_new < bytes_old / 4, - "[%s] 前缀读总字节 < 旧路径 1/4(%lldkB vs %lldkB)", + CHECK(probe_rc == MZ_OK && probe_len == 4096, + "[%s] mz_read_prefix 实测只读 4KB(rc=%d, %zuB/页)", + deflate ? "deflate" : "stored", probe_rc, probe_len); + CHECK(bytes_new * 4 < bytes_old, + "[%s] 前缀实测总字节 < 旧路径 1/4(%lldkB vs %lldkB)", deflate ? "deflate" : "stored", bytes_new / 1024, bytes_old / 1024); mr_book_close_auto(&zb, &zh); } diff --git a/tuku/test/test_reader_perf.c b/tuku/test/test_reader_perf.c index 74093c6..5750658 100644 --- a/tuku/test/test_reader_perf.c +++ b/tuku/test/test_reader_perf.c @@ -20,6 +20,8 @@ * 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)。 */ @@ -499,6 +501,45 @@ int main(void) /* ---- 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); diff --git a/tuku/ui/reader/reader.c b/tuku/ui/reader/reader.c index 4b073b3..df97d55 100644 --- a/tuku/ui/reader/reader.c +++ b/tuku/ui/reader/reader.c @@ -76,7 +76,10 @@ #define MR_TICK_MS 16 #define MR_PAINT_MS 16 /* 跟手拖动重绘节流(≈60fps 上限) */ #define MR_DRAG_SLOP 8 /* 小于此位移算点击 */ -#define MR_ZOOM_TEMP_CAP (6 * 1024 * 1024) /* 缩放临时缓冲软上限(>此值降倍率,保内存) */ +/* 缩放倍率软上限(M32:绘制已改 meow_gfx_blit_rgb565_scaled,原地采样不再 malloc 目标尺寸 + * 临时图;这里保留原保守系数——按 4B/px 估「放大后解码目标」的峰值,比 565 口径更严, + * 行为不回退。>此值逐档降倍率,保内存。 */ +#define MR_ZOOM_TEMP_CAP (6 * 1024 * 1024) #define MR_BOOK_PULL 110 /* 条漫到顶/底后再拉这么远,松手进相邻册 */ #define MR_JUMP_TIMEOUT_MS 2500 /* 目标页迟迟解不出来 → 降级落位并提示 */ #define MR_CONT_SAVE_MS 1000 /* 续读状态落盘节流 */ @@ -460,8 +463,10 @@ static void log_mem(const char *tag) /* 页源解码回调(注入 logic 层的 mr_book 契约;可在后台线程调用) */ /* ============================================================ */ /* 由显示框(want_w×want_h)反推解码长边:默认就解到显示框大小(解码后可直接 blit), - * 只有单页缓存字节上限(MR_PAGE_BYTES_CAP)是硬上界——超长条漫在这里按比例收缩。 - * 不再钳到屏长边:条漫页常见长宽比 1.6~1.8,显示高度本来就 > 屏长边,钳了必然每帧重采样。 */ + * 只有单页缓存字节上限(MR_PAGE_BYTES_CAP,565 口径 = 2B/px)是硬上界——超长条漫在这里按比例收缩。 + * 不再钳到屏长边:条漫页常见长宽比 1.6~1.8,显示高度本来就 > 屏长边,钳了必然每帧重采样。 + * M32 评审 P1:这里必须按 565 的 2B/px 算——乘 4 会把真机 480×853 页压到 359×639 + * (每帧重采样),等于把整单的收益吃掉。 */ static int plan_decode_long(int want_w, int want_h) { int l0 = want_w > want_h ? want_w : want_h; @@ -470,7 +475,7 @@ static int plan_decode_long(int want_w, int want_h) int l = l0 < 64 ? 64 : l0; while (l > 64) { int s = (int)((long long)s0 * l / l0); - if ((long long)l * s * 4 <= MR_PAGE_BYTES_CAP) break; + if ((long long)l * s * 2 <= MR_PAGE_BYTES_CAP) break; l = l * 3 / 4; } return l; @@ -497,8 +502,9 @@ static void plan_box_size(int want_w, int want_h, int *bw, int *bh) } /* 固定字段的解码日志:page/src/peak/path 之后是结论,便于 `grep 'reader decode'` - * 后按列看曲线。peak = reader_estimate_peak 的预估峰值(字节/kB);path = 实际解码路径 - * (backend = M23 真缩解后端,stb = 回退旧路径)。 */ + * 后按列看曲线。peak = 本次「取页」预算口径 = reader_estimate_peak 的解码峰值 + * + 落槽 565 暂存(M32:ok/fail/skip 三种结论同一列,便于和 --wrap malloc 实测对照); + * path = 实际解码路径(backend = M23 真缩解后端,stb = 回退旧路径)。 */ static void log_decode(int page, int src_w, int src_h, long long peak_bytes, long memfree, long memavail, const char *verdict, int step, int backend, unsigned long long ms) @@ -577,7 +583,9 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim) &used_backend) : meow_img_load_file_dim(path, dim); if (!im) { - log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "fail", + /* peak 列 = 本次「取页」口径:解码峰值 + 落槽 565 暂存(与 ok/skip 同一列口径)。 */ + log_decode(page, have ? in.w : 0, have ? in.h : 0, + dec.peak_bytes + dec.convert_bytes, mfree, mavail, "fail", dec.step, have && dec.path == MR_DECODE_PATH_BACKEND, (unsigned long long)(meow_ui_time_ms() - t0)); return NULL; @@ -596,7 +604,8 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim) meow_img *fit = meow_img_resample(im, box_w, box_h); if (fit) { meow_img_free(im); im = fit; } } - log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "ok", + log_decode(page, have ? in.w : 0, have ? in.h : 0, + dec.peak_bytes + dec.convert_bytes, mfree, mavail, "ok", dec.step, used_backend, (unsigned long long)(meow_ui_time_ms() - t0)); return im;