《忍者龙剑传 黑之章》(含飓风版中文汉化 vb-zh-x2.1)与《忍者龙剑传》(2004)的非官方 Windows 原生移植。2.2.31。
2.2.31:第一次进入场景、第一次播过场不再卡一下
玩家会注意到的变化
- 以前每次启动后,第一次进入一个区域、第一次播一段过场,画面都会顿一下(单帧 30–280 ms,取决于显卡和当时要准备的东西)。现在这些准备工作挪到了启动时完成。
- 作者用这个版本实际打了第 1–2 章(33 分钟):超过 50 ms 的帧只有两个。一个是当时种子里还没有的着色器,已收进这一版。另一个是从主菜单进入游戏时约 0.6 秒的停顿,这是游戏自己在清理硬盘缓存,主机上也一样(见下文)。
- 代价是每次启动多等一点:到游戏第 60 帧,RTX 3060 约 3.03 → 3.33 秒,Iris Xe 约 1.9 → 2.1 秒。游戏过程中的帧时间(中位数、p99)与 2.2.30 相同。
- 冷启动时直接用存档里的音乐、音效、语音音量。以前改过音量的玩家,每次在标题画面都会被问“要套用声音设置的更改吗?”,现在不会了。
卡顿是哪来的,怎么解决的
每个来源都单独查明、单独处理,并且都能用启动参数关掉对照。测试用 RTX 3060(独显)和 Intel Iris Xe(核显,D3D9On12),待机演示加性能记录器,每种设置交替各跑两次以上;涉及画面的改动也在高倍渲染(独显 ×8、核显 ×4)下核对过。
- 着色器第一次用到时现场编译(每个 2–40 ms)。发布包带上着色器种子
shader_seed.bin(429 条),启动时与玩家自己的缓存一起建好。新玩家第一次启动,核显上最差帧 282 → 113 ms。 - 游戏自己的顶点程序。它们是游戏代码的翻译,不能随包发,所以只发它们在
default.xbe里的位置vertex_programs.bin(7 个程序,在 7 个镜像版本里都核对过位置和哈希)。启动时从玩家自己的镜像读出来编译。待机演示第一个场景少了 17–23 ms 的编译。 - 显卡驱动在第一次画某种状态组合时现编内部程序(NVIDIA 的固定管线最明显:光源的类型和衰减项、矩阵里哪些元素是 0 和 1 都会让它重新编)。现在会记录游戏用过的绘制状态,启动时在一张 16×16 的小图上全部画一遍,种子
draw_states.bin有 8,339 种。RTX 3060 进区域 47–52 → 26 ms;×8 时 49–54 ms 的卡顿消失。实测 NVIDIA 驱动不把这类 D3D9 程序存到磁盘(清空 DXCache 后连跑 5 次仍是空的),所以每次启动都要画一遍,上面说的启动多等就是这一步。关掉:--no-draw-warm。 - 一批新贴图同时创建时驱动卡住(34–40 ms)。现在按尺寸和格式提前建好贴图池(24 MB),在等垂直同步的空闲里补充。RTX 3060 那几帧的贴图时间 32–36 → 1.3–3.5 ms。关掉:
--no-tex-pool。 - 核显(D3D9On12)第一个 3D 画面等新内存。现在绘制缓冲改为三组轮换、建设备时就写好;强制重建只重做变了的渲染目标。Iris Xe 最差帧 55–138 → 40–45 ms。旧做法:
--ring-discard。 - 移植层自己的像素着色器每次启动都现编(深度填充与读取、抗锯齿等)。现在走着色器缓存,也收进了种子。Iris Xe 进区域 43 → 29 ms。
- 过场第一帧游戏在 CPU 上算 10 张水波纹贴图(每张 2.5 ms,逐像素三个平方根、一个正弦、两个反正切)。结果只取决于参数、函数里的常数和 x87 控制字,所以按这些缓存在
cache\cpu_textures.bin,之后直接复制。和游戏自己算的逐字节一致(--cpu-tex-verify验证过 10/10)。过场那一帧 RTX 3060 38–56 → 9 ms,Iris Xe 35–36 → 24 ms 以下。关掉:--no-cpu-tex。 - 种子覆盖不到的新着色器:加了一个可选的编译预算
--compile-budget 毫秒,超出预算的着色器放到后台线程编译,期间这个绘制先不画。冷缓存下最差帧 237 → 98 ms,但会有约 4 帧缺东西,所以默认关闭,只作为选项。
种子覆盖范围
- 来源:标题画面、待机演示,以及作者游玩第 1–2 章时记录的数据(2026-11-15)。用那次游玩的记录核对,着色器、顶点程序、绘制状态三份种子都是 100% 覆盖。
- 发布包在核显上用空缓存跑 150 秒:没有一帧超过 50 ms;着色器种子覆盖本次用到的全部着色器。
- 第 3 章以后还没有专门记录。遇到种子里没有的东西只会第一次卡一下,之后玩家自己的缓存会记住。
冷启动音量
游戏启动时套用存档设置的函数(x2.1 的 FUN_001DB1C0),前三项(音乐、音效、语音)读的是固定的默认值 60,而不是存档里的值;重启游戏时用的同类函数读的是存档。移植版把这三处读取改成和重启时一样(每处 7 字节,原地替换),按特征码在 6 个黑之章镜像里都只找到唯一一处;2004 没有这个问题,不改。主机上也是这样,--no-saved-volumes 恢复主机原样。
查过但不是移植问题的:进入游戏时约 0.6 秒的停顿
从主菜单进入游戏时,游戏会删掉硬盘缓存里旧章节的过场数据(rtm_*.afs),每删一个就重写一次 instinfo.ins。用 --trace 记录每个内核调用后查明:游戏的 CRI 文件系统由一个线程每帧(60 Hz,实测平均 16.6 ms)唤醒一次推进一步,删一个文件要两步(33 ms),19 个文件约 0.62 秒;真正的磁盘操作只占 3 ms。主机同样是 60 Hz,节奏一样。试过让等待循环让出 CPU,结果不变(618 ms),已撤回,没有放进这一版。
参照 ngbrecomp 的部分
以下做法参照了 ngbrecomp(GPL-2.0,参照的是当时最新的 0.9.68),都按本项目自己的结构重新实现,没有复制代码:
- 着色器种子随包发布(它的
packaging/d3d-seed.bin与docs/renderer.md)。 - 游戏顶点程序只发在
default.xbe里的位置、启动时从玩家镜像读出(同样出自d3d-seed.bin的做法)。 - 冷启动套用存档音量(0.9.54 #182,
src/sound_boot.c)。 - 编译预算:超出预算的着色器后台编译、已缓存的总是当场建好(0.9.68 #94、#21)。本项目实测后作为默认关闭的选项;它的单线程做法在冷缓存时会让约 14 帧缺东西,这里改为最多 4 个线程。
- ngbrecomp 也发布绘制状态的预热表(
packaging/shader-seed/states.bin)。本项目的绘制状态回放是在 D3D9 驱动上逐项实测出来的(光源形状、矩阵 0/1 结构等),格式和做法不同。
新的测试开关
只给排查问题用,玩家不需要:
--hitch-detail:超过 50 ms 的帧在日志里写明时间花在哪(绘制调用、缓冲锁、贴图创建/解码/上传、着色器编译、各线程 CPU、GPU 时间)。--vsh-record 文件:记录用到的游戏顶点程序,用来做种子。--log-vsh:写出顶点程序的反汇编(以前默认写,拖慢过场第一帧,现在默认不写)。--compile-budget 毫秒、--no-draw-warm、--no-tex-pool、--no-cpu-tex、--cpu-tex-verify、--ring-discard、--no-saved-volumes:见上文。--ffp-vs/--ffp-vs-check N:用生成的顶点着色器代替 D3D9 固定管线(默认关;开了没有进一步减少卡顿,还多花约 2% CPU)。
测试
- 两张显卡、待机演示、性能记录器、交替对照:卡顿帧如上;帧时间中位数与 p99 不变。Iris Xe ×4 与上一版交替各两次:中位数 6.96/6.91 → 6.99/6.82 ms,p99 11.16/10.91 → 10.52/10.42 ms,超过 33 ms 的帧都是 0。
- 作者实际游玩第 1–2 章(RTX 3060),如上。
- 发布包:核显、空缓存启动正常;Windows Defender 扫描三个程序:无威胁。
源码
本版源码提交:044c258 … cb4c1ad(11 项改动)与 b43e481(版本号、README)。README 新增「流畅度」一节,详细记录每个卡顿来源、对应开关和实测数据,便于以后追溯。
发布包
ngb-hurricane-pc-2.2.31-win32.zip- 大小:3,281,839 字节
- SHA-256:
d9eba633a577fc613e7336e238e4b8984dfa93af4ed62fa82ebdf4192ecf11c1 - 符号包
ngb-hurricane-pc-2.2.31-win32-symbols.zip:27,163,642 字节 - SHA-256:
22ff82100a9d46bc39439b69181c9f9e41da992f74f3341c0d0b6ecb7c8ae6d8
作者:b站 哈雅布撒(QQ:2682557218)
English
An unofficial native Windows port of Ninja Gaiden Black (including the Hurricane Chinese translation vb-zh-x2.1) and Ninja Gaiden (2004). 2.2.31.
2.2.31: no more hitch the first time an area or a cutscene shows
What players will notice
- Before, after every start, the first time an area or a cutscene showed the picture stuttered once (one frame of 30-280 ms, depending on the graphics card and what had to be prepared). That preparation now happens at start.
- The author played chapters 1-2 on this version (33 minutes): only two frames took over 50 ms. One was a shader the seed did not have then; it is in this release. The other is a pause of about 0.6 s on entering play from the main menu: the game tidying its hard-disk cache, as on the console (see below).
- The cost is a slightly longer start: to the game's frame 60, RTX 3060 about 3.03 -> 3.33 s, Iris Xe about 1.9 -> 2.1 s. Frame times in play (median, p99) are as in 2.2.30.
- A cold start applies the saved music, effects and voice volumes. Players who had changed them were asked "Apply changes to the sound settings?" at the title every time; no longer.
Where the hitches came from and what was done
Each cause was found and handled on its own, and each can be switched off with a start option to compare. Tested on an RTX 3060 and on Intel Iris Xe integrated graphics (D3D9On12) with the attract demo and the performance recorder, every setting at least twice, interleaved; changes to the picture were also checked at high render scales (x8 on the RTX 3060, x4 on the Iris Xe).
- Shaders compiled the first time they are used (2-40 ms each). The package carries a shader seed,
shader_seed.bin(429 entries), made at start with the player's own cache. A new player's first start: worst frame 282 -> 113 ms on the Iris Xe. - The game's own vertex programs. They are translated game code and cannot ship, so the package carries only where they are in
default.xbe,vertex_programs.bin(7 programs; place and hash checked in all 7 builds). At start they are read from the player's own copy and compiled. The attract demo's first scene: 17-23 ms of compiling gone. - The graphics driver building an internal program at a draw state's first draw (NVIDIA's fixed-function pipeline above all: the lights' types and attenuation terms and which matrix elements are 0 or 1 all make it build again). The draw states the game uses are now recorded and all drawn once at start on a 16x16 target; the seed
draw_states.binholds 8,339. RTX 3060 area entry 47-52 -> 26 ms; at x8 the 49-54 ms hitch is gone. Measured: NVIDIA's driver keeps no such D3D9 programs on disk (DXCache emptied, still empty after five starts), so this is done at every start; it is the longer start above. Off:--no-draw-warm. - The driver stalling when a burst of new textures is made (34-40 ms). A texture pool by size and format (24 MB) is made ahead and refilled in the idle wait for vblank. RTX 3060 texture time in those frames 32-36 -> 1.3-3.5 ms. Off:
--no-tex-pool. - D3D9On12 waiting for new memory at the first 3D scene. The draw ring buffers are now three sets used in turn and written at device creation; a forced rebuild remakes only the render targets that changed. Iris Xe worst frame 55-138 -> 40-45 ms. Old way:
--ring-discard. - The port's own pixel shaders compiled at every start (depth, anti-aliasing and others). Now through the shader cache and in the seed. Iris Xe area entry 43 -> 29 ms.
- A cutscene's first frame computing 10 ripple textures on the CPU (2.5 ms each: per texel three square roots, a sine and two arctangents). The result depends only on the arguments, the function's constants and the x87 control word, so it is kept by those in
cache\cpu_textures.binand copied afterwards; byte for byte the game's own (--cpu-tex-verify: 10 of 10). That frame: RTX 3060 38-56 -> 9 ms, Iris Xe 35-36 -> under 24 ms. Off:--no-cpu-tex. - New shaders the seed lacks: an optional compile budget,
--compile-budget MS; shaders past it compile on background threads and their draws wait. On a cold cache the worst frame went 237 -> 98 ms, but about 4 frames miss pieces, so it is off by default, an option only.
What the seeds cover
- Sources: the title, the attract demo, and a recorded play of chapters 1-2 by the author (2026-11-15). Checked against that play's records, all three seeds (shaders, vertex programs, draw states) cover 100%.
- The package on the Iris Xe with an empty cache for 150 s: no frame over 50 ms; the shader seed covered every shader used.
- Chapter 3 on has not been recorded yet. Anything the seed lacks costs one hitch the first time, then the player's own cache keeps it.
Cold-start volumes
The function that applies the saved settings at start (x2.1 FUN_001DB1C0) reads its first three, music, effects and voices, from fixed defaults (60) instead of the save; the same kind of function used on a relaunch reads the save. The port makes those three loads the relaunch's (7 bytes each, in place), found by signature exactly once in each of the 6 Black builds; 2004 does not have the problem and is left alone. The console behaves the old way; --no-saved-volumes keeps it.
Looked into, not the port: the 0.6 s pause entering play
Entering play from the main menu, the game deletes earlier chapters' cutscene packs from its hard-disk cache (rtm_*.afs), rewriting instinfo.ins after each. Logging every kernel call with --trace showed that the game's CRI file system is advanced by a thread woken once a frame (60 Hz, measured 16.6 ms on average), and deleting a file takes two steps (33 ms): 19 files, about 0.62 s, of which the disk work is 3 ms. The console runs at 60 Hz too, at the same pace. Making the wait loops yield the CPU changed nothing (618 ms); it was dropped and is not in this release.
What follows ngbrecomp
These approaches follow ngbrecomp (GPL-2.0; its latest version at the time, 0.9.68), each reimplemented in this project's own structure, no code copied:
- A shader seed in the package (its
packaging/d3d-seed.binanddocs/renderer.md). - The game's vertex programs shipped only as where they are in
default.xbe, read from the player's copy at start (the same idea asd3d-seed.bin). - The saved volumes at a cold start (0.9.54 #182,
src/sound_boot.c). - The compile budget: shaders past it compile in the background, cached ones are always made at once (0.9.68 #94, #21). Measured here and made an option that is off by default; its one thread left about 14 frames missing pieces on a cold cache, so here up to 4 threads.
- ngbrecomp also ships a draw-state warm-up table (
packaging/shader-seed/states.bin). This project's draw-state replay was worked out by measuring the D3D9 drivers (lights' shapes, the matrices' 0/1 structure, ...), with its own format and method.
New test switches
For tracking problems down; players need not use them:
--hitch-detail: a frame over 50 ms gets log lines saying where its time went (draw calls, buffer locks, texture create / decode / upload, shader compiles, each thread's CPU, GPU time).--vsh-record file: records the game's vertex programs used, for making the seed.--log-vsh: writes the vertex programs' disassembly (written by default before, which slowed a cutscene's first frame; now only on request).--compile-budget MS,--no-draw-warm,--no-tex-pool,--no-cpu-tex,--cpu-tex-verify,--ring-discard,--no-saved-volumes: see above.--ffp-vs/--ffp-vs-check N: generated vertex shaders instead of D3D9's fixed-function pipeline (off; on, it removed no further hitch and cost about 2% CPU).
Tests
- Both graphics cards, attract demo, performance recorder, interleaved: hitch frames as above; median and p99 unchanged. Iris Xe x4 against the previous version, twice each: median 6.96/6.91 -> 6.99/6.82 ms, p99 11.16/10.91 -> 10.52/10.42 ms, no frame over 33 ms either way.
- The author's play of chapters 1-2 (RTX 3060), as above.
- The package: starts normally on the Iris Xe with an empty cache; Windows Defender scan of the three programs: no threats.
Source
This release's commits: 044c258 ... cb4c1ad (11 changes) and b43e481 (version, README). The README has a new Smoothness section recording each cause of the hitches, its switch and the measurements, for tracing later.
Package
ngb-hurricane-pc-2.2.31-win32.zip- Size: 3,281,839 bytes
- SHA-256:
d9eba633a577fc613e7336e238e4b8984dfa93af4ed62fa82ebdf4192ecf11c1 - Symbols
ngb-hurricane-pc-2.2.31-win32-symbols.zip: 27,163,642 bytes - SHA-256:
22ff82100a9d46bc39439b69181c9f9e41da992f74f3341c0d0b6ecb7c8ae6d8
Author: 哈雅布撒 on Bilibili (QQ: 2682557218)