From eecc7fa16808bcc3baa37b02edcafa0b341c5c01 Mon Sep 17 00:00:00 2001 From: seedds Date: Thu, 15 Oct 2026 00:40:49 +0800 Subject: [PATCH] NVN: Downsample as a Metal resolve, snapshot fallback - nvnCommandBufferDownsample / DownsampleTextureView are recorded as a texture copy with a host flag (DM_COPY_RESOLVE) and replayed as a Metal multisample resolve (depth takes sample 0); equal sample counts become a plain copy. They used to be skipped. - A texture object that is invalid when recorded (Unity finalizes and re-initializes the same render target between recording and submitting) keeps the old behaviour: the record holds the object pointer and replay reads it at submit. Snapshots made render targets disappear in that case. --- dm/gfx/README.md | 2 +- dm/gfx/README.zh-CN.md | 2 +- dm/gfx/nvn/hle_nvn.c | 21 ++++++++++++++++++--- dm/gfx/nvn/nvn_mtl.m | 21 +++++++++++++++++++++ dm/gfx/nvn/nvn_obj.h | 3 +++ 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/dm/gfx/README.md b/dm/gfx/README.md index 26c3bd2..d0e294b 100644 --- a/dm/gfx/README.md +++ b/dm/gfx/README.md @@ -183,4 +183,4 @@ The following paths are not implemented and report red in the log when reached ( - indexed quad primitives (8-bit indices are widened to 16-bit at replay); - some forms of texture-to-texture copy and texture clear. -Functions listed in `g_nops`, such as `MultiDraw*IndirectCount`, `DispatchComputeIndirect`, `SetDepthBounds` and `DownsampleTextureView`, are empty implementations: calling them neither reports red nor has any effect. +Functions listed in `g_nops`, such as `MultiDraw*IndirectCount`, `DispatchComputeIndirect` and `SetDepthBounds`, are empty implementations: calling them neither reports red nor has any effect. `Downsample` / `DownsampleTextureView` are recorded as texture copies with a host flag (`DM_COPY_RESOLVE`) and replayed as a Metal multisample resolve. diff --git a/dm/gfx/README.zh-CN.md b/dm/gfx/README.zh-CN.md index bc5440d..ebf4411 100644 --- a/dm/gfx/README.zh-CN.md +++ b/dm/gfx/README.zh-CN.md @@ -183,4 +183,4 @@ make -C dm/gfx/metal run # 离屏示例自测,见 metal/README.zh-CN.md - 带索引的四边形图元(8 位索引在回放时扩成 16 位); - 部分形态的贴图到贴图拷贝与清贴图。 -`MultiDraw*IndirectCount`、`DispatchComputeIndirect`、`SetDepthBounds`、`DownsampleTextureView` 等列在 `g_nops` 里的函数是空实现,调用不报红也不生效。 +`MultiDraw*IndirectCount`、`DispatchComputeIndirect`、`SetDepthBounds` 等列在 `g_nops` 里的函数是空实现,调用不报红也不生效。`Downsample` / `DownsampleTextureView` 按贴图拷贝录(带宿主标志 `DM_COPY_RESOLVE`),回放用 Metal 的多重采样解析。 diff --git a/dm/gfx/nvn/hle_nvn.c b/dm/gfx/nvn/hle_nvn.c index d8b41a0..aba5a4f 100644 --- a/dm/gfx/nvn/hle_nvn.c +++ b/dm/gfx/nvn/hle_nvn.c @@ -663,14 +663,17 @@ static const tex_t *tex_snap(void *rec_, size_t off, int k, const NVNtexture *t, if (!t) return NULL; tex_t *s = (tex_t *)((uint8_t *)rec_ + off) + k; memcpy(s, t, sizeof *s); + // 录制时对象无效(Unity 会在录制与提交之间 Finalize 再 Initialize 同一个渲染目标对象):照旧记对象指针、提交时再读,那时多半又有效了;回放仍按 magic 拦 + // The object is invalid at record time (Unity finalizes and re-initializes the same render target object between recording and submitting): keep the object pointer as before and read it at submit, when it is usually valid again; replay still checks the magic if (s->magic != MAGIC('T', 'E', 'X', 0)) { static int red; if (red++ < 4) { // 前 4 例记调用点与对象开头,判断游戏传的是什么 | the first 4 log the call site and the start of the object, to tell what the game passed module_t *m = module_of((void *)ra); const uint64_t *q = (const uint64_t *)t; - LOG("【红】录命令时贴图对象已失效(未初始化或已 Finalize):%p 第 %d 个,调用点 %s+%#llx,对象开头 %llx %llx %llx %llx %llx %llx;回放跳过它", (const void *)t, k, + LOG("【红】录命令时贴图对象无效(未初始化或已 Finalize):%p 第 %d 个,调用点 %s+%#llx,对象开头 %llx %llx %llx %llx %llx %llx;提交时再读", (const void *)t, k, m ? m->name : "?", m ? (unsigned long long)(ra - (uint64_t)m->base) : (unsigned long long)ra, (unsigned long long)q[0], (unsigned long long)q[1], (unsigned long long)q[2], (unsigned long long)q[3], (unsigned long long)q[4], (unsigned long long)q[5]); } + return (const tex_t *)t; } return s; } @@ -912,7 +915,6 @@ static const char *const g_nops[] = { "nvnCommandBufferMultiDrawElementsIndirectCount", "nvnCommandBufferDispatchComputeIndirect", "nvnCommandBufferSetDepthBounds", - "nvnCommandBufferDownsampleTextureView", "nvnCommandBufferWaitSync", "nvnCommandBufferSetShaderScratchMemory", "nvnCommandBufferPushDebugGroupDynamic", @@ -1550,7 +1552,20 @@ NVN(nvnCommandBufferSetTiledCacheAction, void, NVNcommandBuffer *c, NVNtiledCach NVN(nvnCommandBufferSetTiledCacheTileSize, void, NVNcommandBuffer *c, int w, int h) { (void)c; (void)w; (void)h; } NVN_NOPROTO(nvnCommandBufferSetColorReductionEnable, void, NVNcommandBuffer *c, uint32_t en) { (void)c; (void)en; } // 颜色压缩提示 | color compression hints NVN_NOPROTO(nvnCommandBufferSetColorReductionThresholds, void, NVNcommandBuffer *c, int cls, const int *t) { (void)c; (void)cls; (void)t; } -NVN(nvnCommandBufferDownsample, void, NVNcommandBuffer *c, const NVNtexture *s, const NVNtexture *d) { (void)c; (void)s; (void)d; ONCE_LOG("【红】Downsample:宿主未实现(跳过)"); } +// 多重采样贴图解析到单采样贴图(整张):按贴图拷贝录、带 DM_COPY_RESOLVE,回放用 Metal 的多重采样解析(nvn_mtl.m copy_t2t) +// Resolve a multisample texture into a single-sample one (whole texture): recorded as a texture copy with DM_COPY_RESOLVE; replay uses Metal's multisample resolve (copy_t2t in nvn_mtl.m) +static void rec_downsample(NVNcommandBuffer *c, const NVNtexture *s, const NVNtextureView *sv, const NVNtexture *d, const NVNtextureView *dv, uint64_t ra) { + p_copy_t2t *o = REC_SNAP(c, OP_COPY_T2T, p_copy_t2t, 2); + o->src = tex_snap(o, SNAP_OFF(p_copy_t2t), 0, s, ra); o->dst = tex_snap(o, SNAP_OFF(p_copy_t2t), 1, d, ra); + copy_view(&o->sv, &o->has_sv, sv); copy_view(&o->dv, &o->has_dv, dv); + if (o->src) o->sr = (NVNcopyRegion){0, 0, 0, (int)o->src->d.width, (int)o->src->d.height, 1}; + if (o->dst) o->dr = (NVNcopyRegion){0, 0, 0, (int)o->dst->d.width, (int)o->dst->d.height, 1}; + o->flags = DM_COPY_RESOLVE; +} +NVN(nvnCommandBufferDownsample, void, NVNcommandBuffer *c, const NVNtexture *s, const NVNtexture *d) { rec_downsample(c, s, NULL, d, NULL, (uint64_t)__builtin_return_address(0)); } +NVN(nvnCommandBufferDownsampleTextureView, void, NVNcommandBuffer *c, const NVNtexture *s, const NVNtextureView *sv, const NVNtexture *d, const NVNtextureView *dv) { + rec_downsample(c, s, sv, d, dv, (uint64_t)__builtin_return_address(0)); +} // CPU 写贴图(NVN WriteTexels):贴图存储在池内存里、按 Tegra 块线性排布;整级解出 → 改区域 → 排回。写前解写保护并标脏,回放层下次用到时重传 // CPU texture write (NVN WriteTexels): texture storage lives in pool memory in Tegra block-linear layout; decode the whole level → patch the region → re-encode. diff --git a/dm/gfx/nvn/nvn_mtl.m b/dm/gfx/nvn/nvn_mtl.m index 386d409..d6b2887 100644 --- a/dm/gfx/nvn/nvn_mtl.m +++ b/dm/gfx/nvn/nvn_mtl.m @@ -5070,6 +5070,27 @@ static void copy_t2t(const p_copy_t2t *c) { if (!s || !d) return; unsigned sl = c->has_sv && c->sv.set_levels ? c->sv.base_level : 0, dl = c->has_dv && c->dv.set_levels ? c->dv.base_level : 0; unsigned sz0 = c->has_sv && c->sv.set_layers ? c->sv.min_layer : 0, dz0 = c->has_dv && c->dv.set_layers ? c->dv.min_layer : 0; + if (c->flags & DM_COPY_RESOLVE) { // Downsample:多重采样解析进单采样目标(深度取 0 号样本);两边采样数相同时就是普通整张拷贝 | Downsample: multisample resolve into the single-sample destination (depth takes sample 0); with equal sample counts it is a plain whole copy + if (s.sampleCount == d.sampleCount && s.pixelFormat == d.pixelFormat && s.width == d.width && s.height == d.height) { + enc_b_copy_texture(blit(), s, sz0, sl, MTLOriginMake(0, 0, 0), MTLSizeMake(s.width, s.height, 1), d, dz0, dl, MTLOriginMake(0, 0, 0)); + return; + } + if (s.sampleCount <= 1 || d.sampleCount != 1 || s.pixelFormat != d.pixelFormat || s.width != d.width || s.height != d.height) { + RED_ONCE(72, "Downsample 未实现的形态:%lux%lu %lu 样本 格式 %lu → %lux%lu %lu 样本 格式 %lu", (unsigned long)s.width, (unsigned long)s.height, (unsigned long)s.sampleCount, + (unsigned long)s.pixelFormat, (unsigned long)d.width, (unsigned long)d.height, (unsigned long)d.sampleCount, (unsigned long)d.pixelFormat); + return; + } + rp_end(); if (g_blit) { enc_b_end(g_blit); g_blit = nil; } + MTL4RenderPassDescriptor *rp = [MTL4RenderPassDescriptor new]; + MTLPixelFormat pf = s.pixelFormat; + int depth = pf == MTLPixelFormatDepth32Float || pf == MTLPixelFormatDepth16Unorm || pf == MTLPixelFormatDepth32Float_Stencil8; + MTLRenderPassAttachmentDescriptor *a = depth ? rp.depthAttachment : rp.colorAttachments[0]; + a.texture = s; a.level = sl; a.slice = sz0; a.resolveTexture = d; a.resolveLevel = dl; a.resolveSlice = dz0; + a.loadAction = MTLLoadActionLoad; a.storeAction = MTLStoreActionStoreAndMultisampleResolve; + if (depth) rp.depthAttachment.depthResolveFilter = MTLMultisampleDepthResolveFilterSample0; + ser_gate(); enc_r_end(enc_r_begin(rp)); + return; + } PD("t src=%llu lv%u sl%u xyz=%d,%d,%d dst=%llu lv%u sl%u xyz=%d,%d,%d size=%d,%d,%d", c->src->serial, sl, sz0, c->sr.xoffset, c->sr.yoffset, c->sr.zoffset, c->dst->serial, dl, dz0, c->dr.xoffset, c->dr.yoffset, c->dr.zoffset, c->sr.width, c->sr.height, c->sr.depth); int s3 = c->src->d.target == 2, d3 = c->dst->d.target == 2, nz = c->sr.depth ? c->sr.depth : 1; diff --git a/dm/gfx/nvn/nvn_obj.h b/dm/gfx/nvn/nvn_obj.h index c125a59..32c74a9 100644 --- a/dm/gfx/nvn/nvn_obj.h +++ b/dm/gfx/nvn/nvn_obj.h @@ -137,6 +137,9 @@ typedef struct { int32_t prim, itype, count, base_vertex, base_instance, instanc typedef struct { int32_t x, y, z; uint64_t caller; } p_dispatch; // caller:游戏调用点返回地址(定位派发者) | caller: return address of the game call site (to locate the dispatcher) typedef struct { const dpool_t *pool; } p_set_pool; typedef struct { uint64_t src; const tex_t *tex; view_t view; uint8_t has_view; NVNcopyRegion r; int32_t flags; } p_copy_b2t; +// flags 里宿主自用的一位:DM_COPY_RESOLVE = 这是 Downsample(多重采样解析),按贴图拷贝录,其余各处照拷贝对待(读源写目标) +// A host-only bit in flags: DM_COPY_RESOLVE = this is a Downsample (multisample resolve), recorded as a texture copy so everything else treats it as one (reads the source, writes the destination) +#define DM_COPY_RESOLVE 0x40000000 typedef struct { const tex_t *src, *dst; view_t sv, dv; uint8_t has_sv, has_dv; NVNcopyRegion sr, dr; int32_t flags; } p_copy_t2t; // T2B 复用:src 存目标缓冲地址 | Reused for T2B: src stores the destination buffer address typedef struct { const tex_t *tex; view_t view; uint8_t has_view, is_uint; NVNcopyRegion r; int32_t mask; union { float f[4]; uint32_t u[4]; } c; } p_clear_tex; typedef struct { int64_t stride; } p_copy_stride;