Revert "Merge branch 'feat/reader-low-res-first-then-sharpen'"
This reverts commiteb0750051f, reversing changes made toce34974727.
This commit is contained in:
1 parent
eb0750051f
commit
bbc73635a6
4 files changed
+71
-745
No files matched your search
@@ -17,12 +17,7 @@
|
|||||||
* B7b 回退路径:CMYK / Adam7 PNG / 1bpp BMP 走 stb,按旧门控占位
|
* B7b 回退路径:CMYK / Adam7 PNG / 1bpp BMP 走 stb,按旧门控占位
|
||||||
* B7c P1-1 旁路回归:截断 12MP JPEG / 3000×3000 24bpp 头-only BMP(判定 backend,
|
* B7c P1-1 旁路回归:截断 12MP JPEG / 3000×3000 24bpp 头-only BMP(判定 backend,
|
||||||
* 后端失败必须 NULL、峰值 ≤3MB)/ 非法位深 PNG(矩阵拒绝→stb 占位)
|
* 后端失败必须 NULL、峰值 ≤3MB)/ 非法位深 PNG(矩阵拒绝→stb 占位)
|
||||||
* B8 M27/M31:顺序连翻 100 页(101 页册,6 类循环,每页都是不同的下一页)不掉页、
|
* B8 M27:连翻 30 页(6 类循环)不掉页、不占位、任何时刻存活增量 ≤3MB
|
||||||
* 不占位、存活增量 ≤3MB、绝对峰值有上界、预取命中 ≥90%、解码次数 ≤2/页+余量
|
|
||||||
* (M31 r2:修「每翻一页解 4 次」与高峰值,评审 P1-1/P1-2/P2-1/P2-3)
|
|
||||||
* B10 M31:两级清晰度生命周期(JPEG 大页):低档先落位(open 不阻塞)→ 停稳后高档落
|
|
||||||
* 同一槽(总常驻不增加、槽位不空、本次操作增量 ≤3MB)→ 双击放大换清晰不重置缩放/平移
|
|
||||||
* → 拖动中强塞的高档请求被取消通道作废 → 整串解码次数有界
|
|
||||||
* B9 M27:CMYK 回退页集成 —— 占位可重试且不崩
|
* B9 M27:CMYK 回退页集成 —— 占位可重试且不崩
|
||||||
*
|
*
|
||||||
* 夹具:BMP/PNG 小夹具由本文件用 C 写;JPEG/PNG/BMP 大夹具由 python3+Pillow 生成
|
* 夹具:BMP/PNG 小夹具由本文件用 C 写;JPEG/PNG/BMP 大夹具由 python3+Pillow 生成
|
||||||
@@ -120,7 +115,6 @@ static int g_fail;
|
|||||||
} while (0)
|
} while (0)
|
||||||
#define INFO(...) do { printf(" info: "); printf(__VA_ARGS__); printf("\n"); } while (0)
|
#define INFO(...) do { printf(" info: "); printf(__VA_ARGS__); printf("\n"); } while (0)
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* 夹具写入 */
|
/* 夹具写入 */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -221,14 +215,6 @@ static int copy_file(const char *src, const char *dst)
|
|||||||
return ok ? 0 : -1;
|
return ok ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 同一文件系统优先硬链接:多页册只是「同样的夹具当很多页」,101 页 × 12MP/BMP/PNG 真拷贝
|
|
||||||
* 要写几百 MB,跑一次测试 I/O 比解码还久(评审复跑成本)。读侧只按路径打开,硬链接完全等价。 */
|
|
||||||
static int link_or_copy(const char *src, const char *dst)
|
|
||||||
{
|
|
||||||
if (link(src, dst) == 0) return 0;
|
|
||||||
return copy_file(src, dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int have_file(const char *path)
|
static int have_file(const char *path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -475,23 +461,20 @@ int main(void)
|
|||||||
}
|
}
|
||||||
int have_jpeg = gen_jpeg_fixtures(jpgbook);
|
int have_jpeg = gen_jpeg_fixtures(jpgbook);
|
||||||
|
|
||||||
/* 顺序连翻 100 页:6 类典型页循环(12MP / 4MP / baseline / 渐进 / PNG / BMP)。
|
/* 连翻 30 页:6 类典型页循环(12MP / 4MP / baseline / 渐进 / PNG / BMP)。
|
||||||
* 每类都必须走后端且不占位——这是「修好了却用不上」的回归防线。
|
* 每类都必须走后端且不占位——这是「修好了却用不上」的回归防线。 */
|
||||||
* M31 r2(评审 P2-1):101 页顺序遍历(不是 30 页来回跳)——每次翻页都落到**不同的
|
|
||||||
* 下一页**,4MP/12MP 夹具都会真正成为焦点页并被解码/换清晰。 */
|
|
||||||
static const char *const g_mix[] = {
|
static const char *const g_mix[] = {
|
||||||
"base2000.jpg", "prog2000.jpg", "page1600.png",
|
"base2000.jpg", "prog2000.jpg", "page1600.png",
|
||||||
"page1600.bmp", "mp4.jpg", "mp12.jpg",
|
"page1600.bmp", "mp4.jpg", "mp12.jpg",
|
||||||
};
|
};
|
||||||
const int mix_n = (int)(sizeof g_mix / sizeof g_mix[0]);
|
const int mix_n = (int)(sizeof g_mix / sizeof g_mix[0]);
|
||||||
const int mixed_goal = 101; /* 100 次前翻 → 第 101 页(不回卷、不触发册间衔接) */
|
|
||||||
int mixed_pages = 0;
|
int mixed_pages = 0;
|
||||||
if (have_jpeg) {
|
if (have_jpeg) {
|
||||||
for (int i = 0; i < mixed_goal; i++) {
|
for (int i = 0; i < 30; i++) {
|
||||||
char src[MR_PATH_MAX], dst[MR_PATH_MAX];
|
char src[MR_PATH_MAX], dst[MR_PATH_MAX];
|
||||||
snprintf(src, sizeof src, "%s/%s", jpgbook, g_mix[i % mix_n]);
|
snprintf(src, sizeof src, "%s/%s", jpgbook, g_mix[i % mix_n]);
|
||||||
snprintf(dst, sizeof dst, "%s/p%03d_%s", mixed, i, g_mix[i % mix_n]);
|
snprintf(dst, sizeof dst, "%s/p%02d_%s", mixed, i, g_mix[i % mix_n]);
|
||||||
if (link_or_copy(src, dst) == 0) mixed_pages++;
|
if (copy_file(src, dst) == 0) mixed_pages++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* 回退路径集成:CMYK(4 分量)必须占位且不崩。 */
|
/* 回退路径集成:CMYK(4 分量)必须占位且不崩。 */
|
||||||
@@ -608,9 +591,6 @@ int main(void)
|
|||||||
mr_reader_ui_failed_pages());
|
mr_reader_ui_failed_pages());
|
||||||
/* 取消/预取纪律:100 次翻页的解码次数必须有界(M20 实测曾 1.75× 浪费)。 */
|
/* 取消/预取纪律:100 次翻页的解码次数必须有界(M20 实测曾 1.75× 浪费)。 */
|
||||||
CHECK(decodes <= 100 * 2, "decode count bounded over 100 flips (%d)", decodes);
|
CHECK(decodes <= 100 * 2, "decode count bounded over 100 flips (%d)", decodes);
|
||||||
/* M31:小页(480×640=0.3MP)不分档——低档只会多一次解码 + 落框,纯开销。 */
|
|
||||||
CHECK(mr_reader_ui_low_decodes() == 0,
|
|
||||||
"small pages stay single-tier (%d low decodes)", mr_reader_ui_low_decodes());
|
|
||||||
meow_view_page_pop();
|
meow_view_page_pop();
|
||||||
pump_ms(200);
|
pump_ms(200);
|
||||||
}
|
}
|
||||||
@@ -782,237 +762,43 @@ int main(void)
|
|||||||
printf(" SKIP jpeg assertions: Pillow unavailable (python3 -c 'import PIL')\n");
|
printf(" SKIP jpeg assertions: Pillow unavailable (python3 -c 'import PIL')\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- B8 M27/M31:顺序连翻 100 页(101 页册,6 类典型页循环)——不掉页、不占位、
|
/* ---- B8 M27:连翻 30 页(6 类典型页循环)——不掉页、不占位、峰值 ≤3MB ---- */
|
||||||
* 峰值不增长、预取命中、解码次数有界 ----
|
if (have_jpeg && mixed_pages == 30) {
|
||||||
* M31 起这些 1.3–12MP 的页走两级清晰度(低档先上屏、停稳换高档)。评审 P1:
|
|
||||||
* - 顺序前翻(每页都落到不同的下一页),4MP/12MP 夹具真正成为焦点页;
|
|
||||||
* - 断言语义翻页命中率(目标页已在槽)与解码次数上限,防止「每翻一页解 4 次」;
|
|
||||||
* - 存活增量仍按单页解码峰值封顶(两档共存时换清晰前会腾掉非可见页,见 reader.c)。 */
|
|
||||||
if (have_jpeg && mixed_pages == mixed_goal) {
|
|
||||||
mr_reader_ui_test_set_mem(4096, 16000);
|
mr_reader_ui_test_set_mem(4096, 16000);
|
||||||
mr_reader_open_dir(mixed);
|
mr_reader_open_dir(mixed);
|
||||||
pump_ms(1200);
|
pump_ms(1200);
|
||||||
CHECK(mr_reader_ui_count() == mixed_goal, "mixed book pages=%d", mr_reader_ui_count());
|
CHECK(mr_reader_ui_count() == 30, "mixed book pages=%d", mr_reader_ui_count());
|
||||||
|
int w = meow_ui_width();
|
||||||
int advanced = 0, skipped_max = 0;
|
int advanced = 0, skipped_max = 0;
|
||||||
size_t live_steady = g_live;
|
size_t live_steady = g_live;
|
||||||
int d0 = mr_reader_ui_decode_count();
|
int d0 = mr_reader_ui_decode_count();
|
||||||
int low0 = mr_reader_ui_low_decodes(), high0 = mr_reader_ui_high_decodes();
|
|
||||||
int hits0 = mr_reader_ui_flip_hits(), miss0 = mr_reader_ui_flip_misses();
|
|
||||||
/* P2-3:只在窗口稳定后 reset 一次,之后不再 reset——这样淘汰旧图不会掩盖
|
/* P2-3:只在窗口稳定后 reset 一次,之后不再 reset——这样淘汰旧图不会掩盖
|
||||||
* 「分配新图 + 解码」的真实峰值;指标 = 整个 100 翻窗口内相对稳态基线的最大存活增量。
|
* 「分配新图 + 解码」的真实峰值;指标 = 整个 30 翻窗口内相对稳态基线的最大存活增量。
|
||||||
* (旧实现每翻页 reset 一次,淘汰把基线拉低,报告里的 536KB 是伪值。) */
|
* (旧实现每翻页 reset 一次,淘汰把基线拉低,报告里的 536KB 是伪值。) */
|
||||||
size_t base = peak_reset();
|
size_t base = peak_reset();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 30; i++) {
|
||||||
int before = mr_reader_ui_page();
|
int before = mr_reader_ui_page();
|
||||||
tap(40, 300); /* RTL:左 1/3 = 下一页(顺序前翻) */
|
tap(i % 2 == 0 ? 40 : w - 40, 300);
|
||||||
pump_ms(400); /* 700ms/页:给「换清晰→补邻居」留出余量 */
|
pump_ms(220);
|
||||||
if (mr_reader_ui_page() == before + 1) advanced++;
|
if (mr_reader_ui_page() != before) advanced++;
|
||||||
if (mr_reader_ui_skipped_pages() > skipped_max)
|
if (mr_reader_ui_skipped_pages() > skipped_max)
|
||||||
skipped_max = mr_reader_ui_skipped_pages();
|
skipped_max = mr_reader_ui_skipped_pages();
|
||||||
}
|
}
|
||||||
size_t transient = g_peak > base ? g_peak - base : 0;
|
size_t transient = g_peak > base ? g_peak - base : 0;
|
||||||
int hits = mr_reader_ui_flip_hits() - hits0;
|
INFO("30 mixed flips: advanced=%d skipped=%d decodes=%d steady=%zuKB "
|
||||||
int miss = mr_reader_ui_flip_misses() - miss0;
|
"resident=%ldKB max_live_delta=%zuKB",
|
||||||
int decodes = mr_reader_ui_decode_count() - d0;
|
advanced, skipped_max, mr_reader_ui_decode_count() - d0,
|
||||||
INFO("100 mixed flips: advanced=%d last_page=%d hits=%d miss=%d skipped=%d decodes=%d "
|
live_steady / 1024, mr_reader_ui_resident_kb(), transient / 1024);
|
||||||
"(low=%d high=%d) steady=%zuKB resident=%ldKB max_live_delta=%zuKB abs_peak=%zuKB",
|
CHECK(advanced == 30, "30 页全部翻到 (%d/30)", advanced);
|
||||||
advanced, mr_reader_ui_page(), hits, miss, skipped_max, decodes,
|
|
||||||
mr_reader_ui_low_decodes() - low0, mr_reader_ui_high_decodes() - high0,
|
|
||||||
live_steady / 1024, mr_reader_ui_resident_kb(), transient / 1024, g_peak / 1024);
|
|
||||||
CHECK(advanced == 100 && mr_reader_ui_page() == 100,
|
|
||||||
"100 次顺序前翻逐页推进(advanced=%d last=%d)", advanced, mr_reader_ui_page());
|
|
||||||
CHECK(skipped_max == 0, "典型页连翻无占位 (max=%d)", skipped_max);
|
CHECK(skipped_max == 0, "典型页连翻无占位 (max=%d)", skipped_max);
|
||||||
CHECK(mr_reader_ui_failed_pages() == 0, "无永久失败页 (%d)",
|
CHECK(mr_reader_ui_failed_pages() == 0, "无永久失败页 (%d)",
|
||||||
mr_reader_ui_failed_pages());
|
mr_reader_ui_failed_pages());
|
||||||
CHECK(mr_reader_ui_low_decodes() > low0 && mr_reader_ui_high_decodes() > high0,
|
|
||||||
"大页确实走了两级(low +%d, high +%d)",
|
|
||||||
mr_reader_ui_low_decodes() - low0, mr_reader_ui_high_decodes() - high0);
|
|
||||||
/* 顺序阅读的预取命中率:评审要求不低于基线 7/8。 */
|
|
||||||
CHECK(hits >= 90, "顺序翻页预取命中率 ≥90%%(hit=%d miss=%d)", hits, miss);
|
|
||||||
/* 解码次数上限:分档页 = 低档 + 高档(2 次/页),非 JPEG 单档(1 次/页)+ 开册/补邻居余量。
|
|
||||||
* 出现「每页 4 次」的放大(P1-2 的病灶)时这里会立刻失败。 */
|
|
||||||
CHECK(decodes <= 100 * 2 + 20, "100 次翻页解码次数 ≤2/页+余量 (decodes=%d)", decodes);
|
|
||||||
CHECK(mr_reader_ui_resident_kb() <= mr_reader_ui_resident_cap_kb(),
|
|
||||||
"常驻仍在上限内 (%ldKB ≤ %ldKB)", mr_reader_ui_resident_kb(),
|
|
||||||
mr_reader_ui_resident_cap_kb());
|
|
||||||
CHECK(transient <= 3 * 1024 * 1024,
|
CHECK(transient <= 3 * 1024 * 1024,
|
||||||
"100 页连翻任何时刻存活增量 ≤3MB (%zuKB)", transient / 1024);
|
"30 页连翻任何时刻存活增量 ≤3MB (%zuKB)", transient / 1024);
|
||||||
/* 两档共存的总账:常驻(≤3MB)+ 单次解码(≤3MB)——不许因为「基线里已含低档」
|
|
||||||
* 就看着没涨;绝对存活峰值同样要有上界。 */
|
|
||||||
CHECK(g_peak <= (size_t)(mr_reader_ui_resident_cap_kb() + 3 * 1024) * 1024,
|
|
||||||
"绝对存活峰值 ≤ 常驻上限+3MB (%zuKB)", g_peak / 1024);
|
|
||||||
meow_view_page_pop();
|
meow_view_page_pop();
|
||||||
pump_ms(200);
|
pump_ms(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- B10 M31:两级清晰度生命周期(2000×3000 JPEG,真机同类大页)----
|
|
||||||
* 1) 冻结停稳窗口 → 只解低档:焦点页 tier=low、常驻=显示框大小(仍是一次原生 blit),
|
|
||||||
* 主线程不等解码(open 立即返回);
|
|
||||||
* 2) 放开停稳窗口 → 高档落同一槽:tier=high、**总常驻不增加**(不是恒真的槽内字节比较)、
|
|
||||||
* 槽位全程不空、本次操作存活增量 ≤3MB、两档共存绝对峰值有上界;
|
|
||||||
* 3) 双击放大后按新几何先落低档,再换清晰 → 缩放/平移一字不差(P2-2);
|
|
||||||
* 4) 拖动中强塞的高档请求 → 被 M22 的 gen/窗口取消通道作废(skip=cancel),不会被解出来;
|
|
||||||
* 5) 整串生命周期解码次数有界(P2-3)。 */
|
|
||||||
if (have_jpeg) {
|
|
||||||
char tierbook[MR_PATH_MAX];
|
|
||||||
snprintf(tierbook, sizeof tierbook, "%s/tier", root);
|
|
||||||
int tier_ok = mkdir_p(tierbook) == 0;
|
|
||||||
for (int i = 0; tier_ok && i < 2; i++) {
|
|
||||||
char src[MR_PATH_MAX], dst[MR_PATH_MAX];
|
|
||||||
snprintf(src, sizeof src, "%s/base2000.jpg", jpgbook);
|
|
||||||
snprintf(dst, sizeof dst, "%s/t%d.jpg", tierbook, i + 1);
|
|
||||||
if (link_or_copy(src, dst) != 0) tier_ok = 0;
|
|
||||||
}
|
|
||||||
if (tier_ok) {
|
|
||||||
mr_reader_ui_test_set_mem(4096, 16000);
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(60000); /* 冻结:先验证低档先落位 */
|
|
||||||
uint64_t t_open = (uint64_t)meow_ui_time_ms();
|
|
||||||
mr_reader_open_dir(tierbook);
|
|
||||||
uint64_t open_ms = (uint64_t)meow_ui_time_ms() - t_open;
|
|
||||||
CHECK(mr_reader_ui_count() == 2, "tier book pages=%d", mr_reader_ui_count());
|
|
||||||
CHECK(open_ms < 500, "open is non-blocking (%llu ms)",
|
|
||||||
(unsigned long long)open_ms);
|
|
||||||
pump_ms(800);
|
|
||||||
meow_ui_paint_full();
|
|
||||||
int page0 = mr_reader_ui_page();
|
|
||||||
int dec0 = mr_reader_ui_decode_count();
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_LOW,
|
|
||||||
"low tier lands first (tier=%d)", mr_reader_ui_focus_tier());
|
|
||||||
CHECK(mr_reader_ui_low_decodes() >= 1, "low decode issued (%d)",
|
|
||||||
mr_reader_ui_low_decodes());
|
|
||||||
CHECK(mr_reader_ui_high_decodes() == 0,
|
|
||||||
"no high decode before the settle window (%d)", mr_reader_ui_high_decodes());
|
|
||||||
long low_kb = mr_reader_ui_slot_kb(page0);
|
|
||||||
CHECK(low_kb > 0, "low tier image resident (%ldKB)", low_kb);
|
|
||||||
long res_low = mr_reader_ui_resident_kb();
|
|
||||||
int bl = mr_reader_ui_blit_draws(), sc = mr_reader_ui_scaled_draws();
|
|
||||||
meow_ui_paint_full();
|
|
||||||
CHECK(mr_reader_ui_blit_draws() > bl && mr_reader_ui_scaled_draws() == sc,
|
|
||||||
"low tier draws as native blit (blit +%d, scaled +%d)",
|
|
||||||
mr_reader_ui_blit_draws() - bl, mr_reader_ui_scaled_draws() - sc);
|
|
||||||
|
|
||||||
/* 放开升级:边等边采样,焦点槽任何时刻都不能空(不闪白)。 */
|
|
||||||
int high0 = mr_reader_ui_high_decodes();
|
|
||||||
long min_slot = low_kb;
|
|
||||||
size_t op_base = g_live;
|
|
||||||
peak_reset();
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(0);
|
|
||||||
for (int i = 0; i < 100 && mr_reader_ui_focus_tier() != MR_TIER_HIGH; i++) {
|
|
||||||
pump_ms(10);
|
|
||||||
long k = mr_reader_ui_slot_kb(page0);
|
|
||||||
if (k > 0 && k < min_slot) min_slot = k;
|
|
||||||
}
|
|
||||||
size_t op_peak = peak_since(op_base); /* 高档这一次操作的存活增量 */
|
|
||||||
meow_ui_paint_full();
|
|
||||||
CHECK(mr_reader_ui_page() == page0, "tier swap keeps the page (%d -> %d)",
|
|
||||||
page0, mr_reader_ui_page());
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_HIGH,
|
|
||||||
"high tier replaces low in the same slot (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
CHECK(mr_reader_ui_high_decodes() > high0,
|
|
||||||
"high decode issued after settle (%d -> %d)", high0,
|
|
||||||
mr_reader_ui_high_decodes());
|
|
||||||
/* P2-2:旧的 `slot_kb` 相等是恒真(两档都落同一个 box)。改成真正会抓问题的
|
|
||||||
* 「总常驻不增加」:如果换清晰偷偷多留一张低档,这里立刻涨。 */
|
|
||||||
long res_high = mr_reader_ui_resident_kb();
|
|
||||||
CHECK(res_high > 0 && res_high <= res_low,
|
|
||||||
"换清晰不增加常驻(没有第二张低档常驻):%ldKB -> %ldKB",
|
|
||||||
res_low, res_high);
|
|
||||||
CHECK(op_peak <= 3 * 1024 * 1024,
|
|
||||||
"高档单次操作的存活增量 ≤3MB (%zuKB)", op_peak / 1024);
|
|
||||||
/* 两档共存总账:常驻 + 本次解码 的绝对峰值有上界(不是「基线含低档所以没涨」)。 */
|
|
||||||
CHECK(g_peak <= (size_t)(mr_reader_ui_resident_cap_kb() + 3 * 1024) * 1024,
|
|
||||||
"两档共存绝对峰值 ≤ 常驻上限+3MB (%zuKB)", g_peak / 1024);
|
|
||||||
CHECK(min_slot > 0, "no blank frame while swapping tiers (min slot %ldKB)",
|
|
||||||
min_slot);
|
|
||||||
CHECK(mr_reader_ui_scaled_draws() == sc,
|
|
||||||
"high tier still native blit (scaled %d -> %d)", sc,
|
|
||||||
mr_reader_ui_scaled_draws());
|
|
||||||
|
|
||||||
/* P2-2 之二:换清晰不得重置缩放/平移。先冻结窗口 → 双击放大(低档按新几何重解)
|
|
||||||
* → 记录缩放/平移 → 放开升级 → 高档落位后两者必须一字不差。 */
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(60000); /* 先冻结,让放大后的低档先落位 */
|
|
||||||
reader_on_touch_down(240, 360);
|
|
||||||
reader_on_tap(240, 360);
|
|
||||||
pump_ms(60);
|
|
||||||
reader_on_touch_down(240, 360);
|
|
||||||
reader_on_tap(240, 360);
|
|
||||||
pump_ms(150);
|
|
||||||
pump_ms(600);
|
|
||||||
int zoom_before = mr_reader_ui_zoom_pct();
|
|
||||||
int panx_before = mr_reader_ui_pan_x();
|
|
||||||
int pany_before = mr_reader_ui_pan_y();
|
|
||||||
CHECK(zoom_before > 100, "double tap zoomed in (zoom=%d)", zoom_before);
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_LOW,
|
|
||||||
"zoomed page re-decodes to low tier first (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(0);
|
|
||||||
for (int i = 0; i < 100 && mr_reader_ui_focus_tier() != MR_TIER_HIGH; i++)
|
|
||||||
pump_ms(10);
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_HIGH,
|
|
||||||
"zoomed page reaches the target tier (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
CHECK(mr_reader_ui_zoom_pct() == zoom_before &&
|
|
||||||
mr_reader_ui_pan_x() == panx_before &&
|
|
||||||
mr_reader_ui_pan_y() == pany_before,
|
|
||||||
"换清晰不重置缩放/平移(zoom %d→%d, pan %d,%d→%d,%d)",
|
|
||||||
zoom_before, mr_reader_ui_zoom_pct(), panx_before, pany_before,
|
|
||||||
mr_reader_ui_pan_x(), mr_reader_ui_pan_y());
|
|
||||||
|
|
||||||
/* 拖动中取消高档:拖动期间强塞一个「换清晰」请求,必须被取消通道作废。 */
|
|
||||||
int hd0 = mr_reader_ui_high_decodes(), hc0 = mr_reader_ui_high_canceled();
|
|
||||||
reader_on_touch_down(240, 400);
|
|
||||||
reader_on_touch_move(180, 400); /* 进入拖动 → g_sharpen_idx=-1 */
|
|
||||||
CHECK(mr_reader_ui_sharpen_idx() == -1, "drag clears sharpen eligibility");
|
|
||||||
mr_reader_ui_test_queue_high(page0);
|
|
||||||
pump_ms(200);
|
|
||||||
CHECK(mr_reader_ui_high_canceled() > hc0,
|
|
||||||
"high request canceled while dragging (%d -> %d)", hc0,
|
|
||||||
mr_reader_ui_high_canceled());
|
|
||||||
CHECK(mr_reader_ui_high_decodes() == hd0,
|
|
||||||
"canceled high request is never decoded (%d -> %d)", hd0,
|
|
||||||
mr_reader_ui_high_decodes());
|
|
||||||
reader_on_tap(240, 400); /* 松手:回弹,不翻页 */
|
|
||||||
pump_ms(300);
|
|
||||||
|
|
||||||
/* P2-2 之三:条漫滚动位置在换清晰时不得被重置(换的是视口中心页的档位)。 */
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(60000); /* 先冻结:切模式时不要立刻换清晰 */
|
|
||||||
mr_reader_test_toggle_mode(); /* 翻页 → 条漫 */
|
|
||||||
CHECK(mr_reader_ui_mode() == MR_MODE_WEBTOON, "tier book switched to webtoon");
|
|
||||||
pump_ms(300);
|
|
||||||
reader_on_touch_down(240, 600);
|
|
||||||
for (int k = 1; k <= 5; k++) {
|
|
||||||
reader_on_touch_move(240, 600 - k * 50);
|
|
||||||
pump_ms(10);
|
|
||||||
}
|
|
||||||
reader_on_tap(240, 300); /* 松手:fling */
|
|
||||||
pump_ms(2000); /* 等 fling 停稳 */
|
|
||||||
int woff0 = mr_reader_ui_woff();
|
|
||||||
int wpage0 = mr_reader_ui_page();
|
|
||||||
CHECK(woff0 > 0, "webtoon scrolled to a non-top offset (%d px)", woff0);
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_LOW,
|
|
||||||
"webtoon center page is low tier before settle (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(0);
|
|
||||||
for (int i = 0; i < 200 && mr_reader_ui_focus_tier() != MR_TIER_HIGH; i++)
|
|
||||||
pump_ms(10);
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_HIGH,
|
|
||||||
"webtoon center page reaches the target tier (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
CHECK(mr_reader_ui_woff() == woff0 && mr_reader_ui_page() == wpage0,
|
|
||||||
"换清晰不重置条漫滚动位置(woff %d→%d, page %d→%d)",
|
|
||||||
woff0, mr_reader_ui_woff(), wpage0, mr_reader_ui_page());
|
|
||||||
mr_reader_ui_test_set_sharpen_ms(150); /* 恢复产品默认停稳窗口 */
|
|
||||||
/* P2-3:这一串(开册低+高、放大低+高、条漫低+高、一次被取消的高档)解码次数有界。 */
|
|
||||||
int decodes = mr_reader_ui_decode_count() - dec0;
|
|
||||||
CHECK(decodes <= 12, "两档生命周期解码次数有界 (decodes=%d)", decodes);
|
|
||||||
INFO("tier lifecycle: decodes=%d resident=%ldKB (低档/高档/放大/条漫 各一次换清晰)",
|
|
||||||
decodes, mr_reader_ui_resident_kb());
|
|
||||||
meow_view_page_pop();
|
|
||||||
pump_ms(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- B9 M27:回退路径集成 —— CMYK 页占位(可重试)且不崩 ---- */
|
/* ---- B9 M27:回退路径集成 —— CMYK 页占位(可重试)且不崩 ---- */
|
||||||
if (have_fallback) {
|
if (have_fallback) {
|
||||||
mr_reader_ui_test_set_mem(4096, 16000);
|
mr_reader_ui_test_set_mem(4096, 16000);
|
||||||
|
|||||||
@@ -15,12 +15,7 @@
|
|||||||
* P5 「先点适高,再切条漫」:scaled 不增长(评审点名的第二入口);
|
* P5 「先点适高,再切条漫」:scaled 不增长(评审点名的第二入口);
|
||||||
* P6 双击放大再双击缩回:缩回后按新几何重解 → scaled 不增长(P2-1);
|
* P6 双击放大再双击缩回:缩回后按新几何重解 → scaled 不增长(P2-1);
|
||||||
* P7 静止若干帧不产生新解码(无重解抖动/活锁,P2-2);
|
* P7 静止若干帧不产生新解码(无重解抖动/活锁,P2-2);
|
||||||
* P8 驻留页图有字节上界(<= 9MB);
|
* P8 驻留页图有字节上界(<= 9MB)。
|
||||||
* P9 800×1280(评审表格另一档):翻页 + 条漫都必须零重采样;
|
|
||||||
* P10 大屏(1080×1920):fit 目标被字节上限钳住后不得反复重解;
|
|
||||||
* P11 分档策略(M31):非 JPEG 大页不分档(一次解到目标档、仍是原生 blit);
|
|
||||||
* 低档先落位→停稳换清晰→拖动取消的完整生命周期在 test_reader_membudget 的 B10
|
|
||||||
* 用真实 JPEG 夹具覆盖。
|
|
||||||
*
|
*
|
||||||
* 编译:test/run_tests.sh(与 test_open_book_host 同口径;只发布 meow_ui.a 时 SKIP)。
|
* 编译:test/run_tests.sh(与 test_open_book_host 同口径;只发布 meow_ui.a 时 SKIP)。
|
||||||
*/
|
*/
|
||||||
@@ -373,45 +368,6 @@ int main(void)
|
|||||||
"800x1280 webtoon: zero resample (scaled %d -> %d)", st,
|
"800x1280 webtoon: zero resample (scaled %d -> %d)", st,
|
||||||
mr_reader_ui_scaled_draws());
|
mr_reader_ui_scaled_draws());
|
||||||
|
|
||||||
/* ---- P11 分档策略(M31):非 JPEG 大页不分档——一次解到目标档,仍是原生 blit ----
|
|
||||||
* 低档/高档的完整生命周期(低档先落位→停稳换清晰→拖动取消)在 test_reader_membudget
|
|
||||||
* 的 B10 用真实 JPEG 夹具覆盖;这里只钉住「分档只给 JPEG」这条策略,防止把 PNG/BMP
|
|
||||||
* 也拖进未校准的「小图→落框」峰值路径。 */
|
|
||||||
{
|
|
||||||
char book3[MR_PATH_MAX];
|
|
||||||
snprintf(book3, sizeof book3, "./mr_perf_books_%ld/v3", (long)getpid());
|
|
||||||
int ok3 = mkdir_p(book3) == 0;
|
|
||||||
for (int i = 0; ok3 && i < 2; i++) {
|
|
||||||
char p[MR_PATH_MAX];
|
|
||||||
snprintf(p, sizeof p, "%s/t%d.bmp", book3, i + 1);
|
|
||||||
/* 1600×2400 = 3.84MP:量级与真机大页相同,但格式不是 JPEG。 */
|
|
||||||
if (write_bmp24(p, 1600, 2400, (unsigned)(i + 41)) != 0) ok3 = 0;
|
|
||||||
}
|
|
||||||
if (!ok3) {
|
|
||||||
printf(" SKIP: tier fixture write failed\n");
|
|
||||||
} else {
|
|
||||||
meow_view_page_pop(); /* 关掉 book2 */
|
|
||||||
uint64_t t_open = meow_ui_time_ms();
|
|
||||||
mr_reader_open_dir(book3);
|
|
||||||
uint64_t open_ms = meow_ui_time_ms() - t_open;
|
|
||||||
CHECK(mr_reader_ui_count() == 2, "tier book pages=%d", mr_reader_ui_count());
|
|
||||||
CHECK(open_ms < 500, "open returns without decoding on the UI thread (%llu ms)",
|
|
||||||
(unsigned long long)open_ms);
|
|
||||||
pump_ms(600);
|
|
||||||
draw_once();
|
|
||||||
CHECK(mr_reader_ui_focus_tier() == MR_TIER_HIGH,
|
|
||||||
"non-JPEG big page decodes straight to the target tier (tier=%d)",
|
|
||||||
mr_reader_ui_focus_tier());
|
|
||||||
CHECK(mr_reader_ui_low_decodes() == 0,
|
|
||||||
"no low tier for PNG/BMP (%d low decodes)", mr_reader_ui_low_decodes());
|
|
||||||
int bl = mr_reader_ui_blit_draws(), sc = mr_reader_ui_scaled_draws();
|
|
||||||
draw_once();
|
|
||||||
CHECK(mr_reader_ui_blit_draws() > bl && mr_reader_ui_scaled_draws() == sc,
|
|
||||||
"big BMP page draws as native blit (blit +%d, scaled +%d)",
|
|
||||||
mr_reader_ui_blit_draws() - bl, mr_reader_ui_scaled_draws() - sc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
meow_view_page_pop();
|
meow_view_page_pop();
|
||||||
meow_view_free(reader);
|
meow_view_free(reader);
|
||||||
meow_view_free(lib);
|
meow_view_free(lib);
|
||||||
|
|||||||
+49
-442
@@ -24,10 +24,6 @@
|
|||||||
* 按 stb 真实分配模型算峰值,超 3MB 占位 / MemAvailable <6MB 占位。
|
* 按 stb 真实分配模型算峰值,超 3MB 占位 / MemAvailable <6MB 占位。
|
||||||
* 占位可重试,不是永久失败。余量确实充足(MemFree 富余)时允许放宽到 12MB。
|
* 占位可重试,不是永久失败。余量确实充足(MemFree 富余)时允许放宽到 12MB。
|
||||||
* 页数增长不会线性吃内存(常驻只与解码窗大小有关)。
|
* 页数增长不会线性吃内存(常驻只与解码窗大小有关)。
|
||||||
* 手感(M31):JPEG 大页(≥1MP)先按「显示长边一半」的档位解一版低档并立刻上屏,停稳
|
|
||||||
* MR_SHARPEN_MS 后才解目标档位、在同一槽原地替换(两档都落满显示框 → 换图仍是单次 blit,
|
|
||||||
* 不闪白、不重置缩放/滚动)。拖动/连翻/条漫滚动/跳页在途期间只解低档;高档请求复用 M22 的
|
|
||||||
* gen/窗口取消通道作废。每页解码日志尾部带 tier=low|high 便于真机核对「先糊后清」。
|
|
||||||
* 方向:一律读 M3 的 mr_reader_get_direction()(默认 RTL),不另立全局。
|
* 方向:一律读 M3 的 mr_reader_get_direction()(默认 RTL),不另立全局。
|
||||||
*
|
*
|
||||||
* 模式(M7):翻页/条漫是一等公民。阅读 chrome 的图标按钮一键切换;每册的模式与 last page
|
* 模式(M7):翻页/条漫是一等公民。阅读 chrome 的图标按钮一键切换;每册的模式与 last page
|
||||||
@@ -35,7 +31,7 @@
|
|||||||
* 自动恢复。书库列表是文件管理器模型(M18),不再显示续读角标。
|
* 自动恢复。书库列表是文件管理器模型(M18),不再显示续读角标。
|
||||||
*
|
*
|
||||||
* 跳页:chrome 内 meow_slider 选目标页,松手后先把目标页解码进缓存再落位(不白屏不闪烁;
|
* 跳页:chrome 内 meow_slider 选目标页,松手后先把目标页解码进缓存再落位(不白屏不闪烁;
|
||||||
* 目标页的低档落位即可跳过去,停稳后原地换清晰;2.5s 超时则降级落位并提示)。
|
* 2.5s 超时则降级落位并提示)。
|
||||||
*
|
*
|
||||||
* 册间衔接:末页前进 → 同父目录下一个含图册(书库自然序)的首页;首页后退 → 上一册末页;
|
* 册间衔接:末页前进 → 同父目录下一个含图册(书库自然序)的首页;首页后退 → 上一册末页;
|
||||||
* 无相邻册时停住并轻提示(不误触退出)。条漫在条顶/条底继续拉 MR_BOOK_PULL 像素松手触发。
|
* 无相邻册时停住并轻提示(不误触退出)。条漫在条顶/条底继续拉 MR_BOOK_PULL 像素松手触发。
|
||||||
@@ -89,25 +85,6 @@
|
|||||||
#define MR_RESIDENT_CAP (3L * 1024 * 1024) /* 页图缓存总字节上限(M22 由 9MB 校准为 3MB) */
|
#define MR_RESIDENT_CAP (3L * 1024 * 1024) /* 页图缓存总字节上限(M22 由 9MB 校准为 3MB) */
|
||||||
#define MR_SLOT_TOL 2 /* 缓存图与显示框允许的像素偏差(≤2 走原生 blit,不重采样) */
|
#define MR_SLOT_TOL 2 /* 缓存图与显示框允许的像素偏差(≤2 走原生 blit,不重采样) */
|
||||||
|
|
||||||
/* ---- 两级清晰度(M31):先低档秒出,停稳再换高档 ----
|
|
||||||
* 大页(真机 1280×2275 单页解码 392ms)打开/翻到时先按「显示长边的一半」解一版低档,
|
|
||||||
* 落框后立刻上屏;停稳 MR_SHARPEN_MS 后再解目标档位替换同一槽。低档与高档都**精确落到
|
|
||||||
* 显示框**,所以换图那一下尺寸不变:仍是一次原生 blit,不闪白、不重置缩放/滚动。
|
|
||||||
* 内存(r2,评审 P1-1):高档解码时低档还占着槽,所以「一次操作的总峰值」=
|
|
||||||
* resident_bytes()(至少含焦点槽低档)+ reader_estimate_peak()(本次解码的峰值);
|
|
||||||
* 该总峰值受 MR_TIER_TOTAL_CAP 约束(与 page_source 的 12MB 绝对上限同值——常态 3MB
|
|
||||||
* 只约束 stb 回退路径,后端页连 3MB 都超却已被 M27 放行,拿 3MB 当两档共存上限会让
|
|
||||||
* 「先糊后清」在最需要的大页上永远不触发)。超限 → 跳过高档、保留低档(skip=coexist)。
|
|
||||||
* 另两条纪律把常驻压到最低(评审 P1-2):分页模式焦点页还是低档时不预取邻居(停稳 150ms 后
|
|
||||||
* 就会换清晰,预取来也会被挤掉),高档落位后再按翻页方向补回邻居。 */
|
|
||||||
#define MR_SHARPEN_MS 150 /* 停稳多久后才发起高档解码(任务允许 150–300ms;取 150 给「换清晰→补邻居」留出翻页余量) */
|
|
||||||
#define MR_TIER_MIN_PIXELS 1000000LL /* 源像素少于此值不分档:解本身不慢,分档只添乱 */
|
|
||||||
#define MR_LOW_MIN_LONG 240 /* 低档输出长边下限(再粗就糊到难看) */
|
|
||||||
#define MR_LOW_MAX_LONG 480 /* 低档输出长边上限(再细省不了多少解码) */
|
|
||||||
#define MR_TIER_TOTAL_CAP (12LL * 1024 * 1024) /* 两档共存:常驻 + 单次解码 的绝对上限 */
|
|
||||||
|
|
||||||
/* MR_TIER_LOW / MR_TIER_HIGH 定义在 ui/reader/reader.h(宿主测试也要用同一套档位值)。 */
|
|
||||||
|
|
||||||
#define MR_REQ_MAX 8
|
#define MR_REQ_MAX 8
|
||||||
#define MR_RES_MAX 2 /* 结果队列上限(M22:4→2;未取走的已解码图不计入常驻预算) */
|
#define MR_RES_MAX 2 /* 结果队列上限(M22:4→2;未取走的已解码图不计入常驻预算) */
|
||||||
|
|
||||||
@@ -175,9 +152,6 @@ typedef struct page_slot {
|
|||||||
meow_img *img; /* 已 downscale 的页图 */
|
meow_img *img; /* 已 downscale 的页图 */
|
||||||
int src_w, src_h;
|
int src_w, src_h;
|
||||||
int want_w, want_h; /* 本次解码按哪个显示框落的(几何判据,见 slot_ready) */
|
int want_w, want_h; /* 本次解码按哪个显示框落的(几何判据,见 slot_ready) */
|
||||||
unsigned char tier; /* MR_TIER_*:低档过渡图 / 目标档位(M31) */
|
|
||||||
unsigned char hi_tried; /* 1=这一槽最近一次「换清晰」失败/跳过(别每 tick 重试;
|
|
||||||
* 拖动取消时由 settle_reset 复位,停稳后还能再试) */
|
|
||||||
} page_slot;
|
} page_slot;
|
||||||
static page_slot g_slot[MR_SLOTS];
|
static page_slot g_slot[MR_SLOTS];
|
||||||
|
|
||||||
@@ -202,20 +176,15 @@ static page_meta *g_meta;
|
|||||||
typedef struct decode_plan {
|
typedef struct decode_plan {
|
||||||
int want_w, want_h;
|
int want_w, want_h;
|
||||||
int box_w, box_h;
|
int box_w, box_h;
|
||||||
int tier; /* MR_TIER_LOW / MR_TIER_HIGH(M31) */
|
|
||||||
int sharpen; /* 1=「停稳换清晰」的追加请求(要算两档共存峰值) */
|
|
||||||
long long resident; /* 发起这次请求时已在槽里的字节(主线程快照) */
|
|
||||||
} decode_plan;
|
} decode_plan;
|
||||||
static decode_plan g_plan;
|
static decode_plan g_plan;
|
||||||
static int g_plan_page; /* 本次解码的页号(只用于日志) */
|
static int g_plan_page; /* 本次解码的页号(只用于日志) */
|
||||||
static int g_decode_skip; /* reader_load_page 的跳过原因(MR_SKIP_*) */
|
static int g_decode_skip; /* reader_load_page 的跳过原因(MR_SKIP_*) */
|
||||||
static int g_decode_tier; /* reader_load_page 实际生效的档位(低档降为单档时=HIGH) */
|
|
||||||
static volatile int g_decode_count; /* 累计解码次数(观察重解抖动/活锁) */
|
static volatile int g_decode_count; /* 累计解码次数(观察重解抖动/活锁) */
|
||||||
|
|
||||||
#define MR_SKIP_NONE 0 /* 解出来了 */
|
#define MR_SKIP_NONE 0 /* 解出来了 */
|
||||||
#define MR_SKIP_LOWMEM 1 /* 内存门控拦下(MemAvailable 不足) */
|
#define MR_SKIP_LOWMEM 1 /* 内存门控拦下(MemAvailable 不足) */
|
||||||
#define MR_SKIP_PEAK 2 /* 真实峰值超预算,且没有余量放行 */
|
#define MR_SKIP_PEAK 2 /* 真实峰值超预算,且没有余量放行 */
|
||||||
#define MR_SKIP_COEXIST 3 /* 两档共存总峰值超上限:跳过高档、保留低档 */
|
|
||||||
|
|
||||||
static int page_failed(int idx)
|
static int page_failed(int idx)
|
||||||
{
|
{
|
||||||
@@ -269,9 +238,6 @@ typedef struct page_req {
|
|||||||
unsigned gen;
|
unsigned gen;
|
||||||
int want_w, want_h; /* 按哪个显示框解码(主线程算,slot_ready 判据) */
|
int want_w, want_h; /* 按哪个显示框解码(主线程算,slot_ready 判据) */
|
||||||
int box_w, box_h; /* 落地上限(单页字节上限钳制后的目标尺寸) */
|
int box_w, box_h; /* 落地上限(单页字节上限钳制后的目标尺寸) */
|
||||||
unsigned char tier; /* MR_TIER_*(M31:低档过渡 / 目标档位) */
|
|
||||||
unsigned char sharpen; /* 1=「停稳后换清晰」的追加请求:动起来就作废 */
|
|
||||||
long long resident; /* 发起时的常驻字节(M31 r2:高档要算两档共存峰值) */
|
|
||||||
} page_req;
|
} page_req;
|
||||||
typedef struct page_res {
|
typedef struct page_res {
|
||||||
int idx;
|
int idx;
|
||||||
@@ -280,8 +246,6 @@ typedef struct page_res {
|
|||||||
int fail;
|
int fail;
|
||||||
int skip; /* MR_SKIP_*:非 0 = 预算跳过(可重试,不是坏图) */
|
int skip; /* MR_SKIP_*:非 0 = 预算跳过(可重试,不是坏图) */
|
||||||
int want_w, want_h;
|
int want_w, want_h;
|
||||||
unsigned char tier; /* MR_TIER_*:结果按哪一档解出来的 */
|
|
||||||
unsigned char sharpen; /* 1=这是一次「停稳换清晰」的结果(落位后要补邻居) */
|
|
||||||
} page_res;
|
} page_res;
|
||||||
|
|
||||||
static pthread_t g_worker;
|
static pthread_t g_worker;
|
||||||
@@ -295,29 +259,6 @@ static int g_req_head, g_req_tail, g_req_n;
|
|||||||
static page_res g_res[MR_RES_MAX];
|
static page_res g_res[MR_RES_MAX];
|
||||||
static int g_res_n;
|
static int g_res_n;
|
||||||
|
|
||||||
/* ---- 两级清晰度状态(M31) ----
|
|
||||||
* g_sharpen_idx:当前唯一允许解高档的页(-1=拖动/未停稳/无需升级)。worker 在锁内据此
|
|
||||||
* 作废「在飞的高档请求」——复用 M22 已有的 gen/窗口取消机制,只是多一条判据。
|
|
||||||
* g_move_ms:最近一次「动」的时刻(按下/拖动/吸附动画/条漫滚动/fling/换页);
|
|
||||||
* 停稳 MR_SHARPEN_MS 后才允许升级。宿主测试可用 mr_reader_ui_test_set_sharpen_ms 改写窗口。
|
|
||||||
* g_step_dir:最近一次翻页/跳页的方向(+1 页号增,-1 页号减);高档落位后按这个方向优先
|
|
||||||
* 补邻居(P1-2:不在反方向白解一张马上会被挤掉的图)。
|
|
||||||
* g_refill:高档落位 → tick 里补回邻居(换清晰会为省峰值腾掉邻居,不补就每次翻页都 miss)。 */
|
|
||||||
static int g_sharpen_idx = -1;
|
|
||||||
/* 每页是否有请求已排队/正在 worker 手里(去重)。M31 r2(评审 P1-2):只靠队列扫描不够——
|
|
||||||
* 请求被 worker 取走后就不在队列里了,窗口刷新会再排一次同一页(重复解码 + 挤掉刚预取的
|
|
||||||
* 邻居,正是「每翻一页解 4 次 / 0 命中」的来源)。锁内读写。 */
|
|
||||||
static unsigned char g_req_inflight[MR_BOOK_MAX_PAGES];
|
|
||||||
static uint64_t g_move_ms;
|
|
||||||
static int g_sharpen_ms = MR_SHARPEN_MS;
|
|
||||||
static int g_step_dir = 1;
|
|
||||||
static int g_refill;
|
|
||||||
static volatile int g_low_decodes; /* 完成的低档解码次数(宿主测试/日志对照) */
|
|
||||||
static volatile int g_high_decodes; /* 完成的目标档位解码次数 */
|
|
||||||
static volatile int g_high_canceled; /* 被取消的高档请求数(拖动/换页/停稳失效) */
|
|
||||||
static volatile int g_flip_hits; /* 翻页时目标页已在槽(预取命中)次数 */
|
|
||||||
static volatile int g_flip_misses; /* 翻页时目标页还没解好(要等解码)次数 */
|
|
||||||
|
|
||||||
/* ---- 节拍/动画 ---- */
|
/* ---- 节拍/动画 ---- */
|
||||||
static meow_anim *g_ticker;
|
static meow_anim *g_ticker;
|
||||||
static meow_anim *g_snap; /* 翻页滑动吸附 */
|
static meow_anim *g_snap; /* 翻页滑动吸附 */
|
||||||
@@ -371,7 +312,6 @@ static page_slot *slot_find(int idx);
|
|||||||
static void cont_touch(void);
|
static void cont_touch(void);
|
||||||
static void cont_save_now(void);
|
static void cont_save_now(void);
|
||||||
static int current_page(void);
|
static int current_page(void);
|
||||||
static int page_needs_tier(int idx);
|
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
/* 内存读数与观测(M22) */
|
/* 内存读数与观测(M22) */
|
||||||
@@ -480,64 +420,15 @@ static void plan_box_size(int want_w, int want_h, int *bw, int *bh)
|
|||||||
|
|
||||||
/* 固定字段的解码日志:page/src/peak/path 之后是结论,便于 `grep 'reader decode'`
|
/* 固定字段的解码日志:page/src/peak/path 之后是结论,便于 `grep 'reader decode'`
|
||||||
* 后按列看曲线。peak = reader_estimate_peak 的预估峰值(字节/kB);path = 实际解码路径
|
* 后按列看曲线。peak = reader_estimate_peak 的预估峰值(字节/kB);path = 实际解码路径
|
||||||
* (backend = M23 真缩解后端,stb = 回退旧路径);tier = 本次请求的清晰度档位(M31)。 */
|
* (backend = M23 真缩解后端,stb = 回退旧路径)。 */
|
||||||
static void log_decode(int page, int src_w, int src_h, long long peak_bytes,
|
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,
|
long memfree, long memavail, const char *verdict, int step,
|
||||||
int backend, int tier, unsigned long long ms)
|
int backend, unsigned long long ms)
|
||||||
{
|
{
|
||||||
meow_log(MEOW_LOG_INFO,
|
meow_log(MEOW_LOG_INFO,
|
||||||
"reader decode page=%d src=%dx%d peak=%lldkB memfree=%ldkB memavail=%ldkB %s path=%s step=%d ms=%llu tier=%s",
|
"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,
|
page, src_w, src_h, peak_bytes / 1024, memfree, memavail, verdict,
|
||||||
backend ? "backend" : "stb", step, ms,
|
backend ? "backend" : "stb", step, ms);
|
||||||
tier == MR_TIER_LOW ? "low" : "high");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 低档解码目标长边(M31):显示长边的一半,夹到 [MR_LOW_MIN_LONG, MR_LOW_MAX_LONG]。
|
|
||||||
* 只对 JPEG 分档,且必须真的下探一档以上(后端 DCT 阶 n = ceil(8×目标/源长边);
|
|
||||||
* 低档 n 不更小 = 同样的活,只多一次落框重采样):
|
|
||||||
* - 「档位」(step)本来就是 JPEG 缩解的概念;PNG/BMP 后端按目标逐行丢行,本来就不慢,
|
|
||||||
* 而且阅读器的单页峰值模型对非 JPEG 按「输出=显示框」建模——再加一条小图→落框的
|
|
||||||
* 路径会让模型低估。分档只留给真机 168–400ms 的 JPEG 大页。
|
|
||||||
* 返回 0 = 这页不分档,直接按目标档位解。 */
|
|
||||||
static int plan_low_dim(const mr_img_info *in, int box_w, int box_h)
|
|
||||||
{
|
|
||||||
if (!in || in->w <= 0 || in->h <= 0) return 0;
|
|
||||||
if (in->codec != MR_IMG_CODEC_JPEG) return 0;
|
|
||||||
if ((long long)in->w * in->h < MR_TIER_MIN_PIXELS) return 0;
|
|
||||||
int l0 = in->w > in->h ? in->w : in->h;
|
|
||||||
int bl = box_w > box_h ? box_w : box_h;
|
|
||||||
if (l0 <= 0 || bl <= 0) return 0;
|
|
||||||
int lo = bl / 2;
|
|
||||||
if (lo < MR_LOW_MIN_LONG) lo = MR_LOW_MIN_LONG;
|
|
||||||
if (lo > MR_LOW_MAX_LONG) lo = MR_LOW_MAX_LONG;
|
|
||||||
if (lo >= bl) return 0; /* 显示框本来就不大:没有更粗的余地 */
|
|
||||||
int nh = (int)((8LL * bl + l0 - 1) / l0);
|
|
||||||
int nl = (int)((8LL * lo + l0 - 1) / l0);
|
|
||||||
if (nh < 1) nh = 1;
|
|
||||||
if (nl < 1) nl = 1;
|
|
||||||
if (nl >= nh) return 0; /* 同一 DCT 档:低档并不更快 */
|
|
||||||
return lo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按「解码目标长边」跑一次 page_source 的唯一峰值模型(含超预算时继续加粗档位的旧逻辑)。
|
|
||||||
* JPEG 档位由目标长边反推,与 meow_img_load_file_step_ex 的 eff 口径一致;模型在
|
|
||||||
* 「step>1 且输出≠显示框」时会把落框临时图算进峰值——低档走的正是这条已校准的路径。 */
|
|
||||||
static void estimate_for(const mr_img_info *in, int load_dim, int box_w, int box_h,
|
|
||||||
long mfree, long mavail, mr_decode_decision *dec)
|
|
||||||
{
|
|
||||||
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, load_dim);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
||||||
@@ -547,8 +438,8 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
|||||||
uint64_t t0 = meow_ui_time_ms();
|
uint64_t t0 = meow_ui_time_ms();
|
||||||
if (!path || !path[0]) return NULL;
|
if (!path || !path[0]) return NULL;
|
||||||
|
|
||||||
/* 解码计划由 queue_page() 在主线程算好(显示框 + 落地上限 + 档位);worker/主线程
|
/* 解码计划由 queue_page() 在主线程算好(显示框 + 落地上限);worker/主线程同步兜底
|
||||||
* 同步兜底路径在 mr_book_open 前写入。无计划时退回旧预算 maxdim。 */
|
* 路径在 mr_book_open 前写入。无计划时退回旧预算 maxdim。 */
|
||||||
int want_w = g_plan.want_w, want_h = g_plan.want_h;
|
int want_w = g_plan.want_w, want_h = g_plan.want_h;
|
||||||
int box_w = g_plan.box_w, box_h = g_plan.box_h;
|
int box_w = g_plan.box_w, box_h = g_plan.box_h;
|
||||||
int page = g_plan_page;
|
int page = g_plan_page;
|
||||||
@@ -569,49 +460,30 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
|||||||
* reader_estimate_peak 同时给出「走后端还是 stb」与「是否占位」。后端能接住的页
|
* reader_estimate_peak 同时给出「走后端还是 stb」与「是否占位」。后端能接住的页
|
||||||
* (M23,峰值与源尺寸无关)直接放行;只有回退 stb 的页才参与峰值预算/MemAvailable
|
* (M23,峰值与源尺寸无关)直接放行;只有回退 stb 的页才参与峰值预算/MemAvailable
|
||||||
* 门控。模型不再在本文件另写一份。 */
|
* 门控。模型不再在本文件另写一份。 */
|
||||||
int tier = g_plan.tier;
|
|
||||||
mr_img_info in;
|
mr_img_info in;
|
||||||
int have = mr_probe_info(path, &in);
|
int have = mr_probe_info(path, &in);
|
||||||
int load_dim = dim; /* 本次解码的输出长边(低档 < dim) */
|
|
||||||
mr_decode_decision dec;
|
mr_decode_decision dec;
|
||||||
memset(&dec, 0, sizeof dec);
|
memset(&dec, 0, sizeof dec);
|
||||||
dec.step = 1;
|
dec.step = 1;
|
||||||
g_decode_tier = MR_TIER_HIGH;
|
|
||||||
if (!have) tier = MR_TIER_HIGH; /* 尺寸未知:没有分档依据,按单档解(日志别谎报) */
|
|
||||||
if (have) {
|
if (have) {
|
||||||
if (tier == MR_TIER_LOW) {
|
int step = 1;
|
||||||
int lo = plan_low_dim(&in, box_w, box_h);
|
if (in.codec == MR_IMG_CODEC_JPEG) {
|
||||||
if (lo > 0 && lo < dim) load_dim = lo;
|
int l0 = in.w > in.h ? in.w : in.h;
|
||||||
else tier = MR_TIER_HIGH; /* 这页不值得/无法再粗 → 单档 */
|
step = meow_img_pick_scale(l0, dim);
|
||||||
}
|
}
|
||||||
estimate_for(&in, load_dim, box_w, box_h, mfree, mavail, &dec);
|
for (;;) {
|
||||||
/* 低档多一次落框重采样,峰值比高档高;门控拒绝时退回单档(高档),别把本来
|
reader_estimate_peak(&in, step, box_w, box_h, mfree, mavail, &dec);
|
||||||
* 能显示的页变成「内存不足」占位。非 JPEG 的 stb 回退没有缩解能力(整幅解后
|
/* 后端命中 / 已进预算 / 已到最粗档位:定案。非 JPEG 无档位可调。 */
|
||||||
* 再缩),低档只是白解一遍,同样退回单档。 */
|
if (dec.path == MR_DECODE_PATH_BACKEND || dec.skip == MR_DECODE_OK ||
|
||||||
if (tier == MR_TIER_LOW &&
|
step >= 8 || in.codec != MR_IMG_CODEC_JPEG)
|
||||||
(dec.skip != MR_DECODE_OK ||
|
break;
|
||||||
(dec.path == MR_DECODE_PATH_STB && in.codec != MR_IMG_CODEC_JPEG))) {
|
step *= 2;
|
||||||
tier = MR_TIER_HIGH;
|
|
||||||
load_dim = dim;
|
|
||||||
estimate_for(&in, load_dim, box_w, box_h, mfree, mavail, &dec);
|
|
||||||
}
|
|
||||||
g_decode_tier = tier; /* 实际生效的档位(worker 据此计数/落槽) */
|
|
||||||
/* 两档共存(M31 r2 / 评审 P1-1):高档解码时低档仍在槽里显示,所以这次操作的
|
|
||||||
* 真实峰值 = 已在槽里的常驻 + 本次解码峰值。M27 的模型只描述后者;这里把前者
|
|
||||||
* 加进同一个上限(MR_TIER_TOTAL_CAP),超了就跳过高档、保留低档(页面仍可读)。 */
|
|
||||||
if (g_plan.sharpen && dec.skip == MR_DECODE_OK &&
|
|
||||||
g_plan.resident + dec.peak_bytes > MR_TIER_TOTAL_CAP) {
|
|
||||||
g_decode_skip = MR_SKIP_COEXIST;
|
|
||||||
log_decode(page, in.w, in.h, dec.peak_bytes, mfree, mavail, "skip=coexist",
|
|
||||||
dec.step, dec.path == MR_DECODE_PATH_BACKEND, tier,
|
|
||||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
if (dec.skip != MR_DECODE_OK) {
|
if (dec.skip != MR_DECODE_OK) {
|
||||||
g_decode_skip = dec.skip == MR_DECODE_SKIP_LOWMEM ? MR_SKIP_LOWMEM : MR_SKIP_PEAK;
|
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,
|
log_decode(page, in.w, in.h, dec.peak_bytes, mfree, mavail,
|
||||||
dec.skip == MR_DECODE_SKIP_LOWMEM ? "skip=lowmem" : "skip=peak",
|
dec.skip == MR_DECODE_SKIP_LOWMEM ? "skip=lowmem" : "skip=peak",
|
||||||
dec.step, dec.path == MR_DECODE_PATH_BACKEND, tier,
|
dec.step, dec.path == MR_DECODE_PATH_BACKEND,
|
||||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -623,12 +495,12 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
|||||||
* 18–36MB)。只有判定为 stb 的页才允许 stb。 */
|
* 18–36MB)。只有判定为 stb 的页才允许 stb。 */
|
||||||
int allow_stb = !have || dec.path != MR_DECODE_PATH_BACKEND;
|
int allow_stb = !have || dec.path != MR_DECODE_PATH_BACKEND;
|
||||||
int used_backend = 0;
|
int used_backend = 0;
|
||||||
meow_img *im = have ? meow_img_load_file_step_ex(path, load_dim, dec.step, allow_stb,
|
meow_img *im = have ? meow_img_load_file_step_ex(path, dim, dec.step, allow_stb,
|
||||||
&used_backend)
|
&used_backend)
|
||||||
: meow_img_load_file_dim(path, dim);
|
: meow_img_load_file_dim(path, dim);
|
||||||
if (!im) {
|
if (!im) {
|
||||||
log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "fail",
|
log_decode(page, have ? in.w : 0, have ? in.h : 0, dec.peak_bytes, mfree, mavail, "fail",
|
||||||
dec.step, have && dec.path == MR_DECODE_PATH_BACKEND, tier,
|
dec.step, have && dec.path == MR_DECODE_PATH_BACKEND,
|
||||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -637,18 +509,17 @@ static meow_img *reader_load_page(const char *path, void *ud, int maxdim)
|
|||||||
meow_img *small = meow_img_downscale(im, dim);
|
meow_img *small = meow_img_downscale(im, dim);
|
||||||
if (small) { meow_img_free(im); im = small; }
|
if (small) { meow_img_free(im); im = small; }
|
||||||
}
|
}
|
||||||
/* 3) 精确落到显示框(低档过渡图 / 故意用了粗档位时):结果 = box → 命中页仍是一次原生
|
/* 3) 精确落到显示框(仅当「故意用了粗档位」时):结果 = box → 命中页仍是一次原生 blit,
|
||||||
* blit,不会把「先糊后清」变成每帧重采样(M17 的热路径纪律)。低档也落满显示框,
|
* 不会把内存换来的降级变成每帧重采样(M17 的热路径纪律)。
|
||||||
* 所以换高档时尺寸不变:不闪白、不重置缩放/滚动,且两档在槽里是同一张的位置。
|
|
||||||
* 源图本身就小于显示框(小图/缩略图)时绝不放大:那会凭空多占常驻字节,
|
* 源图本身就小于显示框(小图/缩略图)时绝不放大:那会凭空多占常驻字节,
|
||||||
* 且 M17 的 slot_ready 语义依赖「SDK 不做上采样」。 */
|
* 且 M17 的 slot_ready 语义依赖「SDK 不做上采样」。 */
|
||||||
if (have && (tier == MR_TIER_LOW || dec.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_w(im) != box_w || meow_img_h(im) != box_h)) {
|
||||||
meow_img *fit = meow_img_resample(im, box_w, box_h);
|
meow_img *fit = meow_img_resample(im, box_w, box_h);
|
||||||
if (fit) { meow_img_free(im); im = fit; }
|
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, mfree, mavail, "ok",
|
||||||
dec.step, used_backend, tier,
|
dec.step, used_backend,
|
||||||
(unsigned long long)(meow_ui_time_ms() - t0));
|
(unsigned long long)(meow_ui_time_ms() - t0));
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
@@ -809,8 +680,6 @@ static void slot_clear(int i)
|
|||||||
g_slot[i].idx = -1;
|
g_slot[i].idx = -1;
|
||||||
g_slot[i].src_w = g_slot[i].src_h = 0;
|
g_slot[i].src_w = g_slot[i].src_h = 0;
|
||||||
g_slot[i].want_w = g_slot[i].want_h = 0;
|
g_slot[i].want_w = g_slot[i].want_h = 0;
|
||||||
g_slot[i].tier = MR_TIER_HIGH;
|
|
||||||
g_slot[i].hi_tried = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void slots_free_all(void)
|
static void slots_free_all(void)
|
||||||
@@ -863,11 +732,9 @@ static void slots_reserve(long need_bytes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 放进缓存;同页旧图直接替换(fit/zoom/模式变了要换尺寸;低档→高档也走这里)。
|
/* 放进缓存;同页旧图直接替换(fit/zoom/模式变了要换尺寸)。返回 1=已放入。
|
||||||
* 返回 1=已放入。want_w/want_h = 这次解码按哪个显示框落的,交给 slot_ready 做几何判据。
|
* want_w/want_h = 这次解码按哪个显示框落的,交给 slot_ready 做几何判据。 */
|
||||||
* tier 记下这一槽的清晰度:低档图先顶上,高档图落地时**原地替换**(同一槽、同一尺寸),
|
static int slot_put(int idx, meow_img *img, int want_w, int want_h)
|
||||||
* 页面不闪白、不重置缩放/滚动,常驻也不会一页两张。 */
|
|
||||||
static int slot_put(int idx, meow_img *img, int want_w, int want_h, int tier)
|
|
||||||
{
|
{
|
||||||
if (!img || idx < 0) return 0;
|
if (!img || idx < 0) return 0;
|
||||||
int i = -1, empty = -1;
|
int i = -1, empty = -1;
|
||||||
@@ -899,7 +766,6 @@ static int slot_put(int idx, meow_img *img, int want_w, int want_h, int tier)
|
|||||||
if (slot_evict_far(i) < 0) break; /* 只剩目标槽:这一页必须放进来 */
|
if (slot_evict_far(i) < 0) break; /* 只剩目标槽:这一页必须放进来 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 低档 → 高档替换:低档图在这里释放,不会与高档同时常驻。 */
|
|
||||||
slot_clear(i);
|
slot_clear(i);
|
||||||
g_slot[i].idx = idx;
|
g_slot[i].idx = idx;
|
||||||
g_slot[i].img = img;
|
g_slot[i].img = img;
|
||||||
@@ -907,7 +773,6 @@ static int slot_put(int idx, meow_img *img, int want_w, int want_h, int tier)
|
|||||||
g_slot[i].src_h = meow_img_h(img);
|
g_slot[i].src_h = meow_img_h(img);
|
||||||
g_slot[i].want_w = want_w;
|
g_slot[i].want_w = want_w;
|
||||||
g_slot[i].want_h = want_h;
|
g_slot[i].want_h = want_h;
|
||||||
g_slot[i].tier = (unsigned char)(tier == MR_TIER_LOW ? MR_TIER_LOW : MR_TIER_HIGH);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,38 +835,21 @@ static int req_queued(int idx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 预取闸门(任务 4):拖动/惯性期间只解落点页,不做邻居预取;且只在内存确实富裕时预取。
|
/* 预取闸门(任务 4):拖动/惯性期间只解落点页,不做邻居预取;且只在内存确实富裕时预取。
|
||||||
* MemAvailable 读不到按「不预取」处理(保守)。
|
* MemAvailable 读不到按「不预取」处理(保守)。 */
|
||||||
* M31 r2(评审 P1-2):分页模式下焦点页还是低档 → 停稳 150ms 后就要换清晰,而换清晰为了
|
|
||||||
* 压低两档共存峰值会腾掉非可见页;此刻预取的邻居正好会被挤掉(白解一次,且换完不补就
|
|
||||||
* 每次翻页 miss)。所以「焦点页等着换清晰」期间不预取邻居,等高档落位后由 sharpen_refill()
|
|
||||||
* 按翻页方向补回来。低档已放弃升级(hi_tried)或焦点已清晰时恢复正常预取。 */
|
|
||||||
static int prefetch_allowed(void)
|
static int prefetch_allowed(void)
|
||||||
{
|
{
|
||||||
if (g_dragging) return 0;
|
if (g_dragging) return 0;
|
||||||
if (g_scroll && meow_scroll_active(g_scroll)) return 0;
|
if (g_scroll && meow_scroll_active(g_scroll)) return 0;
|
||||||
if (g_mode == MR_MODE_PAGED) {
|
|
||||||
int f = focus_page();
|
|
||||||
page_slot *s = f >= 0 ? slot_find(f) : NULL;
|
|
||||||
if (s && s->tier == MR_TIER_LOW && !s->hi_tried && page_needs_tier(f)) return 0;
|
|
||||||
}
|
|
||||||
long a = mem_avail_kb();
|
long a = mem_avail_kb();
|
||||||
if (a < 0) return 0;
|
if (a < 0) return 0;
|
||||||
return a >= MR_PREFETCH_MIN_KB;
|
return a >= MR_PREFETCH_MIN_KB;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 大页才分档:真机 1280×2275 / 1280×1032 这类页单次解码 168–400ms;小图本来就快,
|
static void queue_page(int idx)
|
||||||
* 分两档只会多一次解码 + 落框重采样。 */
|
|
||||||
static int page_needs_tier(int idx)
|
|
||||||
{
|
|
||||||
int sw = 0, sh = 0;
|
|
||||||
if (!page_meta_dims(idx, &sw, &sh)) return 0;
|
|
||||||
return (long long)sw * sh >= MR_TIER_MIN_PIXELS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 排一个解码请求。tier 决定解低档还是目标档位;sharpen=1 表示「停稳后换清晰」的追加
|
|
||||||
* 请求——动起来(拖动/翻页/滚动)就由 gen/窗口/sharpen 三条判据一起作废。 */
|
|
||||||
static int queue_page_tier(int idx, int tier, int sharpen)
|
|
||||||
{
|
{
|
||||||
|
if (!mr_book_valid(&g_book) || idx < 0 || idx >= g_count) return;
|
||||||
|
/* 永久失败与「仍在跳过期」的页都不排;后者由 maybe_retry_skipped_focus 解封。 */
|
||||||
|
if (slot_ready(idx) || page_blocked(idx)) return;
|
||||||
/* 几何在这里(主线程)算好塞进请求:worker 不再读 g_fit/g_zoom/g_vw/g_vh。 */
|
/* 几何在这里(主线程)算好塞进请求:worker 不再读 g_fit/g_zoom/g_vw/g_vh。 */
|
||||||
int want_w = 0, want_h = 0;
|
int want_w = 0, want_h = 0;
|
||||||
page_geometry_at(idx, &want_w, &want_h);
|
page_geometry_at(idx, &want_w, &want_h);
|
||||||
@@ -1010,13 +858,8 @@ static int queue_page_tier(int idx, int tier, int sharpen)
|
|||||||
plan_box_size(want_w, want_h, &box_w, &box_h);
|
plan_box_size(want_w, want_h, &box_w, &box_h);
|
||||||
/* 先释放再解码:按这一页的落地字节先淘汰不可见旧图,别让常驻与解码缓冲叠加。 */
|
/* 先释放再解码:按这一页的落地字节先淘汰不可见旧图,别让常驻与解码缓冲叠加。 */
|
||||||
slots_reserve((long)box_w * box_h * 4);
|
slots_reserve((long)box_w * box_h * 4);
|
||||||
int queued = 0;
|
|
||||||
pthread_mutex_lock(&g_lock);
|
pthread_mutex_lock(&g_lock);
|
||||||
/* 去重:同一页只要已排队或已交给 worker(g_req_inflight)就不再发。后者必须单列:
|
if (!g_stop && !req_queued(idx) && g_req_n < MR_REQ_MAX) {
|
||||||
* 请求被 worker 取走后就不在队列里了,只看 req_queued 会在每次窗口刷新时重排一页,
|
|
||||||
* 重复解码还会把刚预取的邻居挤掉(评审 P1-2 的 4 次/页、0 命中)。 */
|
|
||||||
int busy = idx >= 0 && idx < MR_BOOK_MAX_PAGES && g_req_inflight[idx];
|
|
||||||
if (!g_stop && !busy && !req_queued(idx) && g_req_n < MR_REQ_MAX) {
|
|
||||||
page_req *r = &g_req[g_req_tail];
|
page_req *r = &g_req[g_req_tail];
|
||||||
r->idx = idx;
|
r->idx = idx;
|
||||||
r->gen = g_gen;
|
r->gen = g_gen;
|
||||||
@@ -1024,37 +867,11 @@ static int queue_page_tier(int idx, int tier, int sharpen)
|
|||||||
r->want_h = want_h;
|
r->want_h = want_h;
|
||||||
r->box_w = box_w;
|
r->box_w = box_w;
|
||||||
r->box_h = box_h;
|
r->box_h = box_h;
|
||||||
r->tier = (unsigned char)(tier == MR_TIER_LOW ? MR_TIER_LOW : MR_TIER_HIGH);
|
|
||||||
r->sharpen = (unsigned char)(sharpen ? 1 : 0);
|
|
||||||
r->resident = (long long)resident_bytes(); /* 腾完地方之后的常驻快照(两档共存峰值) */
|
|
||||||
g_req_tail = (g_req_tail + 1) % MR_REQ_MAX;
|
g_req_tail = (g_req_tail + 1) % MR_REQ_MAX;
|
||||||
g_req_n++;
|
g_req_n++;
|
||||||
if (idx >= 0 && idx < MR_BOOK_MAX_PAGES) g_req_inflight[idx] = 1;
|
|
||||||
queued = 1;
|
|
||||||
pthread_cond_signal(&g_cv);
|
pthread_cond_signal(&g_cv);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&g_lock);
|
pthread_mutex_unlock(&g_lock);
|
||||||
return queued;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void queue_page(int idx)
|
|
||||||
{
|
|
||||||
if (!mr_book_valid(&g_book) || idx < 0 || idx >= g_count) return;
|
|
||||||
/* 永久失败与「仍在跳过期」的页都不排;后者由 maybe_retry_skipped_focus 解封。 */
|
|
||||||
if (page_blocked(idx)) return;
|
|
||||||
page_slot *s = slot_find(idx);
|
|
||||||
if (g_req_inflight[idx]) return; /* 已排队/在解:别重排(重复解码会挤掉邻居) */
|
|
||||||
if (s && slot_ready(idx)) {
|
|
||||||
/* 已经能显示:只有「低档 → 目标档位」这一种情况还要再解,而且必须是当前焦点页、
|
|
||||||
* 已停稳(g_sharpen_idx 由 tier_pump 在去抖窗口后置位)、并且最近一次升级没失败过。
|
|
||||||
* hi_tried 只在「解坏了 / 预算跳过」时置位(避免每 tick 重试);拖动取消会复位。 */
|
|
||||||
if (s->tier != MR_TIER_LOW || s->hi_tried || g_sharpen_idx != idx ||
|
|
||||||
!page_needs_tier(idx))
|
|
||||||
return;
|
|
||||||
queue_page_tier(idx, MR_TIER_HIGH, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
queue_page_tier(idx, page_needs_tier(idx) ? MR_TIER_LOW : MR_TIER_HIGH, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *reader_worker(void *arg)
|
static void *reader_worker(void *arg)
|
||||||
@@ -1068,26 +885,15 @@ static void *reader_worker(void *arg)
|
|||||||
req = g_req[g_req_head];
|
req = g_req[g_req_head];
|
||||||
g_req_head = (g_req_head + 1) % MR_REQ_MAX;
|
g_req_head = (g_req_head + 1) % MR_REQ_MAX;
|
||||||
g_req_n--;
|
g_req_n--;
|
||||||
/* 取消(任务 4 / M20 P0-3;M31 扩展):出队后、mr_book_open 前二次判过期——旧册
|
/* 取消(任务 4 / M20 P0-3):出队后、mr_book_open 前二次判过期——旧册(gen 变)
|
||||||
* (gen 变)或已经不在解码窗(拖动/快翻的中间页)直接丢弃。M20 实测拖动翻页原本
|
* 或已经不在解码窗(拖动/快翻的中间页)直接丢弃。M20 实测拖动翻页原本 1.75× 解码,
|
||||||
* 1.75× 解码,全是这种在飞不可取消的请求。锁内判,窗口/sharpen 目标也在锁内写,
|
* 全是这种在飞不可取消的请求。锁内判,窗口也在锁内写,避免共享状态竞争。 */
|
||||||
* 避免共享状态竞争。
|
|
||||||
* M31:拖动/连翻期间只许解低档——「停稳换清晰」的请求一旦动起来就作废(复用同一条
|
|
||||||
* stale 判据,不新造取消通道)。 */
|
|
||||||
int stale = (req.gen != g_gen) || !in_window(req.idx);
|
int stale = (req.gen != g_gen) || !in_window(req.idx);
|
||||||
int stale_motion = 0;
|
|
||||||
if (!stale && req.sharpen && req.idx != g_sharpen_idx) {
|
|
||||||
stale = 1;
|
|
||||||
stale_motion = 1;
|
|
||||||
}
|
|
||||||
if (stale && req.idx >= 0 && req.idx < MR_BOOK_MAX_PAGES) g_req_inflight[req.idx] = 0;
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
pthread_mutex_unlock(&g_lock);
|
||||||
if (stale) {
|
if (stale) {
|
||||||
if (stale_motion) g_high_canceled++;
|
|
||||||
meow_log(MEOW_LOG_INFO,
|
meow_log(MEOW_LOG_INFO,
|
||||||
"reader decode page=%d src=0x0 peak=0kB memfree=%ldkB memavail=%ldkB skip=cancel step=0 tier=%s",
|
"reader decode page=%d src=0x0 peak=0kB memfree=%ldkB memavail=%ldkB skip=cancel step=0",
|
||||||
req.idx, mem_free_kb(), mem_avail_kb(),
|
req.idx, mem_free_kb(), mem_avail_kb());
|
||||||
req.tier == MR_TIER_LOW ? "low" : "high");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,19 +902,10 @@ static void *reader_worker(void *arg)
|
|||||||
g_plan.want_h = req.want_h;
|
g_plan.want_h = req.want_h;
|
||||||
g_plan.box_w = req.box_w;
|
g_plan.box_w = req.box_w;
|
||||||
g_plan.box_h = req.box_h;
|
g_plan.box_h = req.box_h;
|
||||||
g_plan.tier = req.tier;
|
|
||||||
g_plan.sharpen = req.sharpen;
|
|
||||||
g_plan.resident = req.resident;
|
|
||||||
g_plan_page = req.idx;
|
g_plan_page = req.idx;
|
||||||
g_decode_skip = MR_SKIP_NONE;
|
g_decode_skip = MR_SKIP_NONE;
|
||||||
g_decode_tier = MR_TIER_HIGH;
|
|
||||||
meow_img *img = mr_book_open(&g_book, req.idx);
|
meow_img *img = mr_book_open(&g_book, req.idx);
|
||||||
g_decode_count++;
|
g_decode_count++;
|
||||||
/* 档位以解码回调**实际生效**的为准:低档请求遇到非 JPEG/不值得分档的页会降为
|
|
||||||
* 单档(reader_load_page 把 g_decode_tier 置回 HIGH),落槽也就不会误标成低档。 */
|
|
||||||
int eff_tier = g_decode_tier;
|
|
||||||
if (eff_tier == MR_TIER_LOW) g_low_decodes++;
|
|
||||||
else g_high_decodes++;
|
|
||||||
int skip = img ? MR_SKIP_NONE : g_decode_skip;
|
int skip = img ? MR_SKIP_NONE : g_decode_skip;
|
||||||
|
|
||||||
pthread_mutex_lock(&g_lock);
|
pthread_mutex_lock(&g_lock);
|
||||||
@@ -1120,13 +917,9 @@ static void *reader_worker(void *arg)
|
|||||||
g_res[g_res_n].skip = skip;
|
g_res[g_res_n].skip = skip;
|
||||||
g_res[g_res_n].want_w = req.want_w;
|
g_res[g_res_n].want_w = req.want_w;
|
||||||
g_res[g_res_n].want_h = req.want_h;
|
g_res[g_res_n].want_h = req.want_h;
|
||||||
g_res[g_res_n].tier = (unsigned char)eff_tier;
|
|
||||||
g_res[g_res_n].sharpen = req.sharpen;
|
|
||||||
g_res_n++;
|
g_res_n++;
|
||||||
img = NULL; /* 所有权已移交(失败/跳过时本就是 NULL) */
|
img = NULL; /* 所有权已移交(失败/跳过时本就是 NULL) */
|
||||||
}
|
}
|
||||||
/* 本页请求已经走完(无论落不落槽):解除去重标记,允许下一轮重新判定。 */
|
|
||||||
if (req.idx >= 0 && req.idx < MR_BOOK_MAX_PAGES) g_req_inflight[req.idx] = 0;
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
pthread_mutex_unlock(&g_lock);
|
||||||
if (img) meow_img_free(img);
|
if (img) meow_img_free(img);
|
||||||
}
|
}
|
||||||
@@ -1155,12 +948,10 @@ static void worker_stop(void)
|
|||||||
g_res_n = 0;
|
g_res_n = 0;
|
||||||
g_req_n = 0;
|
g_req_n = 0;
|
||||||
g_req_head = g_req_tail = 0;
|
g_req_head = g_req_tail = 0;
|
||||||
memset(g_req_inflight, 0, sizeof g_req_inflight);
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
pthread_mutex_unlock(&g_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 丢弃窗口外的排队请求(主线程调用)。M31:还没出队的「停稳换清晰」请求,一旦动起来
|
/* 丢弃窗口外的排队请求(主线程调用)。 */
|
||||||
* (g_sharpen_idx 被清)也在这里作废,不必等 worker 出队才发现。 */
|
|
||||||
static void queue_prune(void)
|
static void queue_prune(void)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_lock);
|
pthread_mutex_lock(&g_lock);
|
||||||
@@ -1168,22 +959,11 @@ static void queue_prune(void)
|
|||||||
int n = 0;
|
int n = 0;
|
||||||
for (int i = 0; i < g_req_n; i++) {
|
for (int i = 0; i < g_req_n; i++) {
|
||||||
page_req r = g_req[(g_req_head + i) % MR_REQ_MAX];
|
page_req r = g_req[(g_req_head + i) % MR_REQ_MAX];
|
||||||
int drop = 0;
|
if (r.gen != g_gen || !in_window(r.idx)) continue;
|
||||||
if (r.gen != g_gen || !in_window(r.idx)) drop = 1;
|
|
||||||
if (!drop && r.sharpen && r.idx != g_sharpen_idx) {
|
|
||||||
g_high_canceled++;
|
|
||||||
drop = 1;
|
|
||||||
}
|
|
||||||
/* 几何已变的请求作废(否则会按旧显示框解出来又被判过期)。 */
|
/* 几何已变的请求作废(否则会按旧显示框解出来又被判过期)。 */
|
||||||
if (!drop) {
|
int w = 0, h = 0;
|
||||||
int w = 0, h = 0;
|
page_geometry_at(r.idx, &w, &h);
|
||||||
page_geometry_at(r.idx, &w, &h);
|
if (abs(w - r.want_w) > MR_SLOT_TOL || abs(h - r.want_h) > MR_SLOT_TOL) continue;
|
||||||
if (abs(w - r.want_w) > MR_SLOT_TOL || abs(h - r.want_h) > MR_SLOT_TOL) drop = 1;
|
|
||||||
}
|
|
||||||
if (drop) {
|
|
||||||
if (r.idx >= 0 && r.idx < MR_BOOK_MAX_PAGES) g_req_inflight[r.idx] = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
keep[n++] = r;
|
keep[n++] = r;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < n; i++) g_req[i] = keep[i];
|
for (int i = 0; i < n; i++) g_req[i] = keep[i];
|
||||||
@@ -1194,9 +974,7 @@ static void queue_prune(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 收取后台结果:只留窗口内新页。返回 1=有图落位。
|
/* 收取后台结果:只留窗口内新页。返回 1=有图落位。
|
||||||
* 结果分三种:ok(落槽)/ fail(坏图,永久占位)/ skip(预算跳过,可重试占位)。
|
* 结果分三种:ok(落槽)/ fail(坏图,永久占位)/ skip(预算跳过,可重试占位)。 */
|
||||||
* M31:高档(停稳换清晰)失败/跳过时,低档已经在屏幕上——保留低档继续看,不把整页标成
|
|
||||||
* 「内存不足」或坏图;低档自己的失败/跳过才走旧语义(那才是真的没图)。 */
|
|
||||||
static int results_drain(void)
|
static int results_drain(void)
|
||||||
{
|
{
|
||||||
page_res got[MR_RES_MAX];
|
page_res got[MR_RES_MAX];
|
||||||
@@ -1210,29 +988,21 @@ static int results_drain(void)
|
|||||||
int changed = 0;
|
int changed = 0;
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (got[i].gen != g_gen) { meow_img_free(got[i].img); continue; }
|
if (got[i].gen != g_gen) { meow_img_free(got[i].img); continue; }
|
||||||
page_slot *low = got[i].tier == MR_TIER_HIGH ? slot_find(got[i].idx) : NULL;
|
|
||||||
if (low && low->tier != MR_TIER_LOW) low = NULL; /* 已有目标档:不是「低档兜底」 */
|
|
||||||
if (got[i].skip) {
|
if (got[i].skip) {
|
||||||
/* 预算跳过(内存不足/峰值超限/两档共存超上限):可重试,不写永久失败位。 */
|
/* 预算跳过(内存不足/峰值超限):可重试,不写永久失败位。 */
|
||||||
if (low) { low->hi_tried = 1; continue; } /* 低档在屏:降级显示,不占位 */
|
|
||||||
page_mark_skipped(got[i].idx);
|
page_mark_skipped(got[i].idx);
|
||||||
if (page_visible(got[i].idx)) g_fail_paint = 1;
|
if (page_visible(got[i].idx)) g_fail_paint = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (got[i].fail) {
|
if (got[i].fail) {
|
||||||
if (low) { low->hi_tried = 1; continue; }
|
|
||||||
page_mark_failed(got[i].idx);
|
page_mark_failed(got[i].idx);
|
||||||
if (page_visible(got[i].idx)) g_fail_paint = 1;
|
if (page_visible(got[i].idx)) g_fail_paint = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (in_window(got[i].idx)) {
|
if (in_window(got[i].idx)) {
|
||||||
if (slot_put(got[i].idx, got[i].img, got[i].want_w, got[i].want_h,
|
if (slot_put(got[i].idx, got[i].img, got[i].want_w, got[i].want_h) &&
|
||||||
got[i].tier) && page_visible(got[i].idx))
|
page_visible(got[i].idx)) changed = 1;
|
||||||
changed = 1;
|
|
||||||
page_skip_clear(got[i].idx); /* 解出来了:撤销跳过标记 */
|
page_skip_clear(got[i].idx); /* 解出来了:撤销跳过标记 */
|
||||||
/* 换清晰落位:换清晰前为压峰值腾掉了邻居,这里补回(P1-2)。
|
|
||||||
* 普通预取/首次解码不腾邻居,不需要补。 */
|
|
||||||
if (got[i].sharpen) g_refill = 1;
|
|
||||||
} else {
|
} else {
|
||||||
meow_img_free(got[i].img);
|
meow_img_free(got[i].img);
|
||||||
}
|
}
|
||||||
@@ -1276,86 +1046,6 @@ static void window_update(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================ */
|
|
||||||
/* 两级清晰度的停稳去抖(M31) */
|
|
||||||
/* ------------------------------------------------------------
|
|
||||||
* 规则只有三条:
|
|
||||||
* 1) 拖动 / 吸附动画 / 条漫滚动(含 fling)期间一律不动高档:g_sharpen_idx=-1,
|
|
||||||
* 在飞/排队的「停稳换清晰」请求按 M22 的取消通道作废;跳页在途时同样不解高档
|
|
||||||
* (单 worker 串行,导航优先);
|
|
||||||
* 2) 停稳 MR_SHARPEN_MS 后,焦点页若是低档,才允许它解目标档位(g_sharpen_idx=focus);
|
|
||||||
* 3) 每次「动」都重置停稳计时;换页/换几何也会清掉资格(重新计时)。
|
|
||||||
* 高档只发给焦点页:单 worker 串行,先保证用户正看的那页清晰,邻居留低档待翻到再说。 */
|
|
||||||
static int motion_active(void)
|
|
||||||
{
|
|
||||||
if (g_dragging) return 1;
|
|
||||||
if (g_snap || g_bounce) return 1;
|
|
||||||
if (g_scroll && meow_scroll_active(g_scroll)) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 「动」了:重置停稳计时,并取消当前的高档资格(排队/在飞的请求随之作废)。
|
|
||||||
* 被取消的这一页如果还是低档,把 hi_tried 复位——停稳后还能再试一次。 */
|
|
||||||
static void settle_reset(void)
|
|
||||||
{
|
|
||||||
g_move_ms = meow_ui_time_ms();
|
|
||||||
if (g_sharpen_idx < 0) return;
|
|
||||||
page_slot *s = slot_find(g_sharpen_idx);
|
|
||||||
if (s && s->tier == MR_TIER_LOW) s->hi_tried = 0;
|
|
||||||
pthread_mutex_lock(&g_lock);
|
|
||||||
g_sharpen_idx = -1;
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
|
||||||
queue_prune();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tier_pump(uint64_t now)
|
|
||||||
{
|
|
||||||
if (g_count <= 0 || g_quitting) return;
|
|
||||||
if (motion_active()) {
|
|
||||||
g_move_ms = now;
|
|
||||||
if (g_sharpen_idx >= 0) settle_reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* 跳页在途:单 worker 串行,「换清晰」是锦上添花,不能插在跳页目标的低档前面——
|
|
||||||
* 否则滑块一拖就是几秒(早已过停稳窗口),老页的高档会把跳页落位往后挤几百 ms。 */
|
|
||||||
if (mr_jump_active(&g_jump)) {
|
|
||||||
if (g_sharpen_idx >= 0) settle_reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int f = focus_page();
|
|
||||||
if (f < 0) return;
|
|
||||||
page_slot *s = slot_find(f);
|
|
||||||
if (!s || s->tier != MR_TIER_LOW || s->hi_tried || !page_needs_tier(f)) {
|
|
||||||
if (g_sharpen_idx == f) {
|
|
||||||
pthread_mutex_lock(&g_lock);
|
|
||||||
g_sharpen_idx = -1;
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (now - g_move_ms < (uint64_t)g_sharpen_ms) return; /* 还没停稳 */
|
|
||||||
if (g_sharpen_idx != f) {
|
|
||||||
pthread_mutex_lock(&g_lock);
|
|
||||||
g_sharpen_idx = f;
|
|
||||||
pthread_mutex_unlock(&g_lock);
|
|
||||||
}
|
|
||||||
queue_page(f); /* 幂等:已排队/在飞/已清晰/已试过都直接返回 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 高档落位后补邻居(M31 r2 / 评审 P1-2)。
|
|
||||||
* 换清晰前 slots_reserve 会腾掉非可见页来压两档共存峰值,顺序翻页时被腾掉的正是
|
|
||||||
* 「下一页」的预取;不补回来,每次翻页都会 miss(基线 8/8 → 分档后 0/8)。
|
|
||||||
* 只补最近一次翻页方向上的邻居:反方向那张在 3MB 常驻上限下放不下(必然被下一次预取
|
|
||||||
* 挤掉),白解一次还抬高解码次数;回头看时按需解一版低档——低档本来就是为这个设计的。 */
|
|
||||||
static void sharpen_refill(void)
|
|
||||||
{
|
|
||||||
if (g_mode != MR_MODE_PAGED || g_count <= 0 || g_quitting) return;
|
|
||||||
int f = focus_page();
|
|
||||||
if (f < 0) return;
|
|
||||||
int fwd = f + (g_step_dir >= 0 ? 1 : -1);
|
|
||||||
if (fwd >= 0 && fwd < g_count && !slot_find(fwd)) queue_page(fwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
/* 条漫排版 + 滚动会话 */
|
/* 条漫排版 + 滚动会话 */
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
@@ -1679,7 +1369,6 @@ static void set_fit(int fit)
|
|||||||
g_fit = (fit < 0 || fit >= MR_FIT_COUNT) ? MR_FIT_WIDTH : fit;
|
g_fit = (fit < 0 || fit >= MR_FIT_COUNT) ? MR_FIT_WIDTH : fit;
|
||||||
g_zoom_pct = 100;
|
g_zoom_pct = 100;
|
||||||
g_pan_x = g_pan_y = 0;
|
g_pan_x = g_pan_y = 0;
|
||||||
settle_reset(); /* 几何变了:停稳计时重来,低档先上屏再换清晰 */
|
|
||||||
window_update();
|
window_update();
|
||||||
update_title();
|
update_title();
|
||||||
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
||||||
@@ -1707,14 +1396,11 @@ static void pan_by(int dx, int dy)
|
|||||||
|
|
||||||
static void commit_page(int target)
|
static void commit_page(int target)
|
||||||
{
|
{
|
||||||
int prev = g_page;
|
|
||||||
target = mr_nav_clamp_page(target, g_count);
|
target = mr_nav_clamp_page(target, g_count);
|
||||||
if (target < 0) return;
|
if (target < 0) return;
|
||||||
g_page = target;
|
g_page = target;
|
||||||
if (target != prev) g_step_dir = target > prev ? 1 : -1; /* 高档落位后按这个方向补邻居 */
|
|
||||||
g_zoom_pct = 100;
|
g_zoom_pct = 100;
|
||||||
g_pan_x = g_pan_y = 0;
|
g_pan_x = g_pan_y = 0;
|
||||||
settle_reset(); /* 换页:停稳计时重来(拖动/连翻期间不解高档) */
|
|
||||||
window_update();
|
window_update();
|
||||||
update_title();
|
update_title();
|
||||||
slider_sync();
|
slider_sync();
|
||||||
@@ -1821,13 +1507,11 @@ static void page_flip(int delta)
|
|||||||
|
|
||||||
g_flip_t0 = meow_ui_time_ms();
|
g_flip_t0 = meow_ui_time_ms();
|
||||||
if (slot_find(target)) {
|
if (slot_find(target)) {
|
||||||
g_flip_hits++;
|
|
||||||
if (start_slide(delta, 0))
|
if (start_slide(delta, 0))
|
||||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit", g_page + 1, target + 1);
|
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit", g_page + 1, target + 1);
|
||||||
else
|
else
|
||||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit (no anim slot)", g_page + 1, target + 1);
|
meow_log(MEOW_LOG_INFO, "reader flip %d->%d hit (no anim slot)", g_page + 1, target + 1);
|
||||||
} else {
|
} else {
|
||||||
g_flip_misses++;
|
|
||||||
meow_log(MEOW_LOG_INFO, "reader flip %d->%d miss (decode pending)", g_page + 1, target + 1);
|
meow_log(MEOW_LOG_INFO, "reader flip %d->%d miss (decode pending)", g_page + 1, target + 1);
|
||||||
commit_page(target);
|
commit_page(target);
|
||||||
}
|
}
|
||||||
@@ -1839,10 +1523,8 @@ static void page_flip(int delta)
|
|||||||
static void jump_land(int page, int timed_out)
|
static void jump_land(int page, int timed_out)
|
||||||
{
|
{
|
||||||
if (g_count <= 0) return;
|
if (g_count <= 0) return;
|
||||||
int prev = current_page();
|
|
||||||
page = mr_nav_clamp_page(page, g_count);
|
page = mr_nav_clamp_page(page, g_count);
|
||||||
if (page < 0) page = 0;
|
if (page < 0) page = 0;
|
||||||
if (page != prev) g_step_dir = page > prev ? 1 : -1; /* 高档落位后按跳页方向补邻居 */
|
|
||||||
if (g_mode == MR_MODE_WEBTOON && g_strip_ok) {
|
if (g_mode == MR_MODE_WEBTOON && g_strip_ok) {
|
||||||
if (g_scroll) meow_scroll_stop(g_scroll);
|
if (g_scroll) meow_scroll_stop(g_scroll);
|
||||||
g_woff = mr_strip_offset_for_page(&g_strip, page);
|
g_woff = mr_strip_offset_for_page(&g_strip, page);
|
||||||
@@ -1853,7 +1535,6 @@ static void jump_land(int page, int timed_out)
|
|||||||
g_pan_x = g_pan_y = 0;
|
g_pan_x = g_pan_y = 0;
|
||||||
}
|
}
|
||||||
slider_sync();
|
slider_sync();
|
||||||
settle_reset(); /* 落位:停稳计时重来 */
|
|
||||||
window_update();
|
window_update();
|
||||||
update_title();
|
update_title();
|
||||||
cont_touch();
|
cont_touch();
|
||||||
@@ -1940,7 +1621,6 @@ static void mode_set(int mode, int from_page)
|
|||||||
g_zoom_pct = 100;
|
g_zoom_pct = 100;
|
||||||
g_pan_x = g_pan_y = 0;
|
g_pan_x = g_pan_y = 0;
|
||||||
slider_sync();
|
slider_sync();
|
||||||
settle_reset(); /* 模式/排版几何变了:停稳计时重来 */
|
|
||||||
window_update();
|
window_update();
|
||||||
update_title();
|
update_title();
|
||||||
cont_touch();
|
cont_touch();
|
||||||
@@ -1983,11 +1663,6 @@ static void open_book_reset_state(void)
|
|||||||
mr_jump_cancel(&g_jump);
|
mr_jump_cancel(&g_jump);
|
||||||
g_toast[0] = '\0';
|
g_toast[0] = '\0';
|
||||||
g_title_page = -1;
|
g_title_page = -1;
|
||||||
g_sharpen_idx = -1; /* 新册:高档资格清零,停稳计时重来 */
|
|
||||||
memset(g_req_inflight, 0, sizeof g_req_inflight);
|
|
||||||
g_move_ms = meow_ui_time_ms();
|
|
||||||
g_step_dir = 1;
|
|
||||||
g_refill = 0;
|
|
||||||
free(g_meta);
|
free(g_meta);
|
||||||
g_meta = NULL;
|
g_meta = NULL;
|
||||||
}
|
}
|
||||||
@@ -2085,10 +1760,9 @@ static int open_book(const char *dir, int start_page, int mode)
|
|||||||
g_plan.want_h = wh;
|
g_plan.want_h = wh;
|
||||||
g_plan.box_w = bw;
|
g_plan.box_w = bw;
|
||||||
g_plan.box_h = bh;
|
g_plan.box_h = bh;
|
||||||
g_plan.tier = MR_TIER_HIGH; /* 没有后台线程:直接解目标档,不分档 */
|
|
||||||
g_plan_page = g_page;
|
g_plan_page = g_page;
|
||||||
meow_img *first = mr_book_open(&g_book, g_page);
|
meow_img *first = mr_book_open(&g_book, g_page);
|
||||||
if (first) slot_put(g_page, first, ww, wh, MR_TIER_HIGH);
|
if (first) slot_put(g_page, first, ww, wh);
|
||||||
meow_log(MEOW_LOG_WARN, "reader worker unavailable, sync first page");
|
meow_log(MEOW_LOG_WARN, "reader worker unavailable, sync first page");
|
||||||
}
|
}
|
||||||
slider_build();
|
slider_build();
|
||||||
@@ -2171,10 +1845,8 @@ static void drag_finish(void)
|
|||||||
}
|
}
|
||||||
g_flip_t0 = meow_ui_time_ms();
|
g_flip_t0 = meow_ui_time_ms();
|
||||||
if (slot_find(target) && start_slide(delta, dx)) {
|
if (slot_find(target) && start_slide(delta, dx)) {
|
||||||
g_flip_hits++;
|
|
||||||
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d hit", g_page + 1, target + 1);
|
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d hit", g_page + 1, target + 1);
|
||||||
} else {
|
} else {
|
||||||
g_flip_misses++;
|
|
||||||
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d miss (decode pending)",
|
meow_log(MEOW_LOG_INFO, "reader drag flip %d->%d miss (decode pending)",
|
||||||
g_page + 1, target + 1);
|
g_page + 1, target + 1);
|
||||||
commit_page(target);
|
commit_page(target);
|
||||||
@@ -2204,7 +1876,6 @@ void reader_on_touch_down(int x, int y)
|
|||||||
{
|
{
|
||||||
if (!g_ctrl.active || g_quitting) return;
|
if (!g_ctrl.active || g_quitting) return;
|
||||||
if (g_count <= 0) return;
|
if (g_count <= 0) return;
|
||||||
settle_reset(); /* 手一按下去就不算停稳:在飞的高档请求作废 */
|
|
||||||
g_touch_moved = 0; /* 每次按下都是新手势(chrome 抬手也不残留) */
|
g_touch_moved = 0; /* 每次按下都是新手势(chrome 抬手也不残留) */
|
||||||
if (y >= g_vh - 28) return; /* 屏底热区:系统回桌面,勿劫持 */
|
if (y >= g_vh - 28) return; /* 屏底热区:系统回桌面,勿劫持 */
|
||||||
if (x < 24) return; /* 左缘:系统返回手势,勿劫持 */
|
if (x < 24) return; /* 左缘:系统返回手势,勿劫持 */
|
||||||
@@ -2384,7 +2055,6 @@ static void tap_double(int x, int y)
|
|||||||
g_pan_y = (g_vh / 2 - y) * (z - 100) / 100;
|
g_pan_y = (g_vh / 2 - y) * (z - 100) / 100;
|
||||||
clamp_pan();
|
clamp_pan();
|
||||||
}
|
}
|
||||||
settle_reset(); /* 缩放几何变了:重新计时(低档先上屏) */
|
|
||||||
window_update();
|
window_update();
|
||||||
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
||||||
}
|
}
|
||||||
@@ -2487,15 +2157,6 @@ static void reader_tick(void)
|
|||||||
/* 只有当落位的页真的在屏幕上(或换了几何的当前页)才重绘,预取邻居不刷屏。 */
|
/* 只有当落位的页真的在屏幕上(或换了几何的当前页)才重绘,预取邻居不刷屏。 */
|
||||||
if (results_drain() && !g_dragging) invalidate_content();
|
if (results_drain() && !g_dragging) invalidate_content();
|
||||||
|
|
||||||
/* 高档落位后补回换清晰时为压峰值腾掉的邻居(P1-2:否则顺序翻页每次都 miss)。 */
|
|
||||||
if (g_refill) {
|
|
||||||
g_refill = 0;
|
|
||||||
if (!g_dragging) sharpen_refill();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 两级清晰度:停稳去抖(拖动/滚动期间只留低档,停稳后才补高档)。 */
|
|
||||||
tier_pump(now);
|
|
||||||
|
|
||||||
/* 打开册的瞬时资源失败:稍后自动重试一次(进入本页时已停 library 后台解码)。 */
|
/* 打开册的瞬时资源失败:稍后自动重试一次(进入本页时已停 library 后台解码)。 */
|
||||||
if (g_open_err && g_open_retry_at && now >= g_open_retry_at) {
|
if (g_open_err && g_open_retry_at && now >= g_open_retry_at) {
|
||||||
g_open_retry_at = 0;
|
g_open_retry_at = 0;
|
||||||
@@ -2651,17 +2312,6 @@ static void reader_state_reset(void)
|
|||||||
memset(&g_plan, 0, sizeof g_plan);
|
memset(&g_plan, 0, sizeof g_plan);
|
||||||
g_plan_page = 0;
|
g_plan_page = 0;
|
||||||
g_decode_skip = MR_SKIP_NONE;
|
g_decode_skip = MR_SKIP_NONE;
|
||||||
g_decode_tier = MR_TIER_HIGH;
|
|
||||||
g_sharpen_idx = -1;
|
|
||||||
memset(g_req_inflight, 0, sizeof g_req_inflight);
|
|
||||||
g_move_ms = meow_ui_time_ms();
|
|
||||||
g_step_dir = 1;
|
|
||||||
g_refill = 0;
|
|
||||||
g_low_decodes = 0;
|
|
||||||
g_high_decodes = 0;
|
|
||||||
g_high_canceled = 0;
|
|
||||||
g_flip_hits = 0;
|
|
||||||
g_flip_misses = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reader_page_enter(meow_view *v, void *ctx)
|
static void reader_page_enter(meow_view *v, void *ctx)
|
||||||
@@ -2877,49 +2527,6 @@ int mr_reader_ui_scaled_draws(void) { return g_scaled_draws; }
|
|||||||
int mr_reader_ui_decode_count(void) { return g_decode_count; }
|
int mr_reader_ui_decode_count(void) { return g_decode_count; }
|
||||||
int mr_reader_ui_fit(void) { return g_fit; }
|
int mr_reader_ui_fit(void) { return g_fit; }
|
||||||
int mr_reader_ui_chrome(void) { return g_chrome; }
|
int mr_reader_ui_chrome(void) { return g_chrome; }
|
||||||
/* M31 两级清晰度观察口:焦点页当前档位(-1=无图/0=低档/1=目标档位)、各档解码次数、
|
|
||||||
* 被取消的高档请求数、当前允许升级的页(-1=拖动/未停稳/无需升级)。 */
|
|
||||||
int mr_reader_ui_focus_tier(void)
|
|
||||||
{
|
|
||||||
page_slot *s = slot_find(focus_page());
|
|
||||||
if (!s) return -1;
|
|
||||||
return s->tier == MR_TIER_LOW ? MR_TIER_LOW : MR_TIER_HIGH;
|
|
||||||
}
|
|
||||||
int mr_reader_ui_slot_tier(int idx)
|
|
||||||
{
|
|
||||||
page_slot *s = slot_find(idx);
|
|
||||||
if (!s) return -1;
|
|
||||||
return s->tier == MR_TIER_LOW ? MR_TIER_LOW : MR_TIER_HIGH;
|
|
||||||
}
|
|
||||||
long mr_reader_ui_slot_kb(int idx)
|
|
||||||
{
|
|
||||||
page_slot *s = slot_find(idx);
|
|
||||||
if (!s) return 0;
|
|
||||||
return (long)s->src_w * s->src_h * 4 / 1024;
|
|
||||||
}
|
|
||||||
int mr_reader_ui_low_decodes(void) { return g_low_decodes; }
|
|
||||||
int mr_reader_ui_high_decodes(void) { return g_high_decodes; }
|
|
||||||
int mr_reader_ui_high_canceled(void) { return g_high_canceled; }
|
|
||||||
int mr_reader_ui_sharpen_idx(void) { return g_sharpen_idx; }
|
|
||||||
/* 翻页命中/未命中:目标页已在槽(= 预取生效,翻页不用等解码)/ 还没解好。 */
|
|
||||||
int mr_reader_ui_flip_hits(void) { return g_flip_hits; }
|
|
||||||
int mr_reader_ui_flip_misses(void) { return g_flip_misses; }
|
|
||||||
/* 视图位置(M31 r2 评审 P2-2:换清晰不得重置缩放/平移/条漫滚动位置)。 */
|
|
||||||
int mr_reader_ui_zoom_pct(void) { return g_zoom_pct; }
|
|
||||||
int mr_reader_ui_pan_x(void) { return g_pan_x; }
|
|
||||||
int mr_reader_ui_pan_y(void) { return g_pan_y; }
|
|
||||||
int mr_reader_ui_woff(void) { return g_woff; }
|
|
||||||
/* 宿主测试改写「停稳」窗口:0=立刻允许高档(同步验证落位),很大=冻结高档(验证低档先落位)。 */
|
|
||||||
void mr_reader_ui_test_set_sharpen_ms(int ms)
|
|
||||||
{
|
|
||||||
g_sharpen_ms = ms < 0 ? 0 : ms;
|
|
||||||
}
|
|
||||||
/* 宿主测试:绕过停稳判定强行排一个「换清晰」请求,用于验证拖动中会被取消。 */
|
|
||||||
void mr_reader_ui_test_queue_high(int idx)
|
|
||||||
{
|
|
||||||
if (idx < 0 || idx >= g_count) return;
|
|
||||||
queue_page_tier(idx, MR_TIER_HIGH, 1);
|
|
||||||
}
|
|
||||||
/* 直接切模式(避开顶栏 tap 命中时序):与顶栏动作走同一个 mode_set。 */
|
/* 直接切模式(避开顶栏 tap 命中时序):与顶栏动作走同一个 mode_set。 */
|
||||||
void mr_reader_test_toggle_mode(void)
|
void mr_reader_test_toggle_mode(void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,11 +10,6 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* 两级清晰度档位(M31):低档=先上屏的过渡图,高档=停稳后替换的目标档位。
|
|
||||||
* 同时供宿主测试断言槽内档位用(mr_reader_ui_slot_tier / mr_reader_ui_focus_tier)。 */
|
|
||||||
#define MR_TIER_LOW 0
|
|
||||||
#define MR_TIER_HIGH 1
|
|
||||||
|
|
||||||
void reader_on_touch_down(int x, int y);
|
void reader_on_touch_down(int x, int y);
|
||||||
void reader_on_touch_move(int x, int y);
|
void reader_on_touch_move(int x, int y);
|
||||||
|
|
||||||
@@ -56,24 +51,6 @@ long mr_reader_ui_resident_cap_kb(void); /* 常驻上限(kB) */
|
|||||||
int mr_reader_ui_blit_draws(void); /* 累计原生 blit 绘页次数(热路径应为纯 blit) */
|
int mr_reader_ui_blit_draws(void); /* 累计原生 blit 绘页次数(热路径应为纯 blit) */
|
||||||
int mr_reader_ui_scaled_draws(void);/* 累计重采样绘页次数(每帧重采样=卡顿) */
|
int mr_reader_ui_scaled_draws(void);/* 累计重采样绘页次数(每帧重采样=卡顿) */
|
||||||
int mr_reader_ui_decode_count(void);/* 累计解码次数(查重解抖动/活锁) */
|
int mr_reader_ui_decode_count(void);/* 累计解码次数(查重解抖动/活锁) */
|
||||||
/* ---- M31 两级清晰度(先低档秒出,停稳再换档)---- */
|
|
||||||
int mr_reader_ui_focus_tier(void); /* 焦点页档位:-1=无图,0=低档过渡图,1=目标档位 */
|
|
||||||
int mr_reader_ui_slot_tier(int idx);/* 指定页档位:-1=不在槽内,否则同上 */
|
|
||||||
long mr_reader_ui_slot_kb(int idx); /* 指定槽内页图的常驻字节(KB);0=不在槽内 */
|
|
||||||
int mr_reader_ui_low_decodes(void); /* 低档解码次数 */
|
|
||||||
int mr_reader_ui_high_decodes(void);/* 目标档位解码次数 */
|
|
||||||
int mr_reader_ui_high_canceled(void);/* 被取消(拖动/换页/未停稳)的高档请求数 */
|
|
||||||
int mr_reader_ui_sharpen_idx(void); /* 当前允许升级的页;-1=拖动/未停稳/无需升级 */
|
|
||||||
int mr_reader_ui_flip_hits(void); /* 翻页时目标页已在槽(预取命中,不用等解码)次数 */
|
|
||||||
int mr_reader_ui_flip_misses(void); /* 翻页时目标页还没解好(要等解码)次数 */
|
|
||||||
int mr_reader_ui_zoom_pct(void); /* 当前缩放档(100=未放大) */
|
|
||||||
int mr_reader_ui_pan_x(void); /* 当前平移(px) */
|
|
||||||
int mr_reader_ui_pan_y(void);
|
|
||||||
int mr_reader_ui_woff(void); /* 条漫滚动偏移(px) */
|
|
||||||
/* 改写「停稳」窗口(默认 MR_SHARPEN_MS=150ms):0=立刻允许高档,很大=冻结高档。 */
|
|
||||||
void mr_reader_ui_test_set_sharpen_ms(int ms);
|
|
||||||
/* 绕过停稳判定强排一个「换清晰」请求(验证拖动中会被 M22 的取消通道作废)。 */
|
|
||||||
void mr_reader_ui_test_queue_high(int idx);
|
|
||||||
int mr_reader_ui_fit(void); /* 当前 fit 档(MR_FIT_*) */
|
int mr_reader_ui_fit(void); /* 当前 fit 档(MR_FIT_*) */
|
||||||
int mr_reader_ui_chrome(void); /* 1=顶栏可见 */
|
int mr_reader_ui_chrome(void); /* 1=顶栏可见 */
|
||||||
/* 直接切模式(绕过顶栏 tap 的命中时序;测试用,产品路径仍走顶栏动作)。 */
|
/* 直接切模式(绕过顶栏 tap 的命中时序;测试用,产品路径仍走顶栏动作)。 */
|
||||||
|
|||||||
Reference in new issue
Block a user