图库: 阅读器页槽改 RGB565 常驻,页图内存减半(M32)
- 页槽从 RGBA 改成「已落框的 RGB565 位图」(2B/px):真机 480×853 页 1599KB → 800KB, 两页常驻 3.2MB → 1.6MB。解码仍在 worker 里产出 RGBA,当场转 565 并释放 RGBA。 - SDK 新增 565 原语(meow_ui_draw.h / meow_ui_gfx.c):meow_gfx_blit_rgb565 命中页每行 一次 memcpy(单次拷贝、无每帧重采样),缩放兜底走 565 最近邻。量化与 back_buf 用同一个 rgb565(),565 屏上落下的像素与旧 RGBA 路径逐像素一致(不引入色偏/新增色带)。 - 预算按 565 重算:单页像素上限沿用旧 3MB@RGBA 的同几何口径(=1.5MB@565), MR_RESIDENT_CAP = 3 槽 + 1 张在途 = 3.2MB;reader_estimate_peak 新增 convert_bytes (落槽暂存 2B/px)与 peak_bytes 一起参与门控,避免实测超预算。 - 结果队列(已解码、未落槽)字节计入总预算(pending_bytes),不再「偷吃」最多 2 页。 - CBZ meta() 改读条目 4KB 前缀(mz_read_prefix;deflate 解到 4KB 即停,输入缓冲走栈), 500 页排版不再触发 500 次全量解压。 - 测试:B3/B8 的 565 常驻与在途预算断言、CBZ 500 页 stored/deflate 前后对比、 P12 565 与 RGBA 路径的逐像素颜色对比(量化误差/RMSE/灰阶梯)。
This commit is contained in:
1 parent
bbc73635a6
commit
7036020005
11 files changed
+910
-111
No files matched your search
@@ -4,7 +4,8 @@
|
||||
* - 页表在 init 时一次建好(mz_open 只读 central directory;只看名字,不解压);
|
||||
* - open(i) 才解压单条目:mz_read_alloc → page_cache 落盘 → 注入的解码回调按路径解码。
|
||||
* 因此翻页/条漫两套消费代码与目录册完全一致(meta 走原始像素尺寸,open 走解码);
|
||||
* - meta(i) 按需解压该页到内存只读图片头(不落缓存),结果缓存在 meta_w/meta_h;
|
||||
* - meta(i) 只读该条目的**前 4KB**(mz_read_prefix)取图片头(不落缓存),结果缓存在
|
||||
* meta_w/meta_h;M32 前是整条目解压,500 页排版 = 500 次全量解压;
|
||||
* - 单个 mr_zip_book 的 mz_read* 由自带的互斥量串行化(解码线程与主线程 meta 会碰面)。
|
||||
*
|
||||
* 线程:count/page_name 只读页表,可任意线程;open 可由解码线程调用;meta 按 M6
|
||||
@@ -269,7 +270,38 @@ static int zip_ops_count(void *ctx)
|
||||
return zb ? zb->count : 0;
|
||||
}
|
||||
|
||||
/* meta 按需解压头探测;结果缓存,每页只解压一次。主线程调用(M6 用法)。 */
|
||||
/* meta 只读条目**前缀**(≤4KB)取图片头(M32):改前是 mz_read_alloc 整条目解压后再探
|
||||
* (500 页排版 = 500 次全量解压)。PNG IHDR 33 字节、JPEG SOF 在头几百字节、BMP 在
|
||||
* 54 字节 + 调色板内,4KB 足够;前缀探测失败且确实被截断时才回退整条目读(罕见/病态文件)。 */
|
||||
#define MR_ZIP_META_PREFIX 4096
|
||||
|
||||
static int zip_meta_probe_prefix(mr_zip_book *zb, const mz_entry *e, int *w, int *h)
|
||||
{
|
||||
unsigned char head[MR_ZIP_META_PREFIX];
|
||||
size_t head_len = 0;
|
||||
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
|
||||
int rc = mz_read_prefix((mz_archive *)zb->arch, e, head, sizeof head, &head_len);
|
||||
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
|
||||
if (rc != MZ_OK) return 0;
|
||||
if (mr_probe_size_mem(head, head_len, w, h)) return 1;
|
||||
if (head_len < sizeof head) return 0; /* 整个条目都读到了还探不出 → 坏头 */
|
||||
|
||||
/* 前缀被截断且探不出尺寸(病态长的头部):回退整条目读,保持旧语义。 */
|
||||
void *buf = NULL;
|
||||
size_t len = 0;
|
||||
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
|
||||
rc = mz_read_alloc((mz_archive *)zb->arch, e, &buf, &len);
|
||||
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
|
||||
if (rc != MZ_OK || !buf) {
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
int known = mr_probe_size_mem(buf, len, w, h);
|
||||
free(buf);
|
||||
return known;
|
||||
}
|
||||
|
||||
/* meta 按需探测图片头;结果缓存,每页只读一次(前缀 4KB,不再整页解压)。主线程调用(M6 用法)。 */
|
||||
static int zip_ops_meta(void *ctx, int index, int *w, int *h)
|
||||
{
|
||||
mr_zip_book *zb = (mr_zip_book *)ctx;
|
||||
@@ -297,19 +329,8 @@ static int zip_ops_meta(void *ctx, int index, int *w, int *h)
|
||||
const mz_entry *e = page_entry(zb, index);
|
||||
if (!e) return 0;
|
||||
|
||||
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
|
||||
void *buf = NULL;
|
||||
size_t len = 0;
|
||||
int rc = mz_read_alloc((mz_archive *)zb->arch, e, &buf, &len);
|
||||
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
|
||||
if (rc != MZ_OK || !buf) {
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
int pw = 0, ph = 0;
|
||||
int known = mr_probe_size_mem(buf, len, &pw, &ph);
|
||||
free(buf);
|
||||
if (!known) return 0;
|
||||
if (!zip_meta_probe_prefix(zb, e, &pw, &ph)) return 0;
|
||||
|
||||
zb->meta_w[index] = pw;
|
||||
zb->meta_h[index] = ph;
|
||||
|
||||
@@ -425,6 +425,10 @@ int mr_probe_size(const char *path, int *w, int *h)
|
||||
* 与源图分辨率/压缩体积无关;命中即放行(不做内存门控)。
|
||||
* stb(回退):M22 的真实分配模型(planes + 抽取输出 + 精修/落框临时图)。
|
||||
* 只有这类页参与 3MB 峰值预算与 MemAvailable < 6MB 门控。
|
||||
* M32(页槽改 RGB565):解码仍产出 RGBA 的 meow_img,阅读器把它转成 565 落槽再释放 RGBA。
|
||||
* 这笔「落槽暂存」= 2B/px(转换时 RGBA 与 565 同时存活)单独记在 convert_bytes,与
|
||||
* peak_bytes 相加才是「一次取页」的真实内存;stb 门控与阅读器日志都按两者之和算。
|
||||
* 模型本身(RGBA 解码分配)不变——变的只是每页常驻从 4B/px 降到 2B/px。
|
||||
* ============================================================ */
|
||||
#define MR_PEAK_CAP_BYTES (3LL * 1024 * 1024) /* 常态单次解码峰值上限 */
|
||||
#define MR_PEAK_HARD_BYTES (12LL * 1024 * 1024) /* MemFree 富余时的绝对上限 */
|
||||
@@ -647,6 +651,7 @@ void reader_estimate_peak(const mr_img_info *in, int step, int box_w, int box_h,
|
||||
out->step = 1;
|
||||
out->tw = out->th = 0;
|
||||
out->peak_bytes = 0;
|
||||
out->convert_bytes = 0;
|
||||
out->budget_bytes = reader_peak_budget_bytes(memfree_kb);
|
||||
if (!in || in->w <= 0 || in->h <= 0 || box_w <= 0 || box_h <= 0) return;
|
||||
|
||||
@@ -665,9 +670,10 @@ void reader_estimate_peak(const mr_img_info *in, int step, int box_w, int box_h,
|
||||
out->path = MR_DECODE_PATH_BACKEND;
|
||||
out->peak_bytes = mr_backend_peak_bytes(in, eff, step, box_w, box_h,
|
||||
&out->tw, &out->th);
|
||||
out->convert_bytes = (long long)out->tw * out->th * 2; /* 565 落槽暂存(M32) */
|
||||
if (memavail_kb >= 0 && memavail_kb < MR_MEM_FLOOR_KB)
|
||||
out->skip = MR_DECODE_SKIP_LOWMEM;
|
||||
else if (out->peak_bytes > MR_PEAK_HARD_BYTES)
|
||||
else if (out->peak_bytes + out->convert_bytes > MR_PEAK_HARD_BYTES)
|
||||
out->skip = MR_DECODE_SKIP_PEAK;
|
||||
else
|
||||
out->skip = MR_DECODE_OK;
|
||||
@@ -678,10 +684,11 @@ void reader_estimate_peak(const mr_img_info *in, int step, int box_w, int box_h,
|
||||
if (longside <= dim) { out->tw = in->w; out->th = in->h; }
|
||||
else mr_target_dims(in->w, in->h, dim, &out->tw, &out->th);
|
||||
out->peak_bytes = mr_stb_peak_bytes(in, step, box_w, box_h);
|
||||
out->convert_bytes = (long long)out->tw * out->th * 2; /* 565 落槽暂存(M32) */
|
||||
if (memavail_kb >= 0 && memavail_kb < MR_MEM_FLOOR_KB)
|
||||
out->skip = MR_DECODE_SKIP_LOWMEM;
|
||||
else if (out->peak_bytes > out->budget_bytes)
|
||||
out->skip = MR_DECODE_SKIP_PEAK;
|
||||
else if (out->peak_bytes + out->convert_bytes > out->budget_bytes)
|
||||
out->skip = MR_DECODE_SKIP_PEAK; /* 565 暂存也占内存:门控按「解码+落槽」总量算 */
|
||||
else
|
||||
out->skip = MR_DECODE_OK;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,13 @@ typedef struct mr_decode_decision {
|
||||
int tw, th; /* 预估最终产出尺寸(含阅读器落框精修) */
|
||||
long long peak_bytes; /* 预估峰值(字节,含固定开销) */
|
||||
long long budget_bytes; /* 本次预算(字节;SKIP_PEAK 的比较基准) */
|
||||
/* ---- 追加(M32):页槽改 RGB565 后的「落槽暂存」----
|
||||
* 解码仍产出 RGBA 的 meow_img;阅读器把它一次性转成 565 再释放 RGBA,转换期间
|
||||
* RGBA(4B/px)+ 目标 565(2B/px)同时存在。这笔暂存(= 2×tw×th)与解码峰值不同时
|
||||
* 发生(解码中间面此时已释放),但同属「一次取页操作」的内存,必须一起进预算:
|
||||
* page_source 的门控按 peak_bytes + convert_bytes 比较,阅读器也按两者之和做日志/断言
|
||||
* (与 peak_bytes 分开,保持解码模型的原有口径)。 */
|
||||
long long convert_bytes;
|
||||
} mr_decode_decision;
|
||||
|
||||
void reader_estimate_peak(const mr_img_info *in, int step, int box_w, int box_h,
|
||||
@@ -185,7 +192,7 @@ typedef struct mr_zip_book {
|
||||
void *arch; /* mz_archive*(zip 层句柄;不对外暴露) */
|
||||
void *lock; /* pthread_mutex_t*:mz_read* 串行化(open/meta 共用) */
|
||||
int *order; /* 页 i → zip 条目下标(堆分配) */
|
||||
int *meta_w; /* 页原始宽/高(按需解压头探测;NULL=未探测) */
|
||||
int *meta_w; /* 页原始宽/高(按需读条目 4KB 前缀探测;NULL=未探测) */
|
||||
int *meta_h;
|
||||
int count;
|
||||
int maxdim;
|
||||
|
||||
+62
-6
@@ -18,6 +18,7 @@
|
||||
#define MZ_EOCD_MIN 22u
|
||||
#define MZ_COMMENT_MAX 65535u
|
||||
#define MZ_IN_CHUNK 65536u
|
||||
#define MZ_PREFIX_IN_CHUNK 4096u /* prefix 读的栈缓冲(M32,见 inflate_entry) */
|
||||
#define MZ_CRC_CHUNK (1u << 24)
|
||||
|
||||
struct mz_archive {
|
||||
@@ -322,15 +323,21 @@ static uint32_t crc_buf(const uint8_t *p, size_t n)
|
||||
}
|
||||
|
||||
/* 从 data_off 流式 inflate comp_size 字节到 buf(容量 buf_cap = 声明解压尺寸)。
|
||||
* 输出多于声明尺寸或流提前结束都算 FORMAT/TRUNCATED,不整块读取压缩数据。 */
|
||||
* 输出多于声明尺寸或流提前结束都算 FORMAT/TRUNCATED,不整块读取压缩数据。
|
||||
* prefix=1(M32):装满 buf_cap 就停——只取前缀,不把整条目解压完;此时不判 CRC
|
||||
* (调用方没读完整条目,见 mz_read_prefix)。prefix=0 的语义与旧实现逐字一致。 */
|
||||
static int inflate_entry(FILE *fp, uint64_t data_off, uint64_t comp_size,
|
||||
uint8_t *buf, size_t buf_cap, size_t *out_len)
|
||||
uint8_t *buf, size_t buf_cap, size_t *out_len, int prefix)
|
||||
{
|
||||
z_stream z;
|
||||
memset(&z, 0, sizeof z);
|
||||
if (inflateInit2(&z, -MAX_WBITS) != Z_OK) return MZ_ERR_MEM;
|
||||
|
||||
uint8_t *in = malloc(MZ_IN_CHUNK);
|
||||
/* prefix 模式最多只要 buf_cap 字节(4KB 文件头),输入侧用 4KB 栈缓冲:
|
||||
* 不为一次前缀读分配 64KB(500 页排版 = 500 次 64KB 分配/释放)。 */
|
||||
uint8_t in_stack[MZ_PREFIX_IN_CHUNK];
|
||||
size_t in_chunk = prefix ? sizeof in_stack : MZ_IN_CHUNK;
|
||||
uint8_t *in = prefix ? in_stack : (uint8_t *)malloc(MZ_IN_CHUNK);
|
||||
if (!in) {
|
||||
inflateEnd(&z);
|
||||
return MZ_ERR_MEM;
|
||||
@@ -343,13 +350,14 @@ static int inflate_entry(FILE *fp, uint64_t data_off, uint64_t comp_size,
|
||||
int stalls = 0;
|
||||
|
||||
while (rc == MZ_OK) {
|
||||
if (prefix && produced == buf_cap) break; /* 前缀够了:不再解压 */
|
||||
if (z.avail_in == 0) {
|
||||
if (consumed >= comp_size) {
|
||||
rc = MZ_ERR_TRUNCATED;
|
||||
break;
|
||||
}
|
||||
uint64_t remain = comp_size - consumed;
|
||||
size_t want = remain > MZ_IN_CHUNK ? MZ_IN_CHUNK : (size_t)remain;
|
||||
size_t want = remain > in_chunk ? in_chunk : (size_t)remain;
|
||||
rc = read_exact(fp, in, want);
|
||||
if (rc != MZ_OK) break;
|
||||
z.next_in = in;
|
||||
@@ -399,7 +407,7 @@ static int inflate_entry(FILE *fp, uint64_t data_off, uint64_t comp_size,
|
||||
}
|
||||
|
||||
inflateEnd(&z);
|
||||
free(in);
|
||||
if (!prefix) free(in);
|
||||
if (rc == MZ_OK) *out_len = produced;
|
||||
return rc;
|
||||
}
|
||||
@@ -442,7 +450,7 @@ int mz_read(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size,
|
||||
if (crc_buf((const uint8_t *)buf, need) != e->crc) return MZ_ERR_CRC;
|
||||
} else {
|
||||
size_t made = 0;
|
||||
rc = inflate_entry(z->fp, data_off, e->comp_size, (uint8_t *)buf, need, &made);
|
||||
rc = inflate_entry(z->fp, data_off, e->comp_size, (uint8_t *)buf, need, &made, 0);
|
||||
if (rc != MZ_OK) return rc;
|
||||
if (made != need) return MZ_ERR_FORMAT;
|
||||
if (crc_buf((const uint8_t *)buf, made) != e->crc) return MZ_ERR_CRC;
|
||||
@@ -476,6 +484,54 @@ int mz_read_alloc(mz_archive *z, const mz_entry *e, void **out_buf,
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
int mz_read_prefix(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size,
|
||||
size_t *out_size)
|
||||
{
|
||||
if (out_size) *out_size = 0;
|
||||
if (!z || !z->fp || !e) return MZ_ERR_ARG;
|
||||
if (!buf && buf_size > 0) return MZ_ERR_ARG;
|
||||
if (buf_size == 0) return MZ_OK;
|
||||
if (e->flags & 1u) return MZ_ERR_UNSUPPORTED;
|
||||
if (e->method != 0 && e->method != 8) return MZ_ERR_UNSUPPORTED;
|
||||
if (e->uncomp_size > (uint64_t)SIZE_MAX) return MZ_ERR_UNSUPPORTED;
|
||||
|
||||
/* 与 mz_read 同一套局部头/数据范围校验(结构非法一律拒绝)。 */
|
||||
uint8_t lh[MZ_LOCAL_MIN];
|
||||
int rc = read_at(z->fp, e->local_offset, lh, sizeof lh);
|
||||
if (rc != MZ_OK) return rc;
|
||||
if (rd32(lh) != MZ_SIG_LOCAL) return MZ_ERR_FORMAT;
|
||||
if (rd16(lh + 8) != e->method) return MZ_ERR_FORMAT;
|
||||
|
||||
uint64_t data_off = 0;
|
||||
uint64_t data_end = 0;
|
||||
if (!u64_add(e->local_offset, MZ_LOCAL_MIN, &data_off) ||
|
||||
!u64_add(data_off, rd16(lh + 26), &data_off) ||
|
||||
!u64_add(data_off, rd16(lh + 28), &data_off) ||
|
||||
!u64_add(data_off, e->comp_size, &data_end) ||
|
||||
data_end > z->file_size) {
|
||||
return MZ_ERR_TRUNCATED;
|
||||
}
|
||||
|
||||
size_t want = buf_size;
|
||||
if (e->uncomp_size < (uint64_t)want) want = (size_t)e->uncomp_size;
|
||||
|
||||
if (e->method == 0) {
|
||||
if (e->comp_size != e->uncomp_size) return MZ_ERR_FORMAT;
|
||||
if (want > 0) {
|
||||
rc = read_at(z->fp, data_off, buf, want);
|
||||
if (rc != MZ_OK) return rc;
|
||||
}
|
||||
} else {
|
||||
size_t made = 0;
|
||||
rc = inflate_entry(z->fp, data_off, e->comp_size, (uint8_t *)buf, want, &made, 1);
|
||||
if (rc != MZ_OK) return rc;
|
||||
if (made != want) return MZ_ERR_FORMAT; /* 声明尺寸内提前结束 = 坏流 */
|
||||
}
|
||||
|
||||
if (out_size) *out_size = want;
|
||||
return MZ_OK;
|
||||
}
|
||||
|
||||
const char *mz_strerror(int err)
|
||||
{
|
||||
switch (err) {
|
||||
|
||||
@@ -81,6 +81,15 @@ int mz_read(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size, size_t
|
||||
* 成功时 *out_buf 由调用方 free。 */
|
||||
int mz_read_alloc(mz_archive *z, const mz_entry *e, void **out_buf, size_t *out_size);
|
||||
|
||||
/* 只解压条目**前缀**到 buf(buf_size = 容量):stored 直接读前 buf_size 字节,
|
||||
* deflate 在 inflate 出 buf_size 字节后即停(不整条目解压)。
|
||||
* *out_size = 实际写入字节数(= min(buf_size, uncomp_size))。
|
||||
* 用途:CBZ 排版只看图片头取宽高(PNG IHDR 33 字节 / JPEG SOF / BMP 头都在前 4KB)。
|
||||
* 注意:没有读完整条目,因此**不校验整段 CRC**(只校验结构/长度),不要用它取内容。
|
||||
* 出错语义与 mz_read 相同(越界/坏流/加密/zip64 一律返回错误码)。 */
|
||||
int mz_read_prefix(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size,
|
||||
size_t *out_size);
|
||||
|
||||
const char *mz_strerror(int err);
|
||||
|
||||
#endif /* TUKU_LOGIC_ZIP_ZIP_H */
|
||||
Reference in new issue
Block a user