图库: 门控模型接真缩解后端 + _step 全格式后端(M27)
M23 交付了真缩解后端却「修好了用不上」:_step 只接 JPEG,而阅读器的峰值模型
仍是 stb 口径(PNG/BMP 按 W·H·4、JPEG 按缩解后整幅 RGBA),12MP/PNG 页全被判
超预算占位。本次把两处缝焊上:
- sdk/meow_ui_img.c:meow_img_load_file_step 的 JPEG-only 限制放开到 PNG/BMP;
新增 meow_img_load_file_step_ex 回报实际路径(backend/stb),日志可核对预判。
- tuku/logic/reader/page_source.[ch]:新增 reader_estimate_peak(),成为峰值模型 +
预算 + MemAvailable 门控的唯一实现(reader 不再另写一份)。
* 后端命中(JPEG SOF0/1/2、≤3 分量、8bit、N=1..7;PNG 非交错/标准压缩/合法色型;
BMP DIB≥40/BI_RGB/8·16·24·32bpp)→ 峰值 = 目标图 + 分量平面/行缓冲 + 固定开销,
与源分辨率无关 → 直接放行,不门控;
* 只有回退 stb(CMYK/算术编码、Adam7、RLE/1-4bpp、N≥8 小图等)才按 M22 stb 模型
算峰值,超 3MB 或 MemAvailable <6MB 才占位。Adam7 另补 stb 的整幅暂存上界。
- reader:删掉本地 stb 模型/常量,统一调 reader_estimate_peak;日志加 path=backend|stb。
- 测试:6 类典型页(2000×3000 baseline/渐进/restart、1600×2400 PNG/BMP、4MP、12MP)
断言全部 backend + 不占位 + 估算 ≥ 实测且 ≤1.5×(实测 1.03–1.08×);两条路径输出
尺寸逐像素一致;CMYK/Adam7/1bpp 断言走 stb 并按旧门控占位;连翻 30 页无占位、
单页分配峰值 ≤3MB;membudget 链接补 meow_ui_time.c 与 M23 两个解码 TU。
This commit is contained in:
1 parent
668629bda3
commit
1725af631b
9 files changed
+797
-195
No files matched your search
+55
-99
@@ -15,11 +15,14 @@
|
||||
* 宽恒为视口宽),不是屏长边,所以翻页/滚动热路径只有原生 blit:不做 meow_ui_img_scaled
|
||||
* 的每帧重采样,也不再每帧 malloc 1MB 级临时缓冲。几何变化(fit/缩放/切模式)时按新几何
|
||||
* 重解一次;拖动/吸附帧只 invalidate 内容区(顶栏静态不重绘)。
|
||||
* 内存(M17 起;M22 按真机实测重定):单页显示缓存 ≤3MB、整窗常驻 ≤3MB(3 槽,超限淘汰
|
||||
* 最远页),单次解码真实峰值常态 ≤3MB —— 超出时 JPEG 先降缩解档位、PNG/BMP 直接占位
|
||||
* (占位可重试,不是永久失败)。峰值模型见 decode_peak_bytes():它按 stb 的真实分配算
|
||||
* (JPEG 分量面、渐进的 short 系数面、PNG 的源缓冲 + RGBA 转换副本),不再是「把 stb 当
|
||||
* 缩解库」的低估式。余量确实充足(MemFree 富余)时允许放宽到 MR_DECODE_PEAK_HARD。
|
||||
* 内存(M17 起;M22 按真机实测重定;M27 接真缩解后端):单页显示缓存 ≤3MB、整窗常驻
|
||||
* ≤3MB(3 槽,超限淘汰最远页)。解码路径与峰值预算由 page_source 的 reader_estimate_peak()
|
||||
* 统一判定(唯一实现,本文件不再另写模型):
|
||||
* - 真缩解后端(M23)能接住的页:峰值只与目标尺寸相关(≈1.3–2.5MB,与源分辨率无关),
|
||||
* 直接放行,不做内存门控;
|
||||
* - 后端拒绝的页(CMYK/算术编码 JPEG、Adam7 PNG、RLE/1–4bpp BMP、N≥8 小图…)回退 stb:
|
||||
* 按 stb 真实分配模型算峰值,超 3MB 占位 / MemAvailable <6MB 占位。
|
||||
* 占位可重试,不是永久失败。余量确实充足(MemFree 富余)时允许放宽到 12MB。
|
||||
* 页数增长不会线性吃内存(常驻只与解码窗大小有关)。
|
||||
* 方向:一律读 M3 的 mr_reader_get_direction()(默认 RTL),不另立全局。
|
||||
*
|
||||
@@ -74,15 +77,10 @@
|
||||
#define MR_JUMP_TIMEOUT_MS 2500 /* 目标页迟迟解不出来 → 降级落位并提示 */
|
||||
#define MR_CONT_SAVE_MS 1000 /* 续读状态落盘节流 */
|
||||
#define MR_TOAST_MS 1400
|
||||
/* ---- 内存预算(M22,按 R3 Pro II 实测校准:MemTotal 56872kB、空闲 RSS 6044kB、
|
||||
* 杀点 RSS≈15.6MB / 当时 MemFree 仅 1.4MB;M20 宿主实测单页解码尖峰 3.4–35MB)---- */
|
||||
#define MR_DECODE_PEAK_CAP (3L * 1024 * 1024) /* 常态单次解码峰值上限(真实模型;3MB 硬阈值) */
|
||||
#define MR_DECODE_PEAK_HARD (12L * 1024 * 1024) /* 余量充足时的绝对峰值上限(不再超老 12MB 档) */
|
||||
#define MR_MEM_FREE_MARGIN_KB (4L * 1024) /* 放宽档:MemFree 必须比峰值多出这么多 */
|
||||
#define MR_MEM_FLOOR_KB (6L * 1024) /* 解码前门控:MemAvailable 低于此值不解码 */
|
||||
/* ---- 内存预算(M22 校准;M27 起模型与门控只在 page_source.c 的 reader_estimate_peak 里)----
|
||||
* 真机参考:MemTotal 56872kB、空闲 RSS 6044kB、杀点 RSS≈15.6MB / MemFree 仅 1.4MB。 */
|
||||
#define MR_PREFETCH_MIN_KB (12L * 1024) /* 预取邻居要求的最低 MemAvailable */
|
||||
#define MR_SKIP_RETRY_MS 1500 /* 被预算跳过的页:焦点页冷却这么久后自动重试 */
|
||||
#define MR_DECODE_SLACK (64L * 1024) /* 峰值模型余量:结构体/行缓冲/分配器开销(实测 5–6KB) */
|
||||
#define MR_PAGE_BYTES_CAP (3L * 1024 * 1024) /* 单页「显示尺寸」缓存字节上限 */
|
||||
#define MR_RESIDENT_CAP (3L * 1024 * 1024) /* 页图缓存总字节上限(M22 由 9MB 校准为 3MB) */
|
||||
#define MR_SLOT_TOL 2 /* 缓存图与显示框允许的像素偏差(≤2 走原生 blit,不重采样) */
|
||||
@@ -420,60 +418,17 @@ static void plan_box_size(int want_w, int want_h, int *bw, int *bh)
|
||||
*bh = h;
|
||||
}
|
||||
|
||||
/* 真实解码峰值模型(M22)。上一版把 stb 当「缩解库」估 (W/s)(H/s)·4,对 JPEG 低估 3.5–8 倍
|
||||
* (M20 宿主实测:2000×3000 baseline +10.1MB、渐进 +23.5MB、1200×1600 PNG +12.9MB)。
|
||||
* 这里按 stb 的真实分配逐项相加(与 stb_image.h 的分配点对齐):
|
||||
* 1) stb 阶段:中间面 planes + 解码输出缓冲 dst(两者并存到解压结束)
|
||||
* JPEG:planes = 分量面(+渐进系数面),dst = (W/s)(H/s)·4(s>1 时只物化抽取输出)
|
||||
* PNG :planes = 源像素缓冲,dst = W·H·4(ch==4 时同一缓冲,planes 记 0)
|
||||
* BMP :planes = 0,dst = W·H·4(stb 直接写 RGBA 目标)
|
||||
* 2) 精修阶段:dst 超长边上限时再落一张 box 大小的图(dst + finished 并存)
|
||||
* 3) 落框阶段:档位偏粗时把 finished 重采样到显示框(finished + box 并存)
|
||||
* 峰值 = 三段的最大值。返回字节数(不是 kB),参数非法返回 0。 */
|
||||
static long long decode_peak_bytes(const mr_img_info *in, int step, int box_w, int box_h)
|
||||
{
|
||||
if (!in || in->w <= 0 || in->h <= 0 || box_w <= 0 || box_h <= 0) return 0;
|
||||
long long W = in->w, H = in->h;
|
||||
long long box = (long long)box_w * box_h * 4;
|
||||
long long dst;
|
||||
if (in->codec == MR_IMG_CODEC_JPEG) {
|
||||
if (step < 1) step = 1;
|
||||
dst = ((W + step - 1) / step) * ((H + step - 1) / step) * 4;
|
||||
} else {
|
||||
dst = W * H * 4; /* PNG/BMP:无解码期缩放,整幅落地 */
|
||||
}
|
||||
long long peak = in->planes + dst; /* stb 阶段 */
|
||||
long long finished = dst > box ? box : dst; /* 精修/落框后的最终图 */
|
||||
if (dst + finished > peak) peak = dst + finished;
|
||||
if (finished + box > peak) peak = finished + box;
|
||||
return peak + MR_DECODE_SLACK; /* 结构体/行缓冲/分配器开销的实测余量 */
|
||||
}
|
||||
|
||||
/* 本次解码的峰值预算:
|
||||
* - 常态 = MR_DECODE_PEAK_CAP(3MB 硬阈值,覆盖基线 6044 + 常驻 3MB + 峰值 3MB ≤ 13.5MB);
|
||||
* - 只有 MemFree 真的富余时才放宽(真机杀点由 MemFree 见底触发,不是固定 RSS),
|
||||
* 放宽上限 = min(MR_DECODE_PEAK_HARD, MemFree - MR_MEM_FREE_MARGIN_KB);
|
||||
* - 读不到 MemFree → 只用常态预算(不猜)。 */
|
||||
static long long decode_peak_budget(long memfree_kb)
|
||||
{
|
||||
long long budget = MR_DECODE_PEAK_CAP;
|
||||
if (memfree_kb >= 0) {
|
||||
long long room = (memfree_kb - MR_MEM_FREE_MARGIN_KB) * 1024;
|
||||
if (room > budget) budget = room;
|
||||
if (budget > MR_DECODE_PEAK_HARD) budget = MR_DECODE_PEAK_HARD;
|
||||
}
|
||||
return budget;
|
||||
}
|
||||
|
||||
/* 固定字段的解码日志(任务 5):page/src/peak/memfree/memavail 之后是结论,
|
||||
* 便于 `grep 'reader decode'` 后按列看曲线。 */
|
||||
/* 固定字段的解码日志:page/src/peak/path 之后是结论,便于 `grep 'reader decode'`
|
||||
* 后按列看曲线。peak = reader_estimate_peak 的预估峰值(字节/kB);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,
|
||||
unsigned long long ms)
|
||||
int backend, unsigned long long ms)
|
||||
{
|
||||
meow_log(MEOW_LOG_INFO,
|
||||
"reader decode page=%d src=%dx%d peak=%lldkB memfree=%ldkB memavail=%ldkB %s step=%d ms=%llu",
|
||||
page, src_w, src_h, peak_bytes / 1024, memfree, memavail, verdict, step, ms);
|
||||
"reader decode page=%d src=%dx%d peak=%lldkB memfree=%ldkB memavail=%ldkB %s path=%s step=%d ms=%llu",
|
||||
page, src_w, src_h, peak_bytes / 1024, memfree, memavail, verdict,
|
||||
backend ? "backend" : "stb", step, ms);
|
||||
}
|
||||
|
||||
static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||
@@ -501,50 +456,47 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||
long mfree = mem_free_kb();
|
||||
long mavail = mem_avail_kb();
|
||||
|
||||
/* 1) 门控(任务 1):MemAvailable 不足 → 不解码、不分配,直接占位(可重试)。
|
||||
* 阈值 = MR_MEM_FLOOR_KB。读不到(-1)按「可解码」放行,不因探测失败卡死阅读。 */
|
||||
if (mavail >= 0 && mavail < MR_MEM_FLOOR_KB) {
|
||||
g_decode_skip = MR_SKIP_LOWMEM;
|
||||
log_decode(page, 0, 0, 0, mfree, mavail, "skip=lowmem", 1,
|
||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 2) 头部探测(零像素内存)→ 真实峰值模型 → 选缩解档位。 */
|
||||
/* 1) 头部探测(零像素内存),再交给 page_source 的唯一模型定径:
|
||||
* reader_estimate_peak 同时给出「走后端还是 stb」与「是否占位」。后端能接住的页
|
||||
* (M23,峰值与源尺寸无关)直接放行;只有回退 stb 的页才参与峰值预算/MemAvailable
|
||||
* 门控。模型不再在本文件另写一份。 */
|
||||
mr_img_info in;
|
||||
int have = mr_probe_info(path, &in);
|
||||
int step = 1;
|
||||
long long peak = 0;
|
||||
mr_decode_decision dec;
|
||||
memset(&dec, 0, sizeof dec);
|
||||
dec.step = 1;
|
||||
if (have) {
|
||||
long long budget = decode_peak_budget(mfree);
|
||||
int step = 1;
|
||||
if (in.codec == MR_IMG_CODEC_JPEG) {
|
||||
/* 从「不欠采样」的档位起步,逐步加粗直到峰值进预算。
|
||||
* 分量面与档位无关,所以大页最终仍会无档可用 → 占位(诚实代价)。 */
|
||||
int l0 = in.w > in.h ? in.w : in.h;
|
||||
step = meow_img_pick_scale(l0, dim);
|
||||
for (;;) {
|
||||
peak = decode_peak_bytes(&in, step, box_w, box_h);
|
||||
if (peak <= budget) break;
|
||||
if (step >= 8) { step = 8; break; }
|
||||
step *= 2;
|
||||
}
|
||||
} else {
|
||||
step = 1;
|
||||
peak = decode_peak_bytes(&in, step, box_w, box_h);
|
||||
}
|
||||
if (peak > budget) {
|
||||
g_decode_skip = MR_SKIP_PEAK;
|
||||
log_decode(page, in.w, in.h, peak, mfree, mavail, "skip=peak", step,
|
||||
for (;;) {
|
||||
reader_estimate_peak(&in, step, box_w, box_h, mfree, mavail, &dec);
|
||||
/* 后端命中 / 已进预算 / 已到最粗档位:定案。非 JPEG 无档位可调。 */
|
||||
if (dec.path == MR_DECODE_PATH_BACKEND || dec.skip == MR_DECODE_OK ||
|
||||
step >= 8 || in.codec != MR_IMG_CODEC_JPEG)
|
||||
break;
|
||||
step *= 2;
|
||||
}
|
||||
if (dec.skip != MR_DECODE_OK) {
|
||||
g_decode_skip = dec.skip == MR_DECODE_SKIP_LOWMEM ? MR_SKIP_LOWMEM : MR_SKIP_PEAK;
|
||||
log_decode(page, in.w, in.h, dec.peak_bytes, mfree, mavail,
|
||||
dec.skip == MR_DECODE_SKIP_LOWMEM ? "skip=lowmem" : "skip=peak",
|
||||
dec.step, dec.path == MR_DECODE_PATH_BACKEND,
|
||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* 3) 解码(档位已定)。失败与「跳过」必须分开:NULL + skip==0 才是坏图。 */
|
||||
meow_img *im = have ? meow_img_load_file_step(path, dim, step)
|
||||
/* 2) 解码(路径/档位已定)。失败与「跳过」必须分开:NULL + skip==0 才是坏图。
|
||||
* step_ex 回报实际路径,日志据此区分 backend / stb(决策与实际必须一致)。 */
|
||||
int used_backend = 0;
|
||||
meow_img *im = have ? meow_img_load_file_step_ex(path, dim, dec.step, &used_backend)
|
||||
: meow_img_load_file_dim(path, dim);
|
||||
if (!im) {
|
||||
log_decode(page, have ? in.w : 0, have ? in.h : 0, peak, mfree, mavail, "fail", step,
|
||||
log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "fail",
|
||||
dec.step, used_backend,
|
||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||
return NULL;
|
||||
}
|
||||
@@ -553,16 +505,17 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||
meow_img *small = meow_img_downscale(im, dim);
|
||||
if (small) { meow_img_free(im); im = small; }
|
||||
}
|
||||
/* 4) 精确落到显示框(仅当「故意用了粗档位」时):结果 = box → 命中页仍是一次原生 blit,
|
||||
/* 3) 精确落到显示框(仅当「故意用了粗档位」时):结果 = box → 命中页仍是一次原生 blit,
|
||||
* 不会把内存换来的降级变成每帧重采样(M17 的热路径纪律)。
|
||||
* 源图本身就小于显示框(小图/缩略图)时绝不放大:那会凭空多占常驻字节,
|
||||
* 且 M17 的 slot_ready 语义依赖「SDK 不做上采样」。 */
|
||||
if (have && step > 1 && (in.w >= box_w || in.h >= box_h) &&
|
||||
if (have && dec.step > 1 && (in.w >= box_w || in.h >= box_h) &&
|
||||
(meow_img_w(im) != box_w || meow_img_h(im) != box_h)) {
|
||||
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, peak, mfree, mavail, "ok", step,
|
||||
log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "ok",
|
||||
dec.step, used_backend,
|
||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||
return im;
|
||||
}
|
||||
@@ -2552,15 +2505,18 @@ void mr_reader_ui_test_set_mem(long free_kb, long avail_kb)
|
||||
g_mem_free_override = free_kb;
|
||||
g_mem_avail_override = avail_kb;
|
||||
}
|
||||
/* 峰值模型自检(只读):用真代码的模型算某个文件的预估峰值,供宿主测试与实测对照。 */
|
||||
/* 峰值模型自检(只读):用真代码的模型算某个文件的预估峰值,供宿主测试与实测对照。
|
||||
* 走 page_source 的唯一实现(memfree/mavail 传 -1 = 只算模型,不注入门控读数)。 */
|
||||
long long mr_reader_ui_decode_peak_kb(const char *path, int step, int box_w, int box_h)
|
||||
{
|
||||
mr_img_info in;
|
||||
mr_decode_decision dec;
|
||||
if (!path || !mr_probe_info(path, &in)) return 0;
|
||||
return decode_peak_bytes(&in, step, box_w, box_h) / 1024;
|
||||
reader_estimate_peak(&in, step, box_w, box_h, -1, -1, &dec);
|
||||
return dec.peak_bytes / 1024;
|
||||
}
|
||||
long mr_reader_ui_peak_cap_kb(void) { return (long)(MR_DECODE_PEAK_CAP / 1024); }
|
||||
long mr_reader_ui_peak_budget_kb(void) { return (long)(decode_peak_budget(mem_free_kb()) / 1024); }
|
||||
long mr_reader_ui_peak_cap_kb(void) { return (long)(reader_peak_budget_bytes(-1) / 1024); }
|
||||
long mr_reader_ui_peak_budget_kb(void) { return (long)(reader_peak_budget_bytes(mem_free_kb()) / 1024); }
|
||||
long mr_reader_ui_resident_cap_kb(void) { return (long)(MR_RESIDENT_CAP / 1024); }
|
||||
int mr_reader_ui_blit_draws(void) { return g_blit_draws; }
|
||||
int mr_reader_ui_scaled_draws(void) { return g_scaled_draws; }
|
||||
|
||||
Reference in new issue
Block a user