English only: no Chinese left in code, scripts, logs, tables or docs

- Log messages, script output, test labels, Makefile errors and comments are English. Red log lines start with "[RED] " (was 【红】).
- Shader map stage column: vertex / pixel / compute / geometry / hull / domain (generators, dmsh_build / dmsh_facts and nvn_mtl.m stage_of follow).
- The NSP script's regexes match the new shader_split.py / dmsh_build.py summary lines; patch_nso cut scopes are all / host-only.
- Host UI text table (dm_i18n_tab.h) is English only; dm_lang_zh() removed.
- Text that matches game data (t_swkbd.c, t_textbox.c) is written as \x escapes.
- Shader chain: the chain files lose their bilingual comments, so every profile has a new chain version. dmsh_xlate.dll rebuilt from the translated Program.cs (SPIR-V byte-identical on 4,500 shaders of Wonder, TOTK and Age of Calamity), lock and dmsh_chain.h headers regenerated. c1_full.tsv restamped for Dread, 13 Sentinels and Wonder; the other profiles follow when their rebuild finishes.
- AGENTS.md and docs/agents: the language rule now covers logs; quoted log lines updated.
This commit is contained in:
seedds committed 2026-10-19 15:34:57 +08:00
1 parent c162b95a93
commit c5955d42a6
256 files changed
+170176 -170329

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@
- To analyse or port **another** Switch title, first run `scripts/build_ipa_from_nsp.py` on its NSP folder. With no matching profile, it writes a survey to `<NSP folder>/<title ID>_survey/survey.md`, then a provisional profile guessed from it (`<NSP folder>/<title ID>_build/profile/<title ID>/`, found by the tools through `DM_GAMES_DIR`) and a best-effort IPA that may not boot. Then follow [docs/agents](docs/agents/README.md) (extraction, binary survey, host bring-up failure catalogue, graphics specifics, debugging recipes) and add a profile.
- Scripts the user runs live in `scripts/` at the repository root (now `build_ipa_from_nsp.py`). Each one runs both in Spyder (F5, configured by the settings block at its top) and from a terminal (command-line options override those settings; non-zero exit on error). Tools that only agents or other scripts call stay under `dm/`.
- Game files, keys and anything extracted from them live in the game's NSP folder, never in this repository and never in the home folder (it is nearly full): the work folder `<NSP folder>/<id>-work` for extraction, runs and analysis, beside the NSP script's `<id>_build/`. Tool venvs and other large scratch data go on that drive too. Do not print key values.
- Language: everything is in English: `README.md`, docs, code comments and other files. Log messages stay as they are, and red log lines start with `【红】`. Exception until a shader-chain rebuild: the files the chain version hashes (`dm/gfx/tools/dmsh_chain.py`, `dmsh_build.py`, `dmsh_fetch.py`, `dmsh_facts.py`, `dmsh_xlate/Program.cs`, `dmsh_xlate.csproj`, `dm/ios/tools/ios_metal.py`) and the `dmsh_chain.h` headers they generate still have bilingual comments; editing them changes every game's chain version.
- Language: everything is in English: `README.md`, docs, code comments, log messages, script output and other files. Red log lines (something unimplemented or wrong) start with `[RED] `. Text that must match a game's own Chinese data is written as escapes (`"\xE8\xB5\xB7"`), never as characters.
- After changing anything on the shader translation chain, run the chain procedure for each affected profile. The chain covers `dm/gfx/tools/dmsh_*`, `Program.cs`, the profile's `shader_map.tsv` / `shader_facts.tsv` / `shader_pairs.tsv`, and its `nvn_*` / `shader_*` keys. The procedure:
1. Run `dmsh_chain.py --header`, which writes the profile's `dmsh_chain.h`.
2. Rebuild the shaders (Mac and, with `IOSOUT`, iOS).
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 0100000000010000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "d4c4453854a2"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100000000010000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "9ca75fb4a525"
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
08145fb333797be6 {"local":[32,1,1],"shared":0}
0a3c1d0e17d14726 {"local":[32,1,1],"shared":0}
0b6c0da46a238f31 {"local":[32,1,1],"shared":0}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
00008cfe4fc1d31b fdaffc8b3cd6e0d4
0004d9615081f2dd b867e6b65f74d90d
001583b3af975129 c0b6ea0cb9f95b7a
Can't render this file because it is too large.
+1 -1
View File
@@ -1,4 +1,4 @@
# chain b-4c64982e23bf 2026-10-18T23:48:25+08:00
# chain b-d740cde14039 2026-10-19T15:33:24+08:00
35cbc686dd8461b8
4872bec9a5567f8b
54a618792b2d5f0f
1 # chain b-4c64982e23bf 2026-10-18T23:48:25+08:00 # chain b-d740cde14039 2026-10-19T15:33:24+08:00
2 35cbc686dd8461b8
3 4872bec9a5567f8b
4 54a618792b2d5f0f
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 010015100B514000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "4c64982e23bf"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010015100B514000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "d740cde14039"
+17 -22
View File
@@ -1,58 +1,53 @@
# 超级马力欧兄弟 惊奇(Switch,基础版 v0 与更新 1.2.1)的游戏档案。本目录另有着色器表(shader_split.py 生成)、c1_full.tsv、pso_manifest.tsv 与翻译链版本头 dmsh_chain.h
# Game profile of Super Mario Bros. Wonder (Switch, base game v0 and update 1.2.1). This folder also holds the shader tables (generated by shader_split.py), c1_full.tsv, pso_manifest.tsv and the chain version header dmsh_chain.h
# 格式:键 <TAB> 值;一个键多个值用空格分隔;build 每版一行;注释只能独占一行。改内存 / NVN 常数后重编宿主;改着色器相关的键后按 AGENTS.md 走翻译链流程
# Format: key <TAB> value; several values of one key are separated by spaces; one build row per version; comments must be on their own line. Rebuild the host after changing memory / NVN constants; after changing shader-related keys follow the chain procedure in AGENTS.md
# ---- 身份 | Identity ----
# 游戏名不写在这里:NSP 脚本从 NSP 的控制数据(NACP)取 | the game name is not kept here: the NSP script takes it from the NSP's control data (NACP)
# ---- Identity ----
# the game name is not kept here: the NSP script takes it from the NSP's control data (NACP)
title_id 010015100B514000
# svcGetInfo(ProgramId) 报给游戏的值(运行时 DM_PROGRAM_ID 可改) | the value svcGetInfo(ProgramId) reports to the game (DM_PROGRAM_ID overrides it at run time)
# the value svcGetInfo(ProgramId) reports to the game (DM_PROGRAM_ID overrides it at run time)
program_id 010015100B514000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name Super Mario Bros. Wonder
ipa SMBWMobile.ipa
app_category public.app-category.action-games
# ---- 版本 | Versions ----
# build <版本> <显示版本> <模块>=<构建号>…:NSP 脚本只认这些构建(写死的地址在别的构建上不成立);宿主按 main 的构建号报显示版本
# ---- Versions ----
# build <version> <display version> <module>=<build ID>...: the NSP script accepts only these builds (fixed addresses do not hold on other builds); the host reports the display version by main's build ID
build v0 1.0.0 main=cd6e42aee7934f4d8393cd43893ae25e sdk=3053b22935e32ddf15ef81997603e5c2
build v327680 1.2.1 main=ff773e90972d544eb79406eaa65396d5 sdk=e4bd58cac28cf13a08fb417aa013cdcb
# 缺省打进 IPA 的版本 | version packed into the IPA by default
# version packed into the IPA by default
package v327680
# 着色器表是这几个版本的并集(基础版在前):出包时这些版本都要有 | the shader tables are the union of these versions (base game first): all of them are needed to build
# the shader tables are the union of these versions (base game first): all of them are needed to build
shader_versions v0 v327680
# ---- 文件 | Files ----
# 改写并装载的代码模块(装载顺序;rtld 由宿主代替) | code modules rewritten and loaded (load order; rtld is replaced by the host)
# ---- Files ----
# code modules rewritten and loaded (load order; rtld is replaced by the host)
modules main sdk
# RomFS 里必须有的路径(认游戏文件放对了没有) | a path that must exist in the RomFS (tells whether the game files are in the right place)
# a path that must exist in the RomFS (tells whether the game files are in the right place)
romfs_marker System/Resource
# zip 时不再压缩的扩展名(本身已压缩) | extensions zip stores without compressing (already compressed)
# extensions zip stores without compressing (already compressed)
zip_store .zs:.bwav:.metallib
# ---- 内存(hle_svc.c;分区依据见那里的注释) | Memory (hle_svc.c; the reasons for the partition are in its comments) ----
# ---- Memory (hle_svc.c; the reasons for the partition are in its comments) ----
mem_reserve 0x140000000
mem_alias 0xB8000000
mem_stack 0x10000000
app_pool 0xCD500000
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填;按全库着色器实测) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ----
# c2:贴图句柄表 0x20 + 8 r(32 槽)、分离采样器表 0x568、图像句柄表 0x120 + 8 r(8 槽);translator 内置同值,不同就要先让翻译器可配
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ----
# c2: texture handle table 0x20 + 8 r (32 slots), separate sampler table 0x568, image handle table 0x120 + 8 r (8 slots); the translator has the same values built in, so a game that differs needs a configurable translator first
nvn_tex_base 0x20
nvn_tex_slots 32
nvn_samp_base 0x568
nvn_img_base 0x120
nvn_img_slots 8
# c0:存储缓冲表基址(每阶段 16 槽 × 0x10) | c0: storage buffer table bases (16 slots × 0x10 per stage)
# c0: storage buffer table bases (16 slots × 0x10 per stage)
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ----
# 来源组:路径里含前者就归后者组(按顺序取第一个;main 内嵌的固定归 main,都不中归 other) | source groups: a path containing the first part goes to the second group (first match wins; shaders embedded in main always go to main, no match goes to other)
# ---- Shader split (shader_split.py) ----
# source groups: a path containing the first part goes to the second group (first match wins; shaders embedded in main always go to main, no match goes to other)
shader_groups /Shader/SecredUber=SecredUber .sharcb=agl /Effect/=effect /Layout/=layout /Shader/=SecredUber
# 映射表表头里的着色器来源说明 | description of the shader source in the map table header
shader_note 马力欧着色器
# description of the shader source in the map table header
shader_note Super Mario Bros. Wonder shaders
1 # 超级马力欧兄弟 惊奇(Switch,基础版 v0 与更新 1.2.1)的游戏档案。本目录另有着色器表(shader_split.py 生成)、c1_full.tsv、pso_manifest.tsv 与翻译链版本头 dmsh_chain.h # Game profile of Super Mario Bros. Wonder (Switch, base game v0 and update 1.2.1). This folder also holds the shader tables (generated by shader_split.py), c1_full.tsv, pso_manifest.tsv and the chain version header dmsh_chain.h
# 超级马力欧兄弟 惊奇(Switch,基础版 v0 与更新 1.2.1)的游戏档案。本目录另有着色器表(shader_split.py 生成)、c1_full.tsv、pso_manifest.tsv 与翻译链版本头 dmsh_chain.h
1 # Game profile of Super Mario Bros. Wonder (Switch, base game v0 and update 1.2.1). This folder also holds the shader tables (generated by shader_split.py), c1_full.tsv, pso_manifest.tsv and the chain version header dmsh_chain.h # Game profile of Super Mario Bros. Wonder (Switch, base game v0 and update 1.2.1). This folder also holds the shader tables (generated by shader_split.py), c1_full.tsv, pso_manifest.tsv and the chain version header dmsh_chain.h
# 格式:键 <TAB> 值;一个键多个值用空格分隔;build 每版一行;注释只能独占一行。改内存 / NVN 常数后重编宿主;改着色器相关的键后按 AGENTS.md 走翻译链流程
2 # Format: key <TAB> value; several values of one key are separated by spaces; one build row per version; comments must be on their own line. Rebuild the host after changing memory / NVN constants; after changing shader-related keys follow the chain procedure in AGENTS.md # Format: key <TAB> value; several values of one key are separated by spaces; one build row per version; comments must be on their own line. Rebuild the host after changing memory / NVN constants; after changing shader-related keys follow the chain procedure in AGENTS.md
3 # ---- 身份 | Identity ---- # ---- Identity ----
4 # 游戏名不写在这里:NSP 脚本从 NSP 的控制数据(NACP)取 | the game name is not kept here: the NSP script takes it from the NSP's control data (NACP) # the game name is not kept here: the NSP script takes it from the NSP's control data (NACP)
5 title_id title_id
6 # svcGetInfo(ProgramId) 报给游戏的值(运行时 DM_PROGRAM_ID 可改) | the value svcGetInfo(ProgramId) reports to the game (DM_PROGRAM_ID overrides it at run time) # the value svcGetInfo(ProgramId) reports to the game (DM_PROGRAM_ID overrides it at run time)
7 program_id program_id
8 # ---- App ---- # ---- App ----
9 # 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本) # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
10 # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script) name
name
11 ipa ipa
12 app_category app_category
13 # ---- 版本 | Versions ---- # ---- Versions ----
14 # build <版本> <显示版本> <模块>=<构建号>…:NSP 脚本只认这些构建(写死的地址在别的构建上不成立);宿主按 main 的构建号报显示版本 # build <version> <display version> <module>=<build ID>...: the NSP script accepts only these builds (fixed addresses do not hold on other builds); the host reports the display version by main's build ID
15 # build <version> <display version> <module>=<build ID>...: the NSP script accepts only these builds (fixed addresses do not hold on other builds); the host reports the display version by main's build ID build
16 build build
build
17 # 缺省打进 IPA 的版本 | version packed into the IPA by default # version packed into the IPA by default
18 package package
19 # 着色器表是这几个版本的并集(基础版在前):出包时这些版本都要有 | the shader tables are the union of these versions (base game first): all of them are needed to build # the shader tables are the union of these versions (base game first): all of them are needed to build
20 shader_versions shader_versions
21 # ---- 文件 | Files ---- # ---- Files ----
22 # 改写并装载的代码模块(装载顺序;rtld 由宿主代替) | code modules rewritten and loaded (load order; rtld is replaced by the host) # code modules rewritten and loaded (load order; rtld is replaced by the host)
23 modules modules
24 # RomFS 里必须有的路径(认游戏文件放对了没有) | a path that must exist in the RomFS (tells whether the game files are in the right place) # a path that must exist in the RomFS (tells whether the game files are in the right place)
25 romfs_marker romfs_marker
26 # zip 时不再压缩的扩展名(本身已压缩) | extensions zip stores without compressing (already compressed) # extensions zip stores without compressing (already compressed)
27 zip_store zip_store
28 # ---- 内存(hle_svc.c;分区依据见那里的注释) | Memory (hle_svc.c; the reasons for the partition are in its comments) ---- # ---- Memory (hle_svc.c; the reasons for the partition are in its comments) ----
29 mem_reserve mem_reserve
30 mem_alias mem_alias
31 mem_stack mem_stack
32 app_pool app_pool
33 # ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填;按全库着色器实测) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ---- # ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ----
34 # c2:贴图句柄表 0x20 + 8 r(32 槽)、分离采样器表 0x568、图像句柄表 0x120 + 8 r(8 槽);translator 内置同值,不同就要先让翻译器可配 # c2: texture handle table 0x20 + 8 r (32 slots), separate sampler table 0x568, image handle table 0x120 + 8 r (8 slots); the translator has the same values built in, so a game that differs needs a configurable translator first
35 # c2: texture handle table 0x20 + 8 r (32 slots), separate sampler table 0x568, image handle table 0x120 + 8 r (8 slots); the translator has the same values built in, so a game that differs needs a configurable translator first nvn_tex_base
36 nvn_tex_base nvn_tex_slots
37 nvn_tex_slots nvn_samp_base
38 nvn_samp_base nvn_img_base
39 nvn_img_base nvn_img_slots
nvn_img_slots
40 # c0:存储缓冲表基址(每阶段 16 槽 × 0x10) | c0: storage buffer table bases (16 slots × 0x10 per stage) # c0: storage buffer table bases (16 slots × 0x10 per stage)
41 nvn_ssbo_bases nvn_ssbo_bases
42 # ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ---- # ---- Shader split (shader_split.py) ----
43 # 来源组:路径里含前者就归后者组(按顺序取第一个;main 内嵌的固定归 main,都不中归 other) | source groups: a path containing the first part goes to the second group (first match wins; shaders embedded in main always go to main, no match goes to other) # source groups: a path containing the first part goes to the second group (first match wins; shaders embedded in main always go to main, no match goes to other)
44 shader_groups shader_groups
45 # 映射表表头里的着色器来源说明 | description of the shader source in the map table header # description of the shader source in the map table header
46 shader_note shader_note
47
48
49
50
51
52
53
+2 -2
View File
@@ -1,2 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
2 # 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
02338175eea7af9e {"local":[8,8,1],"shared":0}
075c4abad92db59b {"local":[32,1,1],"shared":896}
0b14078b7931d41c {"local":[32,1,1],"shared":640}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:本作键已含代码区(dm/gfx/tools/shader_split.py),不会有同键异码,表空
# kind: evidence control-section key conflict rulings: this game's keys already include the code area (dm/gfx/tools/shader_split.py), so one key never has two different codes; empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:本作键已含代码区(dm/gfx/tools/shader_split.py),不会有同键异码,表空 # kind: evidence control-section key conflict rulings: this game's keys already include the code area (dm/gfx/tools/shader_split.py), so one key never has two different codes; empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
00261563be068efa 1dd0b5353b8d03d2
00261563be068efa 286980f75cc65755
0048a1f91885dc11 91454f8169b3676d
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 00261563be068efa
3 00261563be068efa
4 0048a1f91885dc11
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 010028600EBDA000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "fa0edc540e9a"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010028600EBDA000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "5b81a2e21261"
+14 -19
View File
@@ -1,42 +1,37 @@
# 超级马力欧 3D 世界 + 狂怒世界(Switch,基础版 v0 + 更新 1.2.2 = v262144)的游戏档案。移植中:Mac 上 3D 世界进到 1-1,狂怒世界进到开放世界
# Game profile of Super Mario 3D World + Bowser's Fury (Switch, base game v0 + update 1.2.2 = v262144). In bring-up: on the Mac 3D World reaches World 1-1 and Bowser's Fury its open world
# 格式见 dm/games/README.md | Format: see dm/games/README.md
# 任天堂 EPD 引擎(sead / agl,NintendoWare G3d / Ui2d / Vfx / Atk),SDK 10.4.0,NVN;RomFS 几乎全是 Yaz0 压缩的 SARC(.szs)。
# 着色器:ShaderData/*.szs 里的 bfsha(BNSH)与 agl sharcb(BAHS),EffectData 的粒子特效(BNSH),少数 LayoutData 布局(BNSH),SystemData/AglResource.szs(BAHS)
# Format: see dm/games/README.md
# Nintendo EPD's engine (sead / agl, NintendoWare G3d / Ui2d / Vfx / Atk), SDK 10.4.0, NVN; RomFS is almost entirely Yaz0-compressed SARC archives (.szs).
# Shaders: bfsha (BNSH) and agl sharcb (BAHS) in ShaderData/*.szs, particle effects in EffectData (BNSH), a few layouts in LayoutData (BNSH), SystemData/AglResource.szs (BAHS)
# ---- 身份 | Identity ----
# ---- Identity ----
title_id 010028600EBDA000
program_id 010028600EBDA000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name Super Mario 3D World + Bowser's Fury
ipa Mario3DWorld.ipa
app_category public.app-category.action-games
# 虚拟手柄首次用 3D 布局(十字键、右摇杆、L3 / R3):右摇杆转视角 | the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
# the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
pad_layout full
feedback_snap 66a71a3081b91b9a 30e3ccb0bd304ead
# ---- 版本 | Versions ----
# 只解了合并 1.2.2 的那份;基础版单独的构建号用到时再补 | only the merged 1.2.2 copy is extracted; the base game's own build IDs are added when needed
# ---- Versions ----
# only the merged 1.2.2 copy is extracted; the base game's own build IDs are added when needed
build v262144 1.2.2 main=635fe25c7cb69847f41b49f19cd9aa89 sdk=204eaef83fe5e5617d0787c8ef01a080
package v262144
shader_versions v262144
# ---- 文件 | Files ----
# ---- Files ----
modules main sdk
romfs_marker ShaderData/RenderMaterial.szs
zip_store .szs:.bfstm:.metallib
# ---- 共享字体 | Shared fonts ----
# 共享字体记作类型 0、不调 Decode,地址原样交给 ScalableFontEngine::LoadFont:地址处要直接是 TTF(main+0x975658)| shared fonts are recorded as type 0, no Decode, and the address goes as is to ScalableFontEngine::LoadFont: it must point at the TTF itself (main+0x975658)
# ---- Shared fonts ----
# shared fonts are recorded as type 0, no Decode, and the address goes as is to ScalableFontEngine::LoadFont: it must point at the TTF itself (main+0x975658)
shared_font ttf
# ---- 内存 | Memory ----
# NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。main 的 bss 只有约 7 MB。先用十三机兵防卫圈的分区(块 4 GB = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB),实测后再定
# ---- Memory ----
# The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 7 MB. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured
mem_reserve 0x100000000
mem_alias 0x10000000
@@ -44,11 +39,11 @@ mem_stack 0x10000000
app_pool 0xCD500000
# ---- NVN ----
# 游戏核对 NVN API 版本:主版本 == 53、次版本 ≥ 313(main+0x711c88),否则不建设备 | the game checks the NVN API version: major == 53 and minor >= 313 (main+0x711c88), else it creates no device
# the game checks the NVN API version: major == 53 and minor >= 313 (main+0x711c88), else it creates no device
device_api 53.313
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# 先沿用马力欧的,按全库着色器实测后再定 | Mario's values until measured over all shaders
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# Mario's values until measured over all shaders
nvn_tex_base 0x20
nvn_tex_slots 32
nvn_samp_base 0x568
@@ -56,6 +51,6 @@ nvn_img_base 0x120
nvn_img_slots 8
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ----
# ---- Shader split (shader_split.py) ----
shader_groups /ShaderData/=shader /EffectData/=effect /LayoutData/=layout /SystemData/=agl
shader_note 超级马力欧 3D 世界 + 狂怒世界着色器
shader_note Super Mario 3D World + Bowser's Fury shaders
1 # 超级马力欧 3D 世界 + 狂怒世界(Switch,基础版 v0 + 更新 1.2.2 = v262144)的游戏档案。移植中:Mac 上 3D 世界进到 1-1,狂怒世界进到开放世界 # Game profile of Super Mario 3D World + Bowser's Fury (Switch, base game v0 + update 1.2.2 = v262144). In bring-up: on the Mac 3D World reaches World 1-1 and Bowser's Fury its open world
# 超级马力欧 3D 世界 + 狂怒世界(Switch,基础版 v0 + 更新 1.2.2 = v262144)的游戏档案。移植中:Mac 上 3D 世界进到 1-1,狂怒世界进到开放世界
1 # Game profile of Super Mario 3D World + Bowser's Fury (Switch, base game v0 + update 1.2.2 = v262144). In bring-up: on the Mac 3D World reaches World 1-1 and Bowser's Fury its open world # Game profile of Super Mario 3D World + Bowser's Fury (Switch, base game v0 + update 1.2.2 = v262144). In bring-up: on the Mac 3D World reaches World 1-1 and Bowser's Fury its open world
2 # 格式见 dm/games/README.md | Format: see dm/games/README.md # Format: see dm/games/README.md
# 任天堂 EPD 引擎(sead / agl,NintendoWare G3d / Ui2d / Vfx / Atk),SDK 10.4.0,NVN;RomFS 几乎全是 Yaz0 压缩的 SARC(.szs)。
# 着色器:ShaderData/*.szs 里的 bfsha(BNSH)与 agl sharcb(BAHS),EffectData 的粒子特效(BNSH),少数 LayoutData 布局(BNSH),SystemData/AglResource.szs(BAHS)
3 # Nintendo EPD's engine (sead / agl, NintendoWare G3d / Ui2d / Vfx / Atk), SDK 10.4.0, NVN; RomFS is almost entirely Yaz0-compressed SARC archives (.szs). # Nintendo EPD's engine (sead / agl, NintendoWare G3d / Ui2d / Vfx / Atk), SDK 10.4.0, NVN; RomFS is almost entirely Yaz0-compressed SARC archives (.szs).
4 # Shaders: bfsha (BNSH) and agl sharcb (BAHS) in ShaderData/*.szs, particle effects in EffectData (BNSH), a few layouts in LayoutData (BNSH), SystemData/AglResource.szs (BAHS) # Shaders: bfsha (BNSH) and agl sharcb (BAHS) in ShaderData/*.szs, particle effects in EffectData (BNSH), a few layouts in LayoutData (BNSH), SystemData/AglResource.szs (BAHS)
5 # ---- 身份 | Identity ---- # ---- Identity ----
6 title_id title_id
7 program_id program_id
8 # ---- App ---- # ---- App ----
9 # 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本) # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
10 # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script) name
name
11 ipa ipa
12 app_category app_category
13 # 虚拟手柄首次用 3D 布局(十字键、右摇杆、L3 / R3):右摇杆转视角 | the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera # the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
14 pad_layout pad_layout
15 feedback_snap feedback_snap
16 # ---- 版本 | Versions ---- # ---- Versions ----
17 # 只解了合并 1.2.2 的那份;基础版单独的构建号用到时再补 | only the merged 1.2.2 copy is extracted; the base game's own build IDs are added when needed # only the merged 1.2.2 copy is extracted; the base game's own build IDs are added when needed
18 build build
19 package package
20 shader_versions shader_versions
21 # ---- 文件 | Files ---- # ---- Files ----
22 modules modules
23 romfs_marker romfs_marker
24 zip_store zip_store
25 # ---- 共享字体 | Shared fonts ---- # ---- Shared fonts ----
26 # 共享字体记作类型 0、不调 Decode,地址原样交给 ScalableFontEngine::LoadFont:地址处要直接是 TTF(main+0x975658)| shared fonts are recorded as type 0, no Decode, and the address goes as is to ScalableFontEngine::LoadFont: it must point at the TTF itself (main+0x975658) # shared fonts are recorded as type 0, no Decode, and the address goes as is to ScalableFontEngine::LoadFont: it must point at the TTF itself (main+0x975658)
27 shared_font shared_font
28 # ---- 内存 | Memory ---- # ---- Memory ----
29 # NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。main 的 bss 只有约 7 MB。先用十三机兵防卫圈的分区(块 4 GB = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB),实测后再定 # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 7 MB. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured
30 # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 7 MB. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured mem_reserve
31 mem_reserve mem_alias
32 mem_alias mem_stack
33 mem_stack app_pool
34 app_pool # ---- NVN ----
# ---- NVN ----
35 # 游戏核对 NVN API 版本:主版本 == 53、次版本 ≥ 313(main+0x711c88),否则不建设备 | the game checks the NVN API version: major == 53 and minor >= 313 (main+0x711c88), else it creates no device # the game checks the NVN API version: major == 53 and minor >= 313 (main+0x711c88), else it creates no device
36 device_api device_api
37 # ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ---- # ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
39 nvn_tex_base nvn_tex_base
40 nvn_tex_slots nvn_tex_slots
41 nvn_samp_base nvn_samp_base
42 nvn_img_base nvn_img_base
43 nvn_img_slots nvn_img_slots
44 nvn_ssbo_bases nvn_ssbo_bases
45 # ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ---- # ---- Shader split (shader_split.py) ----
46 shader_groups shader_groups
47 shader_note shader_note
48
49
51
52
53
54
55
56
@@ -1,4 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
2 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort. # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
137136fdf2d03d13 {"local":[32,1,1],"shared":0}
1430f92f3806cf9a {"local":[32,1,1],"shared":0}
15b5a9de9c886b5d {"local":[32,1,1],"shared":0}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
000064cdb6305b93 653b0b6b4b586c87
00021722b48824b8 797af20b9c75a60c
000b0b36bebae754 ae55491184a18f55
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 000064cdb6305b93
3 00021722b48824b8
4 000b0b36bebae754
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 01002B00111A2000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "5f57cfae0ff6"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01002B00111A2000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "613639403635"
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
042f8ea834cd727c {"local":[16,16,1],"shared":8192}
0465046677860bb7 {"local":[32,32,1],"shared":0}
04db7767a3e5062b {"local":[8,8,4],"shared":4112}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
001644c2f2467a09 2b9c80331d48cb20
002146fe8ed50512 2b9c80331d48cb20
003089bfd00e8731 2b9c80331d48cb20
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 001644c2f2467a09
3 002146fe8ed50512
4 003089bfd00e8731
+1 -1
View File
@@ -1 +1 @@
# chain b-9e8a8bed7481 2026-10-18T23:25:23+08:00
# chain b-286d88dfacfa 2026-10-19T15:29:08+08:00
1 # chain b-9e8a8bed7481 2026-10-18T23:25:23+08:00 # chain b-286d88dfacfa 2026-10-19T15:29:08+08:00
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 01003FC01670C000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "9e8a8bed7481"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01003FC01670C000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "286d88dfacfa"
+11 -18
View File
@@ -1,37 +1,31 @@
# 十三机兵防卫圈(Switch,基础版 v0 + 更新 1.10 = v65536)的游戏档案。移植中:Mac 上新游戏能从序章进到第一场战斗
# Game profile of 13 Sentinels: Aegis Rim (Switch, base game v0 + update 1.10 = v65536). In bring-up: on the Mac a new game gets through the prologue into the first battle
# 格式见 dm/games/README.md | Format: see dm/games/README.md
# Vanillaware 自己的引擎,SDK 11.4.3(sdk 与 subsdk0 与银河战士 生存恐惧的逐字节相同),NVN;数据全在 6 个 CRI CPK 里(ROBO_*.CPK),音频 CRI ADX2(acb / awb),影片 CRI USM。
# 着色器不在 RomFS:838 个 GLSLC 编译输出(魔数 0x19866891)编进了 main 的只读段,由 .data 里的表引用(shader_split.py 读,见 shader_main_glslc)
# Format: see dm/games/README.md
# Vanillaware's own engine, SDK 11.4.3 (sdk and subsdk0 are byte-identical to Metroid Dread's), NVN; all data is in 6 CRI CPK archives (ROBO_*.CPK), audio is CRI ADX2 (acb / awb), movies are CRI USM.
# The shaders are not in RomFS: 838 GLSLC compiler outputs (magic 0x19866891) are compiled into main's read-only segment and referenced from a table in .data (read by shader_split.py, see shader_main_glslc)
# ---- 身份 | Identity ----
# ---- Identity ----
title_id 01003FC01670C000
program_id 01003FC01670C000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name 13 Sentinels: Aegis Rim
ipa 13Sentinels.ipa
app_category public.app-category.role-playing-games
# ---- 版本 | Versions ----
# 只解了合并 1.10 的那份;基础版单独的构建号用到时再补 | only the merged 1.10 copy is extracted; the base game's own build IDs are added when needed
# ---- Versions ----
# only the merged 1.10 copy is extracted; the base game's own build IDs are added when needed
build v65536 1.10 main=4b632573ca5c160eabb70519fb622718 subsdk0=507f19d1240d09d00992248e8a0fd77e sdk=2d369d9999e3e10e665106f840ffff9b
package v65536
shader_versions v65536
# ---- 文件 | Files ----
# 装载顺序同 Horizon:main、subsdk*、sdk(subsdk0 = NintendoSDK_movie 11.4.3) | load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3)
# ---- Files ----
# load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3)
modules main subsdk0 sdk
romfs_marker ROBO_US.CPK
zip_store .CPK:.metallib
# ---- 内存 | Memory ----
# NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。main 的 bss 只有约 21 MB。实测(DM_SVC=1):堆按「池 − 已用」要 0xc9400000(3.14 GB);别名区只在 SDK 初始化时映射一页;
# 线程栈经 MapMemory 放进栈区。块 = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB = 4 GB。Mac 上进第一场战斗时常驻约 2.2 GB
# ---- Memory ----
# The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 21 MB. Measured (DM_SVC=1): the heap request is pool − used = 0xc9400000 (3.14 GB); the alias region gets one page during SDK initialization;
# thread stacks go into the stack region through MapMemory. Block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region = 4 GB. On the Mac about 2.2 GB is resident in the first battle
mem_reserve 0x100000000
@@ -39,8 +33,8 @@ mem_alias 0x10000000
mem_stack 0x10000000
app_pool 0xCD500000
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# 沿用马力欧的。全部 751 个着色器按这组基址翻译,绑定都解出了槽号(dmsh_build 报「绑定没解出槽号的着色器 0」);贴图走无绑定句柄 | Mario's values. All 751 shaders translate with these bases and every binding resolves to a slot (dmsh_build: 0 shaders with unresolved bindings); textures go through bindless handles
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# Mario's values. All 751 shaders translate with these bases and every binding resolves to a slot (dmsh_build: 0 shaders with unresolved bindings); textures go through bindless handles
nvn_tex_base 0x20
nvn_tex_slots 32
nvn_samp_base 0x568
@@ -48,10 +42,9 @@ nvn_img_base 0x120
nvn_img_slots 8
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py;main 里的 GLSLC 输出) | Shader split (shader_split.py; GLSLC outputs in main) ----
# ---- Shader split (shader_split.py; GLSLC outputs in main) ----
shader_main_glslc on
# 每份 GLSLC 输出各建一个单阶段程序(初始化时 838 次 ProgramSetShaders),绘制时 BindProgram 按阶段位拼顶点、像素:配对表按覆盖写(shader_split.py separable_pairs)
# Each GLSLC output becomes a single-stage program (838 ProgramSetShaders at startup), and BindProgram's stage bits combine vertex and pixel per draw: the pair table is written as a cover (shader_split.py separable_pairs)
shader_separable on
shader_groups ROBO=cpk
shader_note 十三机兵防卫圈着色器
shader_note 13 Sentinels: Aegis Rim shaders
1 # 十三机兵防卫圈(Switch,基础版 v0 + 更新 1.10 = v65536)的游戏档案。移植中:Mac 上新游戏能从序章进到第一场战斗 # Game profile of 13 Sentinels: Aegis Rim (Switch, base game v0 + update 1.10 = v65536). In bring-up: on the Mac a new game gets through the prologue into the first battle
# 十三机兵防卫圈(Switch,基础版 v0 + 更新 1.10 = v65536)的游戏档案。移植中:Mac 上新游戏能从序章进到第一场战斗
1 # Game profile of 13 Sentinels: Aegis Rim (Switch, base game v0 + update 1.10 = v65536). In bring-up: on the Mac a new game gets through the prologue into the first battle # Game profile of 13 Sentinels: Aegis Rim (Switch, base game v0 + update 1.10 = v65536). In bring-up: on the Mac a new game gets through the prologue into the first battle
2 # 格式见 dm/games/README.md | Format: see dm/games/README.md # Format: see dm/games/README.md
# Vanillaware 自己的引擎,SDK 11.4.3(sdk 与 subsdk0 与银河战士 生存恐惧的逐字节相同),NVN;数据全在 6 个 CRI CPK 里(ROBO_*.CPK),音频 CRI ADX2(acb / awb),影片 CRI USM。
# 着色器不在 RomFS:838 个 GLSLC 编译输出(魔数 0x19866891)编进了 main 的只读段,由 .data 里的表引用(shader_split.py 读,见 shader_main_glslc)
3 # Vanillaware's own engine, SDK 11.4.3 (sdk and subsdk0 are byte-identical to Metroid Dread's), NVN; all data is in 6 CRI CPK archives (ROBO_*.CPK), audio is CRI ADX2 (acb / awb), movies are CRI USM. # Vanillaware's own engine, SDK 11.4.3 (sdk and subsdk0 are byte-identical to Metroid Dread's), NVN; all data is in 6 CRI CPK archives (ROBO_*.CPK), audio is CRI ADX2 (acb / awb), movies are CRI USM.
4 # The shaders are not in RomFS: 838 GLSLC compiler outputs (magic 0x19866891) are compiled into main's read-only segment and referenced from a table in .data (read by shader_split.py, see shader_main_glslc) # The shaders are not in RomFS: 838 GLSLC compiler outputs (magic 0x19866891) are compiled into main's read-only segment and referenced from a table in .data (read by shader_split.py, see shader_main_glslc)
5 # ---- 身份 | Identity ---- # ---- Identity ----
6 title_id title_id
7 program_id program_id
8 # ---- App ---- # ---- App ----
9 # 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本) # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
10 # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script) name
name
11 ipa ipa
12 app_category app_category
13 # ---- 版本 | Versions ---- # ---- Versions ----
14 # 只解了合并 1.10 的那份;基础版单独的构建号用到时再补 | only the merged 1.10 copy is extracted; the base game's own build IDs are added when needed # only the merged 1.10 copy is extracted; the base game's own build IDs are added when needed
15 build build
16 package package
17 shader_versions shader_versions
18 # ---- 文件 | Files ---- # ---- Files ----
19 # 装载顺序同 Horizon:main、subsdk*、sdk(subsdk0 = NintendoSDK_movie 11.4.3) | load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3) # load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3)
20 modules modules
21 romfs_marker romfs_marker
22 zip_store zip_store
23 # ---- 内存 | Memory ---- # ---- Memory ----
24 # NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。main 的 bss 只有约 21 MB。实测(DM_SVC=1):堆按「池 − 已用」要 0xc9400000(3.14 GB);别名区只在 SDK 初始化时映射一页; # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 21 MB. Measured (DM_SVC=1): the heap request is pool − used = 0xc9400000 (3.14 GB); the alias region gets one page during SDK initialization;
25 # 线程栈经 MapMemory 放进栈区。块 = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB = 4 GB。Mac 上进第一场战斗时常驻约 2.2 GB # thread stacks go into the stack region through MapMemory. Block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region = 4 GB. On the Mac about 2.2 GB is resident in the first battle
26 # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. main's bss is only about 21 MB. Measured (DM_SVC=1): the heap request is pool − used = 0xc9400000 (3.14 GB); the alias region gets one page during SDK initialization; mem_reserve
27 # thread stacks go into the stack region through MapMemory. Block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region = 4 GB. On the Mac about 2.2 GB is resident in the first battle mem_alias
28 mem_reserve mem_stack
mem_alias
mem_stack
29 app_pool app_pool
30 # ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ---- # ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
31 # 沿用马力欧的。全部 751 个着色器按这组基址翻译,绑定都解出了槽号(dmsh_build 报「绑定没解出槽号的着色器 0」);贴图走无绑定句柄 | Mario's values. All 751 shaders translate with these bases and every binding resolves to a slot (dmsh_build: 0 shaders with unresolved bindings); textures go through bindless handles # Mario's values. All 751 shaders translate with these bases and every binding resolves to a slot (dmsh_build: 0 shaders with unresolved bindings); textures go through bindless handles
33 nvn_tex_slots nvn_tex_slots
34 nvn_samp_base nvn_samp_base
35 nvn_img_base nvn_img_base
36 nvn_img_slots nvn_img_slots
37 nvn_ssbo_bases nvn_ssbo_bases
38 # ---- 着色器拆分(shader_split.py;main 里的 GLSLC 输出) | Shader split (shader_split.py; GLSLC outputs in main) ---- # ---- Shader split (shader_split.py; GLSLC outputs in main) ----
39 shader_main_glslc shader_main_glslc
40 # 每份 GLSLC 输出各建一个单阶段程序(初始化时 838 次 ProgramSetShaders),绘制时 BindProgram 按阶段位拼顶点、像素:配对表按覆盖写(shader_split.py separable_pairs) # Each GLSLC output becomes a single-stage program (838 ProgramSetShaders at startup), and BindProgram's stage bits combine vertex and pixel per draw: the pair table is written as a cover (shader_split.py separable_pairs)
42 shader_separable shader_groups
43 shader_groups shader_note
44 shader_note
45
46
47
48
49
50
@@ -1,4 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
2 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort. # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1 +1 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
1 # 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省 # Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
0040f948baa144f4 0ddfcfcdcc3ddf9e
005dc0a0507ca0c0 0ddfcfcdcc3ddf9e
00870f86a49cf3b9 0ddfcfcdcc3ddf9e
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 0040f948baa144f4
3 005dc0a0507ca0c0
4 00870f86a49cf3b9
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 0100535012974000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "907d5c3232ae"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100535012974000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "675e2f28a97a"
+1 -1
View File
@@ -1 +1 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
1 # 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省 # Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1 +1 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 010055D009F78000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "020352bb86fa"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010055D009F78000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "b215c66de31d"
@@ -1,4 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
2 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort. # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
00d5d19d07c37895 {"local":[64,1,1],"shared":512}
0302e0b2c9a0a67d {"local":[64,1,1],"shared":304}
03a8070268830752 {"local":[64,1,1],"shared":512}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
00a49b8c9ac64535 042f574560d4d29c
00b8d7ff5efad105 042f574560d4d29c
010459e0117b01e9 042f574560d4d29c
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 00a49b8c9ac64535
3 00b8d7ff5efad105
4 010459e0117b01e9
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 01006BB00C6F0000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "d84b01f127bd"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01006BB00C6F0000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "e58ec56c1d38"
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
03c43da3e3f46da8 {"attr":{"1":"float","2":"float","3":"float","4":"float","5":"float"}}
079700f3577e311f {"attr":{"1":"float","2":"float","3":"float","4":"float","5":"float"}}
147c829e79fb322f {"attr":{"1":"float","2":"float","3":"float","4":"float","5":"float"}}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
003ae6cb51c2cf9f 45819944bd962440
003ccb11417c8bb5 09eb3491d53dc59b
003ccb11417c8bb5 2cc937320c4a2e78
Can't render this file because it is too large.
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 01007EF00011E000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "c2bffc1b7bae"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 01007EF00011E000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "72b3f2ce8dd0"
+11 -16
View File
@@ -1,49 +1,44 @@
# 塞尔达传说 旷野之息(Switch,基础版 v0 + 更新 1.9.0 = v1114112)的游戏档案。移植中
# Game profile of The Legend of Zelda: Breath of the Wild (Switch, base game v0 + update 1.9.0 = v1114112). In bring-up
# 格式见 dm/games/README.md | Format: see dm/games/README.md
# 任天堂 EPD 引擎(sead / agl / gsys / ksys,NintendoWare G3d / Ui2d / Vfx / Atk,Havok),SDK 20.5.22,NVN;模块 main + subsdk0(NintendoSDK_movie)+ sdk。
# RomFS 几乎全是 Yaz0(.s* 扩展名)与 SARC(Pack/*.pack)。着色器:Pack/Bootup_Graphics.pack 里的 Shader/*.sbfsha(BNSH)与 gsys / ksys 资源包,TitleBG.pack 的地形资源包,Effect/*.sesetlist 粒子特效,布局 .sblarc
# Format: see dm/games/README.md
# Nintendo EPD's engine (sead / agl / gsys / ksys, NintendoWare G3d / Ui2d / Vfx / Atk, Havok), SDK 20.5.22, NVN; modules main + subsdk0 (NintendoSDK_movie) + sdk.
# RomFS is almost all Yaz0 (.s* extensions) and SARC (Pack/*.pack). Shaders: Shader/*.sbfsha (BNSH) and the gsys / ksys resource packs in Pack/Bootup_Graphics.pack, the terrain resource pack in TitleBG.pack, particle effects in Effect/*.sesetlist, layouts in .sblarc
# ---- 身份 | Identity ----
# ---- Identity ----
title_id 01007EF00011E000
program_id 01007EF00011E000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name The Legend of Zelda: Breath of the Wild
ipa ZeldaBreathOfTheWild.ipa
app_category public.app-category.adventure-games
# 虚拟手柄首次用 3D 布局(十字键、右摇杆、L3 / R3):右摇杆转视角 | the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
# the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
pad_layout full
# ---- Graphics ----
# The frame-interval controller (60 fps cuts in patch_nso.py) and dynamic resolution read the GPU time between the frame's two timestamp reports: each report takes its segment's GPU start / end
gpu_timestamps segment
# ---- 版本 | Versions ----
# 只解了合并 1.9.0 的那份;基础版单独的构建号用到时再补 | only the merged 1.9.0 copy is extracted; the base game's own build IDs are added when needed
# ---- Versions ----
# only the merged 1.9.0 copy is extracted; the base game's own build IDs are added when needed
build v1114112 1.9.0 main=cd57b23fa4bbad65803d9788c01821ee subsdk0=f39f4c431234c72eb520cfdd1001bbcf sdk=574284b40c06cf1821a44857939083be
package v1114112
shader_versions v1114112
# ---- 文件 | Files ----
# ---- Files ----
modules main subsdk0 sdk
romfs_marker Pack/Bootup_Graphics.pack
zip_store .sstera:.sbactorpack:.bfstm:.sbfres:.sbmaptex:.bars:.sbitemico:.shknm2:.shktmrb:.sbeventpack:.pack:.sblwp:.sstats:.shksc:.smubin:.ssarc:.sesetlist:.webm:.jpg:.metallib
# ---- 内存 | Memory ----
# NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。先用十三机兵防卫圈的分区(块 4 GB = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB),实测后再定
# ---- Memory ----
# The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured
mem_reserve 0x100000000
mem_alias 0x10000000
mem_stack 0x10000000
app_pool 0xCD500000
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# 先沿用马力欧的,按全库着色器实测后再定 | Mario's values until measured over all shaders
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# Mario's values until measured over all shaders
nvn_tex_base 0x20
nvn_tex_slots 32
nvn_samp_base 0x568
@@ -51,6 +46,6 @@ nvn_img_base 0x120
nvn_img_slots 8
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ----
# ---- Shader split (shader_split.py) ----
shader_groups /Shader/=shader /Effect/=effect /Layout/=layout /System/=gsys /Terrain/=terrain
shader_note 塞尔达传说 旷野之息着色器
shader_note Breath of the Wild shaders
1 # 塞尔达传说 旷野之息(Switch,基础版 v0 + 更新 1.9.0 = v1114112)的游戏档案。移植中 # Game profile of The Legend of Zelda: Breath of the Wild (Switch, base game v0 + update 1.9.0 = v1114112). In bring-up
# 塞尔达传说 旷野之息(Switch,基础版 v0 + 更新 1.9.0 = v1114112)的游戏档案。移植中
1 # Game profile of The Legend of Zelda: Breath of the Wild (Switch, base game v0 + update 1.9.0 = v1114112). In bring-up # Game profile of The Legend of Zelda: Breath of the Wild (Switch, base game v0 + update 1.9.0 = v1114112). In bring-up
2 # 格式见 dm/games/README.md | Format: see dm/games/README.md # Format: see dm/games/README.md
# 任天堂 EPD 引擎(sead / agl / gsys / ksys,NintendoWare G3d / Ui2d / Vfx / Atk,Havok),SDK 20.5.22,NVN;模块 main + subsdk0(NintendoSDK_movie)+ sdk。
# RomFS 几乎全是 Yaz0(.s* 扩展名)与 SARC(Pack/*.pack)。着色器:Pack/Bootup_Graphics.pack 里的 Shader/*.sbfsha(BNSH)与 gsys / ksys 资源包,TitleBG.pack 的地形资源包,Effect/*.sesetlist 粒子特效,布局 .sblarc
3 # Nintendo EPD's engine (sead / agl / gsys / ksys, NintendoWare G3d / Ui2d / Vfx / Atk, Havok), SDK 20.5.22, NVN; modules main + subsdk0 (NintendoSDK_movie) + sdk. # Nintendo EPD's engine (sead / agl / gsys / ksys, NintendoWare G3d / Ui2d / Vfx / Atk, Havok), SDK 20.5.22, NVN; modules main + subsdk0 (NintendoSDK_movie) + sdk.
4 # RomFS is almost all Yaz0 (.s* extensions) and SARC (Pack/*.pack). Shaders: Shader/*.sbfsha (BNSH) and the gsys / ksys resource packs in Pack/Bootup_Graphics.pack, the terrain resource pack in TitleBG.pack, particle effects in Effect/*.sesetlist, layouts in .sblarc # RomFS is almost all Yaz0 (.s* extensions) and SARC (Pack/*.pack). Shaders: Shader/*.sbfsha (BNSH) and the gsys / ksys resource packs in Pack/Bootup_Graphics.pack, the terrain resource pack in TitleBG.pack, particle effects in Effect/*.sesetlist, layouts in .sblarc
5 # ---- 身份 | Identity ---- # ---- Identity ----
6 title_id title_id
7 program_id program_id
8 # ---- App ---- # ---- App ----
9 # 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本) # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
10 # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script) name
name
11 ipa ipa
12 app_category app_category
13 # 虚拟手柄首次用 3D 布局(十字键、右摇杆、L3 / R3):右摇杆转视角 | the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera # the virtual gamepad starts in the 3D layout (D-pad, right stick, L3 / R3): the right stick turns the camera
14 pad_layout pad_layout
15 # ---- Graphics ---- # ---- Graphics ----
16 # The frame-interval controller (60 fps cuts in patch_nso.py) and dynamic resolution read the GPU time between the frame's two timestamp reports: each report takes its segment's GPU start / end # The frame-interval controller (60 fps cuts in patch_nso.py) and dynamic resolution read the GPU time between the frame's two timestamp reports: each report takes its segment's GPU start / end
17 gpu_timestamps gpu_timestamps
18 # ---- 版本 | Versions ---- # ---- Versions ----
19 # 只解了合并 1.9.0 的那份;基础版单独的构建号用到时再补 | only the merged 1.9.0 copy is extracted; the base game's own build IDs are added when needed # only the merged 1.9.0 copy is extracted; the base game's own build IDs are added when needed
20 build build
21 package package
22 shader_versions shader_versions
23 # ---- 文件 | Files ---- # ---- Files ----
24 modules modules
25 romfs_marker romfs_marker
26 zip_store zip_store
27 # ---- 内存 | Memory ---- # ---- Memory ----
28 # NPDM 不声明系统资源(META +0x14 = 0):走 SetHeapSize。先用十三机兵防卫圈的分区(块 4 GB = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 3.25 GB),实测后再定 # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured
29 # The NPDM declares no system resource (META +0x14 = 0): the game uses SetHeapSize. Starts from 13 Sentinels' partition (4 GB block = 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region) until measured mem_reserve
30 mem_reserve mem_alias
31 mem_alias mem_stack
32 mem_stack app_pool
33 app_pool # ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
34 # 先沿用马力欧的,按全库着色器实测后再定 | Mario's values until measured over all shaders # Mario's values until measured over all shaders
35 nvn_tex_base nvn_tex_base
36 nvn_tex_slots nvn_tex_slots
37 nvn_samp_base nvn_samp_base
38 nvn_img_base nvn_img_base
39 nvn_img_slots nvn_img_slots
40 nvn_ssbo_bases nvn_ssbo_bases
41 # ---- 着色器拆分(shader_split.py) | Shader split (shader_split.py) ---- # ---- Shader split (shader_split.py) ----
42 shader_groups shader_groups
43 shader_note shader_note
44
46
47
48
49
50
51
@@ -1,4 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
2 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort. # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
001b98741aa774e4 {"local":[32,1,1],"shared":0}
062451f1f13eae17 {"local":[32,1,1],"shared":0}
1124a38a1f191344 {"local":[32,1,1],"shared":0}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
0002f3adb60ad088 158bc7996448bf0c
0008c66a52dbebd8 2f012059eb72df36
001c291f3cfe6938 2477d5ad1681ae9b
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 0002f3adb60ad088
3 0008c66a52dbebd8
4 001c291f3cfe6938
+1 -1
View File
@@ -1,3 +1,3 @@
# chain b-c1dfcd9e019a 2026-10-18T23:25:13+08:00
# chain b-36f66cb1ca43 2026-10-19T15:28:59+08:00
57eb9f0bae677756
f826f2b59cfa39a8
1 # chain b-c1dfcd9e019a 2026-10-18T23:25:13+08:00 # chain b-36f66cb1ca43 2026-10-19T15:28:59+08:00
2 57eb9f0bae677756 57eb9f0bae677756
3 f826f2b59cfa39a8 f826f2b59cfa39a8
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 010093801237C000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "c1dfcd9e019a"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010093801237C000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "36f66cb1ca43"
+11 -20
View File
@@ -1,36 +1,30 @@
# 银河战士 生存恐惧(Switch,基础版 v0 + 更新 2.1.0 = v327680)的游戏档案。移植起步中:内存值沿用马力欧的,待实测
# Game profile of Metroid Dread (Switch, base game v0 + update 2.1.0 = v327680). Bring-up has just started: the memory values are Mario's until measured
# 格式见 dm/games/README.md | Format: see dm/games/README.md
# MercurySteam 自己的引擎,SDK 11.4.3,NVN;脚本是 Lua 5.1 字节码(.lc),音频是 NintendoWare Atk。着色器全在 system/shd/*.bshdat(MSHD 容器,shader_split.py 读)
# Format: see dm/games/README.md
# MercurySteam's own engine, SDK 11.4.3, NVN; scripts are Lua 5.1 bytecode (.lc), audio is NintendoWare Atk. Every shader is in system/shd/*.bshdat (MSHD containers, read by shader_split.py)
# ---- 身份 | Identity ----
# ---- Identity ----
title_id 010093801237C000
program_id 010093801237C000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name Metroid Dread
ipa MetroidDread.ipa
app_category public.app-category.action-games
# ---- 版本 | Versions ----
# 只解了合并 2.1.0 的那份;基础版单独的构建号用到时再补 | only the merged 2.1.0 copy is extracted; the base game's own build IDs are added when needed
# ---- Versions ----
# only the merged 2.1.0 copy is extracted; the base game's own build IDs are added when needed
build v327680 2.1.0 main=646761f643afebb379edd5e6a5151af2 subsdk0=507f19d1240d09d00992248e8a0fd77e sdk=2d369d9999e3e10e665106f840ffff9b
package v327680
shader_versions v327680
# ---- 文件 | Files ----
# 装载顺序同 Horizon:main、subsdk*、sdk(subsdk0 = NintendoSDK_movie 11.4.3) | load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3)
# ---- Files ----
# load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie 11.4.3)
modules main subsdk0 sdk
romfs_marker system/shd
zip_store .bctex:.webm:.bfstm:.metallib
# ---- 内存 | Memory ----
# NPDM 不声明系统资源(META +0x14 = 0),没有虚拟地址内存:走 SetHeapSize。main 的 bss 约 1.8 GB(0x70cd2fa0,引擎的静态堆),在模块映像里,不占下面的块。
# 实测(DM_SVC=1):别名区只在 SDK 初始化时映射一页(4 KB);线程栈经 MapMemory 放进栈区;堆按「池 − 已用」要 0x58800000(1.38 GB,已用含 1.8 GB bss)。
# 所以块只要 2.25 GB = 前置 256 MB + 别名区 256 MB + 栈区 256 MB + 堆区 1.5 GB;连 main 的 1.9 GB 映像共约 4.3 GB,低于真机跑通过的 5 GB(D2R)。应用池仍报 Switch 的 3285 MB
# ---- Memory ----
# The NPDM declares no system resource (META +0x14 = 0), so there is no virtual address memory: the game uses SetHeapSize. main's bss is about 1.8 GB (0x70cd2fa0, the engine's static heap); it is part of the module image, not of the block below.
# Measured (DM_SVC=1): the alias region gets one page (4 KB) mapped during SDK initialization; thread stacks go into the stack region through MapMemory; the heap request is pool − used = 0x58800000 (1.38 GB, with the 1.8 GB bss counted as used).
# So the block needs only 2.25 GB = 256 MB in front + 256 MB alias region + 256 MB stack region + 1.5 GB heap region; with main's 1.9 GB image that is about 4.3 GB, below the 5 GB proven on a phone (D2R). The application pool is still the Switch's 3285 MB
@@ -39,15 +33,12 @@ mem_alias 0x10000000
mem_stack 0x10000000
app_pool 0xCD500000
# ---- 线程 | Threads ----
# 同核抢占近似(hle_thread.c):影片播放器的主线程(优先级 44,核 0)把「摘等待项」请求投给分发线程、给工作线程留下消息后(两者优先级 30,只许核 0)不等就释放对象、在原处建新对象;
# 真机上它们立即抢占主线程、先处理完。宿主不近似就在片头 / 标题影片切换时约半数运行崩(SignalEvent 进空 MultiWait、main+0x225338 读已释放对象),近似后 12 次连跑无崩
# ---- Threads ----
# Same-core preemption approximation (hle_thread.c): the movie player's main thread (priority 44, core 0) posts "unlink the holders" to the dispatcher thread and leaves messages for the worker thread (both priority 30, core 0 only), then frees the object and builds a new one in place without waiting;
# on hardware they preempt the main thread at once and finish first. Without the approximation about half of the runs crashed at the intro / title movie changes (SignalEvent on a null MultiWait, main+0x225338 reading a freed object); with it 12 runs in a row were clean
core_preempt on
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# 着色器文件里留着 SASS 反汇编:全部 TEX / TEXS 的句柄立即数是 0x8–0x22 字(步长 2),即 c2 字节 0x20 + 8 r,与马力欧同(翻译器内置的就是这组)。其余沿用马力欧的,待翻译后按全库实测
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time) ----
# The shader files keep the SASS disassembly: the handle immediates of every TEX / TEXS are words 0x8–0x22 (step 2), i.e. c2 bytes 0x20 + 8 r, as in Mario (the set built into the translator). The rest stay as Mario's until measured over all translated shaders
nvn_tex_base 0x20
nvn_tex_slots 32
@@ -56,6 +47,6 @@ nvn_img_base 0x120
nvn_img_slots 8
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py;MSHD 容器) | Shader split (shader_split.py; MSHD containers) ----
# ---- Shader split (shader_split.py; MSHD containers) ----
shader_groups /system/shd/=shd
shader_note 银河战士 生存恐惧着色器
shader_note Metroid Dread shaders
Can't render this file because it contains an unexpected character in line 37 and column 113.
@@ -1,4 +1,2 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
# Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 本作尚未录制(DM_PSO_RECORD):空表 = 不预热。行序 = 首次使用顺序,不要排序。
2 # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort. # Not recorded yet for this game (DM_PSO_RECORD): an empty list = no prewarm. Row order = first-use order; do not sort.
+1 -1
View File
@@ -1 +1 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
1 # 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省 # Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
003b38fe9b3627ba d8d69e1d5331e497
0076e7956282bfc5 245b7bce27997afb
0076e7956282bfc5 78f87b399904ae28
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 003b38fe9b3627ba
3 0076e7956282bfc5
4 0076e7956282bfc5
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 010099C022B96000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "51ce8e63d3fa"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 010099C022B96000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "9a08b2229283"
+1 -1
View File
@@ -1 +1 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
1 # 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省 # Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1 +1 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 0100A6301214E000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "26bf029c37b4"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100A6301214E000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "af88a8c9e8e6"
+13 -25
View File
@@ -1,50 +1,39 @@
# 火焰纹章 Engage(Switch,基础版 v0 + 更新 2.0.0 = v327680)的游戏档案。Mac 上能到标题画面;NVN 句柄表按全库着色器实测,内存分区为 Mac 起机所设
# Game profile of Fire Emblem Engage (Switch, base game v0 + update 2.0.0 = v327680). It reaches the title screen on the Mac; the NVN handle tables are measured over all its shaders, the memory partition is set for Mac bring-up
# 格式见 dm/games/README.md | Format: see dm/games/README.md
# Unity 2020.3.18f1(IL2CPP),SDK 12.3.7:着色器是 UnityFS 包与 data.unity3d 里的 Shader 对象(shader_split.py 经 unityfs.py 读出) | Unity 2020.3.18f1 (IL2CPP), SDK 12.3.7: the shaders are Shader objects in UnityFS bundles and data.unity3d (read by shader_split.py through unityfs.py)
# Format: see dm/games/README.md
# Unity 2020.3.18f1 (IL2CPP), SDK 12.3.7: the shaders are Shader objects in UnityFS bundles and data.unity3d (read by shader_split.py through unityfs.py)
# ---- 身份 | Identity ----
# ---- Identity ----
title_id 0100A6301214E000
program_id 0100A6301214E000
# ---- App ----
# 游戏名:只给档案列表与 Mac 窗口标题当标签;iOS App 名取基础版 NSP 里的游戏名,包名按基础版标题号(build_ipa.sh / NSP 脚本)
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name Fire Emblem Engage
ipa FEEngage.ipa
app_category public.app-category.strategy-games
# ---- 版本 | Versions ----
# 只解了合并 2.0.0 的那份;基础版单独的构建号用到时再补 | only the merged 2.0.0 copy is extracted; the base game's own build IDs are added when needed
# ---- Versions ----
# only the merged 2.0.0 copy is extracted; the base game's own build IDs are added when needed
build v327680 2.0.0 main=8c08b9719e085f91847b5e0f935d9488 subsdk0=bffed55ea46c1b48ddeabcacdfbf81bf sdk=56ec24747ec9e7f19b3efb062483a19d
package v327680
shader_versions v327680
# ---- 文件 | Files ----
# 装载顺序同 Horizon:main、subsdk*、sdk(subsdk0 = NintendoSDK_movie) | load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie)
# ---- Files ----
# load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie)
modules main subsdk0 sdk
romfs_marker Data/data.unity3d
zip_store .bundle:.wem:.unity3d:.metallib
# ---- 出图 | Presentation ----
# 原版设出图间隔 2(Switch 上 30 帧)。2.0.0 main 有一处改写剪口(patch_nso.py,QualitySettings.set_vSyncCount 只能设 1),走游戏自己的 60 帧路径:
# 按帧计数的逻辑都按 GameTime.VsyncFixedCount(= vSyncCount)步进,计数 1、60 帧时速度与原版 30 帧相同。于是游戏自己设间隔 1,game 模式给 60 帧。
# Mac 实测(存档列表按住下 0.45 秒):剪口 60 帧与原版 30 帧都移 6 格;不剪而用 DM_PRESENT_INTERVAL=display 强出 60 帧时移到底(≥9 格),即按帧计数的逻辑加速
# ---- Presentation ----
# The original sets present interval 2 (30 fps on the Switch). A rewriter cut on 2.0.0 main (patch_nso.py, QualitySettings.set_vSyncCount can only set 1) takes the game's own 60 fps path:
# frame-counted logic steps by GameTime.VsyncFixedCount (= vSyncCount), so a count of 1 at 60 fps runs at the speed of the original at 30 fps. The game then sets interval 1 itself and game mode gives 60 fps.
# Measured on the Mac (holding Down for 0.45 s in the save list): 60 fps with the cut and the original 30 fps both move 6 slots; without the cut, 60 fps forced by DM_PRESENT_INTERVAL=display reaches the end (9 or more), i.e. frame-counted logic speeds up
present_interval game
# ---- 内存 | Memory ----
# NPDM 声明系统资源 16 MB,所以 IsVirtualAddressMemoryEnabled 为真:nninitStartup 把整个空闲内存(应用池 − 64 MB)作虚拟地址内存交给 StandardAllocator,
# 在别名区里一次分一整块地址区域(按 1 GB 对齐)。Unity 的低层虚拟分配器也自己 AllocateAddressRegion(每块按尺寸对齐,2.25 MB 占 4 MB、16.25 MB 占 20 MB),
# 序章战斗时约 1.05 GB。较新 SDK 的 AddressRegionManager 先占别名区开头约 1 GB 作节点区;patch_nso.py 给本作 sdk 构建登记了剪口,把它缩到 62 MB(真机别名区 64 GB,不在乎这点)。
# ---- Memory ----
# The NPDM declares 16 MB of system resource, so IsVirtualAddressMemoryEnabled is true: nninitStartup hands all free memory (application pool − 64 MB) to StandardAllocator as virtual address memory,
# one address region in the alias region (1 GB aligned). Unity's low-level virtual allocator also calls AllocateAddressRegion itself (each block aligned by size: 2.25 MB takes 4 MB, 16.25 MB takes 20 MB),
# about 1.05 GB in the prologue battle. Newer SDKs' AddressRegionManager first takes about 1 GB at the start of the alias region as its node area; patch_nso.py registers a cut for this game's sdk build that shrinks it to 62 MB (the hardware's alias region is 64 GB and does not care).
# 实测(序章到战斗,12 分钟):StandardAllocator 只提交 0.58 GB,Unity 自己的区域提交 0.97 GB,合计 1.55 GB。所以应用池报 2.5 GB(StandardAllocator 约 2.44 GB,是用量的 4 倍),
# 整块就能放进 5 GB = 前置 256 MB + 别名区 4.375 GB + 栈区 256 MB + 堆区 128 MB(Engage 不调 SetHeapSize,线程栈在栈区里随机放)。5 GB 是真机跑通过的总量(D2R);
# iOS 不带「扩展虚拟寻址」授权时单块最多 6 GB,但 Metal 等还要地址(Mac 上 Engage 的显存分配约 1 GB),原先的 6 GB 块在手机上没有余量。
# Measured (prologue to the battle, 12 minutes): StandardAllocator commits only 0.58 GB and Unity's own regions 0.97 GB, 1.55 GB in all. So the pool is reported as 2.5 GB (StandardAllocator about 2.44 GB, four times its use),
# and the block fits in 5 GB = 256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region (Engage never calls SetHeapSize; thread stacks are placed at random in the stack region). 5 GB is the total proven on a phone (D2R);
# without the "extended virtual addressing" entitlement iOS allows one reservation of at most 6 GB, but Metal and the rest need address space too (Engage allocates about 1 GB of GPU memory on the Mac), so the former 6 GB block left no margin on a phone.
@@ -53,8 +42,7 @@ mem_alias 0x118000000
mem_stack 0x10000000
app_pool 0xA0000000
# ---- NVN 驱动常量段(dmsh_build.py 出包、nvn_mtl.m 运行时现填;按全库着色器实测) | NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ----
# 旧驱动布局(同 D2R):c2 贴图句柄表 0x168 + 8 r(128 槽)、分离采样器表 0x568 + 8 r;Unity 只用分离贴图 / 采样器(BindSeparateTexture / Sampler)。图像表与 c0 存储缓冲表未实测,先沿用
# ---- NVN driver constant segments (dmsh_build.py at packaging, nvn_mtl.m filled at run time; measured over all shaders) ----
# Older driver layout (as D2R): c2 texture handle table 0x168 + 8 r (128 slots), separate sampler table 0x568 + 8 r; Unity uses only separate textures / samplers (BindSeparateTexture / Sampler). The image table and the c0 storage buffer table are not measured yet and stay as they were
nvn_tex_base 0x168
nvn_tex_slots 128
@@ -63,7 +51,7 @@ nvn_img_base 0x120
nvn_img_slots 8
nvn_ssbo_bases vs=0x110 ps=0x510 cs=0x310
# ---- 着色器拆分(shader_split.py;Unity Shader 对象,读法见 unityfs.py) | Shader split (shader_split.py; Unity Shader objects, read by unityfs.py) ----
# 引擎自带(data.unity3d、Resources/unity default resources)归 engine,Addressables 资源包归 assets | engine built-ins (data.unity3d, Resources/unity default resources) go to engine, the Addressables bundles to assets
# ---- Shader split (shader_split.py; Unity Shader objects, read by unityfs.py) ----
# engine built-ins (data.unity3d, Resources/unity default resources) go to engine, the Addressables bundles to assets
shader_groups /data.unity3d=engine /Resources/=engine /StreamingAssets/=assets
shader_note Engage 着色器
shader_note Fire Emblem Engage shaders
Can't render this file because it contains an unexpected character in line 39 and column 15.
@@ -1,6 +1,4 @@
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# Mac 录制(DM_PSO_RECORD,预热关):开机 → 片头 → 标题 → 读存档 → 序章战斗与战斗演出,548 条渲染管线 + 798 个着色器。行序 = 首次使用顺序,不要排序。真机按它在首帧起后台预热,免得进片头前同步编几百条管线(iPhone 实测一次 1.85 s)
# Recorded on the Mac (DM_PSO_RECORD, prewarm off): boot → opening movie → title → save load → prologue battle and combat, 548 render pipelines + 798 shaders. Row order = first-use order; do not sort. Devices warm these up in the background from the first frame, instead of building hundreds of pipelines synchronously before the movie (1.85 s at once, measured on an iPhone)
S 84ae0a2e9ed42909_b 0
S 9f7ea3433bb8f0a6_b 1
1 # 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# 预热种子清单(随包,与 shader_map.tsv 同目录)。行:S<TAB>着色器键<TAB>阶段 / R<TAB>渲染管线键(pso_skey 十六进制)/ C<TAB>计算着色器键。
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key. # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# Mac 录制(DM_PSO_RECORD,预热关):开机 → 片头 → 标题 → 读存档 → 序章战斗与战斗演出,548 条渲染管线 + 798 个着色器。行序 = 首次使用顺序,不要排序。真机按它在首帧起后台预热,免得进片头前同步编几百条管线(iPhone 实测一次 1.85 s)
2 # Recorded on the Mac (DM_PSO_RECORD, prewarm off): boot → opening movie → title → save load → prologue battle and combat, 548 render pipelines + 798 shaders. Row order = first-use order; do not sort. Devices warm these up in the background from the first frame, instead of building hundreds of pipelines synchronously before the movie (1.85 s at once, measured on an iPhone) # Recorded on the Mac (DM_PSO_RECORD, prewarm off): boot → opening movie → title → save load → prologue battle and combat, 548 render pipelines + 798 shaders. Row order = first-use order; do not sort. Devices warm these up in the background from the first frame, instead of building hundreds of pipelines synchronously before the movie (1.85 s at once, measured on an iPhone)
3 S S
4 S S
+1 -1
View File
@@ -1 +1 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
1 # 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省 # Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
0005f969d0503763 4e5e3e7e2bf888a6
0013778be04d7552 625913f24c1c27da
001cd961d56560ab 96c99a5b2f35e26e
1 # kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key # kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
2 0005f969d0503763
3 0013778be04d7552
4 001cd961d56560ab
+2 -2
View File
@@ -1,2 +1,2 @@
// 生成文件(dm/gfx/tools/dmsh_chain.py --header,游戏 0100F2C0115B6000):本源码树期望的 B 着色器库翻译链版本(缺省选项);宿主只加载 <库目录>/b-<V>/
#define DMSH_CHAIN "e1b1691d9514"
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100F2C0115B6000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "d99146495c62"
+1 -1
View File
@@ -1,4 +1,4 @@
# 翻译查询的作答表(dm/gfx/tools/shader_split.py 生成,勿手改):键 <TAB> 事实 JSON。本作没有 PC 版可配对:只有计算着色器的线程组 / 共享内存(取自控制段),其余查询按翻译器缺省
# Answer table for translation queries (generated by dm/gfx/tools/shader_split.py, do not edit): key <TAB> fact JSON. This game has no PC version to pair with: only the thread group / shared memory of compute shaders (taken from the control section); other queries get the translator defaults
00326ff37aed3099 {"local":[32,1,1],"shared":0}
019e4e1b9c9553ca {"local":[32,1,1],"shared":0}
01c0d1ea17e642a6 {"local":[32,1,1],"shared":0}
Can't render this file because it contains an unexpected character in line 2 and column 19.
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
# kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence 控制段键冲突裁定:表空 | control-section key conflict rulings: empty # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 dm_ctl_sha16
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence BNSH 变体里的顶点 / 像素着色器配对(dm/gfx/tools/shader_split.py 生成,勿手改):顶点键 <TAB> 像素键 | vertex / pixel shader pairs within BNSH variations (generated, do not edit): vertex key <TAB> pixel key
# kind: evidence vertex / pixel shader pairs within BNSH variations (generated by dm/gfx/tools/shader_split.py, do not edit): vertex key <TAB> pixel key
00027f7dc4646be2 bb23ee5354bf4517
0003381c7ea4650d a76f7a12126b1a48
00147d2e62d54f26 16f5b942537783cd
Can't render this file because it is too large.
+16 -16
View File
@@ -25,7 +25,7 @@ class Game:
def get(self, key, default=None):
r = self.values(key)
if r: return '\t'.join(r[0])
if default is None: raise KeyError(f'{self.dir / "game.tsv"} 没有键 {key} | has no key {key}')
if default is None: raise KeyError(f'{self.dir / "game.tsv"} has no key {key}')
return default
def words(self, key, default=None): return self.get(key, default).split()
@@ -59,11 +59,11 @@ def load(name=None):
name = name or os.environ.get('DM_GAME')
if not name: # there is no default game
have = ', '.join(f'{g.id} ({g.get("name", "")})' for g in all_games())
raise SystemExit(f'【红】没设 DM_GAME:用基础版标题号选游戏档案:{have} | DM_GAME is not set: pick a game profile by base game title ID: {have}')
raise SystemExit(f'[RED] DM_GAME is not set: pick a game profile by base game title ID: {have}')
d = find_dir(name)
if not (d / 'game.tsv').exists(): # profile folders are named after the base game's title ID (the old names smbw / fe_engage are gone)
have = ', '.join(f'{g.id} ({g.get("name", "")})' for g in all_games())
raise SystemExit(f'【红】没有游戏档案 {d / "game.tsv"}(DM_GAME / GAME 用基础版标题号:{have})| no game profile {d / "game.tsv"} (DM_GAME / GAME take the base game title ID: {have})')
raise SystemExit(f'[RED] no game profile {d / "game.tsv"} (DM_GAME / GAME take the base game title ID: {have})')
return Game(d)
@@ -88,29 +88,29 @@ def header(g):
sb = ' : '.join(f'(stage) == {STAGES[k]} ? {v}' for k, v in ssbo.items()) + ' : -1'
vers = ', '.join('{' + q(b['main']) + ', ' + q(b['display']) + '}' for b in g.builds().values() if 'main' in b)
pi = g.get('present_interval', 'game') # present interval: game = as the game sets it (as on the Switch), display = one frame per display tick
if pi not in ('game', 'display'): sys.exit(f'{g.id}: present_interval 只能是 game / display | present_interval must be game or display: {pi}')
if pi not in ('game', 'display'): sys.exit(f'{g.id}: present_interval must be game or display: {pi}')
cp = g.get('core_preempt', 'off') # same-core preemption approximation (hle_thread.c): on / off
if cp not in ('on', 'off'): sys.exit(f'{g.id}: core_preempt 只能是 on / off | core_preempt must be on or off: {cp}')
if cp not in ('on', 'off'): sys.exit(f'{g.id}: core_preempt must be on or off: {cp}')
sf = g.get('shared_font', 'bfttf') # what the shared-font address points at (hle_sdk16.c): bfttf = 8-byte header + TTF, ttf = the TTF alone
if sf not in ('bfttf', 'ttf'): sys.exit(f'{g.id}: shared_font 只能是 bfttf / ttf | shared_font must be bfttf or ttf: {sf}')
if sf not in ('bfttf', 'ttf'): sys.exit(f'{g.id}: shared_font must be bfttf or ttf: {sf}')
pl = g.get('pad_layout', 'simple') # the virtual gamepad's first layout (DmGamepad.m; afterwards whatever the player switched to in game is kept): simple = 2D, full = 3D (D-pad, right stick, L3 / R3)
if pl not in ('simple', 'full'): sys.exit(f'{g.id}: pad_layout 只能是 simple / full | pad_layout must be simple or full: {pl}')
if pl not in ('simple', 'full'): sys.exit(f'{g.id}: pad_layout must be simple or full: {pl}')
fbs = g.words('feedback_snap', '') # fragment shader key prefixes whose same-pass feedback sampling gets a snapshot first (nvn_mtl.m fb_prepare); none by default
if any(not re.fullmatch(r'[0-9a-f]{8,16}', k) for k in fbs): sys.exit(f'{g.id}: feedback_snap 要写片元着色器键(8–16 位小写十六进制)| feedback_snap takes fragment shader keys (8-16 lowercase hex digits): {fbs}')
if any(not re.fullmatch(r'[0-9a-f]{8,16}', k) for k in fbs): sys.exit(f'{g.id}: feedback_snap takes fragment shader keys (8-16 lowercase hex digits): {fbs}')
afr = g.words('alpha_from_red', '') # fragment shader key prefixes that read single-channel textures mapped (R, 0, 0, 1) with alpha = red (nvn_mtl.m tex_alpha_red); none by default
if any(not re.fullmatch(r'[0-9a-f]{8,16}', k) for k in afr): sys.exit(f'{g.id}: alpha_from_red 要写片元着色器键(8–16 位小写十六进制)| alpha_from_red takes fragment shader keys (8-16 lowercase hex digits): {afr}')
if any(not re.fullmatch(r'[0-9a-f]{8,16}', k) for k in afr): sys.exit(f'{g.id}: alpha_from_red takes fragment shader keys (8-16 lowercase hex digits): {afr}')
rcd = g.get('render_cond', 'off') # NVN conditional rendering evaluated at replay, timestamp reports with counter 0 (nvn_mtl.m OP_RENDER_COND): on / off
if rcd not in ('on', 'off'): sys.exit(f'{g.id}: render_cond 只能是 on / off | render_cond must be on or off: {rcd}')
if rcd not in ('on', 'off'): sys.exit(f'{g.id}: render_cond must be on or off: {rcd}')
tfu = g.get('tex_flush_upload', 'off') # textures written by the CPU into a pool and published with nvnMemoryPoolFlushMappedRange are uploaded like packaged ones (hle_nvn.c): on / off
if tfu not in ('on', 'off'): sys.exit(f'{g.id}: tex_flush_upload 只能是 on / off | tex_flush_upload must be on or off: {tfu}')
if tfu not in ('on', 'off'): sys.exit(f'{g.id}: tex_flush_upload must be on or off: {tfu}')
tdu = g.get('tex_dirty_upload', 'off') # with tex_flush_upload: a registered texture the CPU writes after init without any flush is uploaded too (dirty pages, hle_nvn.c nvn_ft_written): on / off
if tdu not in ('on', 'off') or (tdu == 'on' and tfu != 'on'): sys.exit(f'{g.id}: tex_dirty_upload 只能是 on / off,且要 tex_flush_upload on | tex_dirty_upload must be on or off, and needs tex_flush_upload on: {tdu}')
if tdu not in ('on', 'off') or (tdu == 'on' and tfu != 'on'): sys.exit(f'{g.id}: tex_dirty_upload must be on or off, and needs tex_flush_upload on: {tdu}')
gts = g.get('gpu_timestamps', 'submission') # counter timestamps (nvn_mtl.m rep_ticks): submission = one time per submission, segment = the GPU start / end of the segment holding the report
if gts not in ('submission', 'segment'): sys.exit(f'{g.id}: gpu_timestamps 只能是 submission / segment | gpu_timestamps must be submission or segment: {gts}')
if gts not in ('submission', 'segment'): sys.exit(f'{g.id}: gpu_timestamps must be submission or segment: {gts}')
om = g.get('op_mode', 'console') # the operation mode reported at start (hle_oe.c dm_opmode; DM_OPMODE overrides): console = docked, handheld = touch screen in use (Super Mario Galaxy's pointer)
if om not in ('console', 'handheld'): sys.exit(f'{g.id}: op_mode 只能是 console / handheld | op_mode must be console or handheld: {om}')
if om not in ('console', 'handheld'): sys.exit(f'{g.id}: op_mode must be console or handheld: {om}')
api = g.get('device_api', '55.15') # the NVN API major.minor version nvnDeviceGetInteger reports (hle_nvn.c)
if not re.fullmatch(r'\d+\.\d+', api): sys.exit(f'{g.id}: device_api 要写成 主.次 | device_api must be major.minor: {api}')
if not re.fullmatch(r'\d+\.\d+', api): sys.exit(f'{g.id}: device_api must be major.minor: {api}')
src = f'dm/games/{g.id}' if g.dir.parent == HERE else str(g.dir)
L = [f'// Generated by dm/games/game.py from {src}/game.tsv; do not edit',
'#pragma once',
@@ -166,4 +166,4 @@ if __name__ == '__main__':
elif len(a) == 2:
print(load(a[0]).get(a[1]))
else:
sys.exit(__doc__ or '用法 | usage: game.py list | game.py <id> <key> | game.py header <id> <out.h> | game.py dir <id> | game.py link <id> <link>')
sys.exit(__doc__ or 'usage: game.py list | game.py <id> <key> | game.py header <id> <out.h> | game.py dir <id> | game.py link <id> <link>')
+1 -1
View File
@@ -10,7 +10,7 @@ For the build steps of the whole project see `README.md` and `setup.sh` in the r
- The game's main program imports only one NVN symbol, `nvnBootstrapLoader`; all other NVN functions are obtained through it by name. The host forcibly takes over this import (`nvn/hle_nvn.c`), and not a single instruction of the original graphics driver is executed.
- The game fetches the addresses of 523 names in total (`nvn/names.txt`). Each name has a thunk (`nvn/nvn_thunks.S`, a generated file): it first increments that function's call count and then jumps to the implementation.
- Of these, the 216 that have call sites in the game's main program (`nvn/nvn_slots.tsv`) all have implementations: 203 are implemented according to NVN semantics, and 13 are empty implementations (`g_nops` in `hle_nvn.c`). The remaining 307 are connected to a red-reporting stub that, when called, prints `【红】NVN 未实现被调用:名字` in the log ("[red] unimplemented NVN function called: name").
- Of these, the 216 that have call sites in the game's main program (`nvn/nvn_slots.tsv`) all have implementations: 203 are implemented according to NVN semantics, and 13 are empty implementations (`g_nops` in `hle_nvn.c`). The remaining 307 are connected to a red-reporting stub that, when called, prints `[RED] NVN unimplemented function called: <name>` in the log.
- Every implementation gets a compile-time signature check against `nvn/nvn_protos.h` (a generated file). The calling conventions of Apple arm64 and the Switch differ for integer parameters narrower than 32 bits, so on the host side `NVNboolean` parameters are always received as `uint32_t` and only the low 8 bits are examined.
### Two layers: record and replay
+3 -3
View File
@@ -8,10 +8,10 @@ typedef void *(*nv_realloc_fn)(void *p, unsigned long size, void *user);
struct nv_allocator { nv_alloc_fn alloc; nv_free_fn free; nv_realloc_fn realloc; void *user; };
struct nv_allocator g_nv_alloc, g_nv_devtools_alloc;
#define WHY "NVN 已整块换成宿主空设备,驱动胶水层无事可做"
HLE_FORCE("_ZN2nv20SetGraphicsAllocatorEPFPvmmS0_EPFvS0_S0_EPFS0_S0_mS0_ES0_", WHY ";分配器记入 g_nv_alloc",
#define WHY "NVN has been replaced wholesale with a host null device, so the driver glue layer has nothing to do"
HLE_FORCE("_ZN2nv20SetGraphicsAllocatorEPFPvmmS0_EPFvS0_S0_EPFS0_S0_mS0_ES0_", WHY "; allocator recorded in g_nv_alloc",
void, h_nv_SetAlloc, nv_alloc_fn a, nv_free_fn f, nv_realloc_fn r, void *u) { g_nv_alloc = (struct nv_allocator){a, f, r, u}; }
HLE_FORCE("_ZN2nv28SetGraphicsDevtoolsAllocatorEPFPvmmS0_EPFvS0_S0_EPFS0_S0_mS0_ES0_", WHY ";分配器记入 g_nv_devtools_alloc",
HLE_FORCE("_ZN2nv28SetGraphicsDevtoolsAllocatorEPFPvmmS0_EPFvS0_S0_EPFS0_S0_mS0_ES0_", WHY "; allocator recorded in g_nv_devtools_alloc",
void, h_nv_SetDevAlloc, nv_alloc_fn a, nv_free_fn f, nv_realloc_fn r, void *u) { g_nv_devtools_alloc = (struct nv_allocator){a, f, r, u}; }
HLE_FORCE("_ZN2nv18InitializeGraphicsEPvm", WHY, void, h_nv_Init, void *mem, unsigned long size) { (void)mem; (void)size; }
HLE_FORCE("_ZN2nv16FinalizeGraphicsEv", WHY, void, h_nv_Fin, void) {}
+10 -10
View File
@@ -6,14 +6,14 @@
static struct { uint32_t magic; } g_display = {0x50534944}, g_layer = {0x5259414c};
static struct { uint32_t magic; void *layer; } g_native = {0x4e49574e, 0};
HLE_FORCE("_ZN2nn2vi10InitializeEv", "显示服务初始化;宿主无 vi 服务", void, h_vi_Init, void) {}
HLE_FORCE("_ZN2nn2vi18OpenDefaultDisplayEPPNS0_7DisplayE", "开默认显示;宿主给静态对象", Result, h_vi_OpenDisp, void **out) {
HLE_FORCE("_ZN2nn2vi10InitializeEv", "display service init; the host has no vi service", void, h_vi_Init, void) {}
HLE_FORCE("_ZN2nn2vi18OpenDefaultDisplayEPPNS0_7DisplayE", "open default display; the host returns a static object", Result, h_vi_OpenDisp, void **out) {
*out = &g_display; return RES_OK;
}
HLE_FORCE("_ZN2nn2vi11CreateLayerEPPNS0_5LayerEPNS0_7DisplayE", "建图层;宿主给静态对象", Result, h_vi_CreateLayer, void **out, void *disp) {
HLE_FORCE("_ZN2nn2vi11CreateLayerEPPNS0_5LayerEPNS0_7DisplayE", "create layer; the host returns a static object", Result, h_vi_CreateLayer, void **out, void *disp) {
(void)disp; *out = &g_layer; return RES_OK;
}
HLE_FORCE("_ZN2nn2vi15GetNativeWindowEPPvPNS0_5LayerE", "原生窗口句柄,交给 NVN 窗口 builder;宿主给静态对象", Result, h_vi_GetNative, void **out, void *layer) {
HLE_FORCE("_ZN2nn2vi15GetNativeWindowEPPvPNS0_5LayerE", "native window handle, passed to the NVN window builder; the host returns a static object", Result, h_vi_GetNative, void **out, void *layer) {
(void)layer; *out = &g_native; return RES_OK;
}
// Link's Awakening also crops its layer and tears the display down on exit; the SDK's versions dereference the vi service objects the host never made (SetLayerCrop: SIGSEGV at +0x64).
@@ -21,16 +21,16 @@ HLE_FORCE("_ZN2nn2vi15GetNativeWindowEPPvPNS0_5LayerE", "原生窗口句柄,
// to that every frame. NVN games crop through their window instead (nvnWindowSetCrop), so their present does not read it. Logged only when it changes
static _Atomic uint64_t g_crop; // x | y << 16 | w << 32 | h << 48, 0 = none
void dm_vi_layer_crop(int c[4]) { uint64_t v = g_crop; c[0] = (int)(v & 0xffff); c[1] = (int)(v >> 16 & 0xffff); c[2] = (int)(v >> 32 & 0xffff); c[3] = (int)(v >> 48); }
HLE_FORCE("_ZN2nn2vi12SetLayerCropEPNS0_5LayerEiiii", "图层裁切;宿主记下,Vulkan 出图按它裁", Result, h_vi_SetCrop, void *layer, int x, int y, int w, int h) {
HLE_FORCE("_ZN2nn2vi12SetLayerCropEPNS0_5LayerEiiii", "layer crop; the host records it and the Vulkan present crops by it", Result, h_vi_SetCrop, void *layer, int x, int y, int w, int h) {
(void)layer;
uint64_t v = x < 0 || y < 0 || w <= 0 || h <= 0 || x > 0xffff || y > 0xffff || w > 0xffff || h > 0xffff ? 0 : (uint64_t)x | (uint64_t)y << 16 | (uint64_t)w << 32 | (uint64_t)h << 48;
if (atomic_exchange(&g_crop, v) != v) LOG("vi::SetLayerCrop %d,%d %dx%d", x, y, w, h);
return RES_OK;
}
// Super Mario Odyssey sets its layer's scaling mode; the SDK's version uses the vi service objects the host never made
HLE_FORCE("_ZN2nn2vi19SetLayerScalingModeEPNS0_5LayerENS0_11ScalingModeE", "图层缩放方式;宿主出图自己缩放", Result, h_vi_SetScaling, void *layer, int mode) {
(void)layer; LOG("vi::SetLayerScalingMode %d(忽略)", mode); return RES_OK;
HLE_FORCE("_ZN2nn2vi19SetLayerScalingModeEPNS0_5LayerENS0_11ScalingModeE", "layer scaling mode; the host present does its own scaling", Result, h_vi_SetScaling, void *layer, int mode) {
(void)layer; LOG("vi::SetLayerScalingMode %d (ignored)", mode); return RES_OK;
}
HLE_FORCE("_ZN2nn2vi12DestroyLayerEPNS0_5LayerE", "销毁图层;宿主静态对象", void, h_vi_DestroyLayer, void *layer) { (void)layer; }
HLE_FORCE("_ZN2nn2vi12CloseDisplayEPNS0_7DisplayE", "关显示;宿主静态对象", void, h_vi_CloseDisp, void *disp) { (void)disp; }
HLE_FORCE("_ZN2nn2vi8FinalizeEv", "显示服务收尾;宿主无 vi 服务", void, h_vi_Fini, void) {}
HLE_FORCE("_ZN2nn2vi12DestroyLayerEPNS0_5LayerE", "destroy layer; host static object", void, h_vi_DestroyLayer, void *layer) { (void)layer; }
HLE_FORCE("_ZN2nn2vi12CloseDisplayEPNS0_7DisplayE", "close display; host static object", void, h_vi_CloseDisp, void *disp) { (void)disp; }
HLE_FORCE("_ZN2nn2vi8FinalizeEv", "display service shutdown; the host has no vi service", void, h_vi_Fini, void) {}
+5 -5
View File
@@ -33,7 +33,7 @@
#define RT(p) ((__bridge MTLBRt *)(void *)(p))
static void err(const char *where, NSError *e) {
fprintf(stderr, "[mtlb] %s 失败%s%s\n", where, e ? ":" : "",
fprintf(stderr, "[mtlb] %s failed%s%s\n", where, e ? ": " : "",
e ? e.localizedDescription.UTF8String : "");
}
@@ -48,7 +48,7 @@ mtlb_ctx *mtlb_init(void) {
@autoreleasepool {
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
if (!dev) { err("MTLCreateSystemDefaultDevice", nil); return NULL; }
if (!dev.hasUnifiedMemory) { err("需要统一内存设备(Shared 渲染目标)", nil); return NULL; }
if (!dev.hasUnifiedMemory) { err("unified-memory device check (needed for Shared render targets)", nil); return NULL; }
MTLBCtx *c = [MTLBCtx new];
c.dev = dev;
c.queue = [dev newCommandQueue];
@@ -80,7 +80,7 @@ static MTLPixelFormat pixfmt(mtlb_fmt f) {
mtlb_rt *mtlb_target(mtlb_ctx *ctx, uint32_t w, uint32_t h, mtlb_fmt fmt) {
@autoreleasepool {
MTLPixelFormat pf = pixfmt(fmt);
if (!ctx || !w || !h || pf == MTLPixelFormatInvalid) { err("mtlb_target 参数", nil); return NULL; }
if (!ctx || !w || !h || pf == MTLPixelFormatInvalid) { err("mtlb_target argument check", nil); return NULL; }
MTLTextureDescriptor *d =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pf width:w height:h mipmapped:NO];
d.storageMode = MTLStorageModeShared;
@@ -184,13 +184,13 @@ int mtlb_readback(mtlb_ctx *ctx, mtlb_rt *rt, void *buf, size_t buf_size) {
MTLBCtx *c = CTX(ctx);
MTLBRt *r = RT(rt);
size_t row = (size_t)r.w * 4;
if (buf_size < row * r.h) { err("mtlb_readback 缓冲太小", nil); return -1; }
if (buf_size < row * r.h) { err("mtlb_readback (buffer too small)", nil); return -1; }
if (c.frame) {
id<MTLCommandBuffer> cb = c.frame;
c.frame = nil;
[cb commit];
[cb waitUntilCompleted];
if (cb.status != MTLCommandBufferStatusCompleted) { err("commandBuffer 执行", cb.error); return -1; }
if (cb.status != MTLCommandBufferStatusCompleted) { err("commandBuffer execution", cb.error); return -1; }
}
[r.tex getBytes:buf bytesPerRow:row fromRegion:MTLRegionMake2D(0, 0, r.w, r.h) mipmapLevel:0];
return 0;
+2 -2
View File
@@ -15,7 +15,7 @@ int main(void) {
id<MTLBuffer> A = [dev newBufferWithBytesNoCopy:DOWN(a0) length:UP(a1) - DOWN(a0) options:MTLResourceStorageModeShared deallocator:nil];
id<MTLBuffer> B = [dev newBufferWithBytesNoCopy:DOWN(b0) length:UP(b1) - DOWN(b0) options:MTLResourceStorageModeShared deallocator:nil];
id<MTLBuffer> bad = [dev newBufferWithBytesNoCopy:a0 length:pg options:MTLResourceStorageModeShared deallocator:nil];
printf("页大小 %zu;A %s B %s;未对齐起点建缓冲 %s\n", pg, A ? "成功" : "失败", B ? "成功" : "失败", bad ? "成功(与预期不符)" : "失败(符合预期)");
printf("page size %zu; A %s B %s; buffer at an unaligned start %s\n", pg, A ? "ok" : "failed", B ? "ok" : "failed", bad ? "ok (not as expected)" : "failed (as expected)");
// GPU: copy 4096 bytes from a0 in A to b0 in B (of the two blocks' landing points, one is outside and one is inside the overlapping pages)
for (int i = 0; i < 4096; i++) a0[i] = (uint8_t)(i * 7 + 3);
id<MTLCommandQueue> q = [dev newCommandQueue];
@@ -29,6 +29,6 @@ int main(void) {
for (int i = 0; i < 256; i++) ok2 &= b0[0x1000 + i] == 0xAB;
uint8_t *viaA = (uint8_t *)A.contents + (b0 + 0x1000 - DOWN(a0));
int ok3 = viaA[0] == 0xAB && viaA[255] == 0xAB;
printf("GPU 拷贝 A→B 4096 B 全等:期望 1 → 真结果 %d\nGPU 填充经 CPU 原指针读到:期望 1 → 真结果 %d\n同一字节经 A 的 contents 读到:期望 1 → 真结果 %d\n", ok1, ok2, ok3);
printf("GPU copy A→B 4096 B identical: expected 1 → got %d\nGPU fill seen through the original CPU pointer: expected 1 → got %d\nsame bytes seen through A's contents: expected 1 → got %d\n", ok1, ok2, ok3);
return !(A && B && !bad && ok1 && ok2 && ok3);
}
+19 -19
View File
@@ -36,8 +36,8 @@ static int near(px_t a, px_t b, int tol) {
}
static void report(const char *name, px_t want, px_t got, int ok) {
printf(" %-34s 期望 (%3d,%3d,%3d,%3d) → 真结果 (%3d,%3d,%3d,%3d) %s\n", name,
want.r, want.g, want.b, want.a, got.r, got.g, got.b, got.a, ok ? "过" : "【不符】");
printf(" %-34s expected (%3d,%3d,%3d,%3d) → got (%3d,%3d,%3d,%3d) %s\n", name,
want.r, want.g, want.b, want.a, got.r, got.g, got.b, got.a, ok ? "pass" : "[MISMATCH]");
if (!ok) fails++;
}
@@ -73,11 +73,11 @@ static px_t tri_expect(int i, int j) {
static const char *fmt_name(mtlb_fmt f) { return f == MTLB_FMT_BGRA8 ? "BGRA8" : "RGBA8"; }
static void test_clear(mtlb_ctx *ctx, mtlb_fmt fmt, uint8_t *buf) {
printf("[测试1 清屏纯红 %s %dx%d]\n", fmt_name(fmt), W, H);
printf("[test 1: clear to pure red %s %dx%d]\n", fmt_name(fmt), W, H);
mtlb_rt *rt = mtlb_target(ctx, W, H, fmt);
memset(buf, 0x5a, W * H * 4); /* Fill with garbage first, to prevent "passes even though nothing was written" */
if (!rt || mtlb_clear(ctx, rt, 1, 0, 0, 1) || mtlb_readback(ctx, rt, buf, W * H * 4)) {
printf(" 后端调用失败\n"); fails++; mtlb_target_free(rt); return;
printf(" backend call failed\n"); fails++; mtlb_target_free(rt); return;
}
px_t want = {255, 0, 0, 255};
int bad = 0; px_t first_bad = {0};
@@ -86,26 +86,26 @@ static void test_clear(mtlb_ctx *ctx, mtlb_fmt fmt, uint8_t *buf) {
px_t g = get_px(buf, fmt, x, y);
if (!near(g, want, 0)) { if (!bad) first_bad = g; bad++; }
}
printf(" 全部 %d 像素逐个比:期望 (255,0,0,255) 全等 → 真结果 不符 %d 个%s\n", W * H, bad,
bad ? "【不符】" : " 过");
if (bad) { printf(" 首个不符像素 (%d,%d,%d,%d)\n", first_bad.r, first_bad.g, first_bad.b, first_bad.a); fails++; }
printf(" all %d pixels compared one by one: expected all (255,0,0,255) → got %d mismatched%s\n", W * H, bad,
bad ? " [MISMATCH]" : " pass");
if (bad) { printf(" first mismatched pixel (%d,%d,%d,%d)\n", first_bad.r, first_bad.g, first_bad.b, first_bad.a); fails++; }
mtlb_target_free(rt);
}
static void test_triangle(mtlb_ctx *ctx, mtlb_fmt fmt, uint8_t *buf) {
printf("[测试2 黑底三色三角形 %s %dx%d,清屏+三角形两个 pass 同一帧]\n", fmt_name(fmt), W, H);
printf("[test 2: three-color triangle on black %s %dx%d, clear + triangle as two passes in one frame]\n", fmt_name(fmt), W, H);
mtlb_rt *rt = mtlb_target(ctx, W, H, fmt);
memset(buf, 0x5a, W * H * 4);
if (!rt || mtlb_clear(ctx, rt, 0, 0, 0, 1) || mtlb_triangle(ctx, rt) ||
mtlb_readback(ctx, rt, buf, W * H * 4)) {
printf(" 后端调用失败\n"); fails++; mtlb_target_free(rt); return;
printf(" backend call failed\n"); fails++; mtlb_target_free(rt); return;
}
px_t clear = {0, 0, 0, 255};
/* 1) The four corner points = clear color */
const int corners[4][2] = {{0, 0}, {W - 1, 0}, {0, H - 1}, {W - 1, H - 1}};
for (int k = 0; k < 4; k++) {
char name[64]; snprintf(name, sizeof name, "角点 (%d,%d) 清屏色", corners[k][0], corners[k][1]);
char name[64]; snprintf(name, sizeof name, "corner (%d,%d) clear color", corners[k][0], corners[k][1]);
px_t g = get_px(buf, fmt, corners[k][0], corners[k][1]);
report(name, clear, g, near(g, clear, 0));
}
@@ -113,14 +113,14 @@ static void test_triangle(mtlb_ctx *ctx, mtlb_fmt fmt, uint8_t *buf) {
/* 2) Centroid: NDC (0,-1/6) → pixel (128, 149.33); take pixel (128,149), which is not the clear color and ≈ the interpolated value */
{
px_t want = tri_expect(128, 149), g = get_px(buf, fmt, 128, 149);
report("重心 (128,149) 非清屏、≈1/3 混色", want, g, !near(g, clear, 0) && near(g, want, TOL));
report("centroid (128,149) not clear, ≈1/3 mix", want, g, !near(g, clear, 0) && near(g, want, TOL));
}
/* 3) Near the three vertices: vertex pixels v0(128,64) v1(64,192) v2(192,192), each sampled a few pixels inward toward the centroid */
const struct { int x, y, ch; const char *name; } vs[3] = {
{128, 72, 0, "近上顶点 (128,72) 偏红"},
{72, 188, 1, "近左下顶点 (72,188) 偏绿"},
{183, 188, 2, "近右下顶点 (183,188) 偏蓝"},
{128, 72, 0, "near top vertex (128,72) reddish"},
{72, 188, 1, "near bottom-left vertex (72,188) greenish"},
{183, 188, 2, "near bottom-right vertex (183,188) bluish"},
};
for (int k = 0; k < 3; k++) {
px_t want = tri_expect(vs[k].x, vs[k].y), g = get_px(buf, fmt, vs[k].x, vs[k].y);
@@ -141,22 +141,22 @@ static void test_triangle(mtlb_ctx *ctx, mtlb_fmt fmt, uint8_t *buf) {
else if (dmin < -1.0) { n_out++; if (!near(g, clear, 0)) bad_out++; }
else n_edge++;
}
printf(" 全图内侧 %d 像素:期望 ≈插值(±%d) → 真结果 不符 %d 个%s\n", n_in, TOL, bad_in, bad_in ? "【不符】" : " 过");
printf(" 全图外侧 %d 像素:期望 =清屏色 → 真结果 不符 %d 个%s\n", n_out, bad_out, bad_out ? "【不符】" : " 过");
printf(" 边带跳过 %d 像素(合计 %d)\n", n_edge, n_in + n_out + n_edge);
printf(" whole image, inside %d pixels: expected ≈interpolated(±%d) → got %d mismatched%s\n", n_in, TOL, bad_in, bad_in ? " [MISMATCH]" : " pass");
printf(" whole image, outside %d pixels: expected =clear color → got %d mismatched%s\n", n_out, bad_out, bad_out ? " [MISMATCH]" : " pass");
printf(" edge band skipped %d pixels (total %d)\n", n_edge, n_in + n_out + n_edge);
if (bad_in || bad_out) fails++;
mtlb_target_free(rt);
}
int main(void) {
mtlb_ctx *ctx = mtlb_init();
if (!ctx) { printf("mtlb_init 失败\n"); return 1; }
if (!ctx) { printf("mtlb_init failed\n"); return 1; }
uint8_t *buf = malloc(W * H * 4);
const mtlb_fmt fmts[2] = {MTLB_FMT_BGRA8, MTLB_FMT_RGBA8};
for (int i = 0; i < 2; i++) test_clear(ctx, fmts[i], buf);
for (int i = 0; i < 2; i++) test_triangle(ctx, fmts[i], buf);
free(buf);
mtlb_shutdown(ctx);
printf("%s:不符项 %d\n", fails ? "【失败】" : "全部通过", fails);
printf("%s: mismatches %d\n", fails ? "[FAILED]" : "all passed", fails);
return fails ? 1 : 0;
}
+110 -110
View File
@@ -38,7 +38,7 @@
// ---------- Slot table, thunks, unimplemented stubs ----------
typedef struct { _Atomic uint64_t cnt; void *fn; } nvn_slot_t;
nvn_slot_t nvn_slot[NVN_NFUNC]; // The thunk accesses it by i*16; the layout must not change
_Static_assert(sizeof(nvn_slot_t) == 16, "跳板按 16 字节步长寻址");
_Static_assert(sizeof(nvn_slot_t) == 16, "the trampolines are addressed in 16-byte steps");
extern void *nvn_thunk[NVN_NFUNC];
extern const char *nvn_names[NVN_NFUNC];
extern void nvn_unimpl_entry(void);
@@ -51,7 +51,7 @@ static uint8_t g_unimpl_seen[NVN_NFUNC];
uint64_t nvn_unimpl_c(nvn_slot_t *slot, uint64_t x0, uint64_t x1, uint64_t x2) {
unsigned i = (unsigned)(slot - nvn_slot);
if (!g_unimpl_seen[i]++) LOG("【红】NVN 未实现被调用:%s (x0=%#llx x1=%#llx x2=%#llx)", nvn_names[i], x0, x1, x2);
if (!g_unimpl_seen[i]++) LOG("[RED] NVN unimplemented function called: %s (x0=%#llx x1=%#llx x2=%#llx)", nvn_names[i], x0, x1, x2);
return 0;
}
@@ -82,7 +82,7 @@ static void nvn_init_slots(void) {
for (unsigned k = 0; k < nim; k++) {
unsigned i = 0;
while (i < NVN_NFUNC && strcmp(nvn_names[i], im[k].name)) i++;
if (i == NVN_NFUNC) die("NVN 实现 %s 不在 main 的名字表里", im[k].name);
if (i == NVN_NFUNC) die("NVN implementation %s is not in main's name table", im[k].name);
nvn_slot[i].fn = im[k].fn; hit++;
}
g_nsem = hit;
@@ -92,12 +92,12 @@ static void *nvn_lookup(const char *name) {
nvn_init_slots();
for (unsigned i = 0; i < NVN_NFUNC; i++)
if (!strcmp(nvn_names[i], name)) return nvn_thunk[i];
LOG("【红】NVN 取址未知名字:%s", name ? name : "(null)");
LOG("[RED] NVN address lookup of an unknown name: %s", name ? name : "(null)");
return NULL;
}
// nvnBootstrapLoader is imported by main (defined in sdk); after it is forcibly taken over, the NVN driver in sdk is no longer referenced
HLE_FORCE("nvnBootstrapLoader", "NVN 图形接口整块换成宿主空设备(后续 Metal),sdk 的 NVN 驱动不执行",
HLE_FORCE("nvnBootstrapLoader", "the NVN graphics API is replaced wholesale with a host null device (Metal behind it); the SDK's NVN driver does not run",
void *, h_nvnBootstrapLoader, const char *name) { return nvn_lookup(name); }
// ---------- Boolean parameters: Switch callers do not extend them, only look at the low 8 bits ----------
@@ -143,7 +143,7 @@ NVN(nvnDeviceGetInteger, void, const NVNdevice *d, NVNdeviceInfo what, int *out)
(void)d;
unsigned w = (unsigned)what;
// 0x5F separate sampler binding count and 0x60/0x61 are not in the table measured on SMO: 0x5F is given 4096 per the sampler pool limit, unverified; 0x60/0x61 report a failure and return 0
if (w >= sizeof g_devinfo / sizeof *g_devinfo) { LOG("【红】nvnDeviceGetInteger 未知项 %#x,给 0", w); *out = 0; return; }
if (w >= sizeof g_devinfo / sizeof *g_devinfo) { LOG("[RED] nvnDeviceGetInteger unknown item %#x, returning 0", w); *out = 0; return; }
*out = g_devinfo[w];
if (w <= 1) { static int api[2] = {-1, -1}; if (api[0] < 0) { const char *e = getenv("DM_NVN_API"); api[0] = g_devinfo[0]; api[1] = g_devinfo[1]; if (e) sscanf(e, "%d.%d", &api[0], &api[1]); } *out = api[w]; }
OLOG("DeviceGetInteger %#x -> %d", w, *out);
@@ -155,8 +155,8 @@ NVN(nvnDeviceGetSeparateSamplerHandle, NVNseparateSamplerHandle, const NVNdevice
NVN(nvnDeviceInstallDebugCallback, void, NVNdevice *d, const PFNNVNDEBUGCALLBACKPROC cb, void *data, uint32_t enable) {
dev_t_ *o = (dev_t_ *)d; o->dbg_cb = B(enable) ? (void *)cb : NULL; o->dbg_data = data;
}
NVN(nvnDeviceSetWindowOriginMode, void, NVNdevice *d, NVNwindowOriginMode m) { ((dev_t_ *)d)->origin = m; mtl_set_origin_lower_left(m == NVN_WINDOW_ORIGIN_MODE_LOWER_LEFT); LOG("[nvn] 窗口原点 %s", m ? "左上(同 D3D / Metal)" : "左下(GL)"); }
NVN(nvnDeviceSetDepthMode, void, NVNdevice *d, NVNdepthMode m) { ((dev_t_ *)d)->depth_mode = m; mtl_set_depth_zero(m != 0); LOG("[nvn] 深度模式 %s", m ? "近=0(同 D3D / Metal)" : "近=-w(GL)"); }
NVN(nvnDeviceSetWindowOriginMode, void, NVNdevice *d, NVNwindowOriginMode m) { ((dev_t_ *)d)->origin = m; mtl_set_origin_lower_left(m == NVN_WINDOW_ORIGIN_MODE_LOWER_LEFT); LOG("[nvn] window origin %s", m ? "upper left (as D3D / Metal)" : "lower left (GL)"); }
NVN(nvnDeviceSetDepthMode, void, NVNdevice *d, NVNdepthMode m) { ((dev_t_ *)d)->depth_mode = m; mtl_set_depth_zero(m != 0); LOG("[nvn] depth mode %s", m ? "near=0 (as D3D / Metal)" : "near=-w (GL)"); }
NVN(nvnDeviceFinalizeCommandHandle, void, NVNdevice *d, NVNcommandHandle h) { (void)d; (void)h; }
// ---------- Memory pools ----------
@@ -168,7 +168,7 @@ NVN(nvnMemoryPoolInitialize, NVNboolean, NVNmemoryPool *p, const NVNmemoryPoolBu
const pool_builder_t *s = (const pool_builder_t *)b; pool_t *o = (pool_t *)p;
o->magic = MAGIC('P', 'O', 'O', 'L'); o->flags = s->flags; o->mem = s->mem; o->size = s->size;
if (o->mem) { mtl_pool_add(o->mem, o->size); nvn_dirty_add_range(o->mem, o->size); }
if (!o->mem && !(o->flags & 0x200)) LOG("【红】MemoryPoolInitialize 非虚拟池没有存储 flags=%#x size=%#llx", o->flags, o->size);
if (!o->mem && !(o->flags & 0x200)) LOG("[RED] MemoryPoolInitialize: non-virtual pool has no storage flags=%#x size=%#llx", o->flags, o->size);
if (getenv("DM_NVN_VLOG")) { LOG("[nvn] MemoryPool %p mem=%p size=%#llx flags=%#x", (void *)p, (void *)o->mem, (unsigned long long)o->size, o->flags); void dm_backtrace(uint64_t fp); dm_backtrace((uint64_t)__builtin_frame_address(0)); }
OLOG("MemoryPool %p mem=%p size=%#llx flags=%#x", (void *)p, (void *)o->mem, o->size, o->flags);
return 1;
@@ -194,9 +194,9 @@ static int dpool_add(struct dpool_base *t, int *nt, int *next, const void *pool,
pthread_mutex_lock(&g_dpool_mu);
int i = 0; while (i < *nt && t[i].pool != pool) i++;
if (i == *nt) {
if (*nt == NDPOOL) { pthread_mutex_unlock(&g_dpool_mu); LOG("【红】%s池超过 %d 个:后来的与第一个共用编号", kind, NDPOOL); return 0; }
if (*nt == NDPOOL) { pthread_mutex_unlock(&g_dpool_mu); LOG("[RED] %s pools exceed %d: later ones share the first pool's numbering", kind, NDPOOL); return 0; }
t[i] = (struct dpool_base){pool, *next}; (*nt)++; *next += (n + 255) & ~255;
if (i) LOG("[nvn] 第 %d 个%s池(%d 项):编号在宿主表里从 %d 起,与前面的池分开", i + 1, kind, n, t[i].base);
if (i) LOG("[nvn] pool #%d (%s, %d entries): numbered from %d in the host table, separate from the earlier pools", i + 1, kind, n, t[i].base);
}
int b = t[i].base; pthread_mutex_unlock(&g_dpool_mu); return b;
}
@@ -208,12 +208,12 @@ static void cb_pool_set(const void *c, int tex, int base) {
int i = 0; while (i < g_ncbpool && g_cbpool[i].cmd != c) i++;
if (i == g_ncbpool && base && g_ncbpool < 64) g_cbpool[g_ncbpool++] = (typeof(g_cbpool[0])){c, 0, 0};
if (i < g_ncbpool) { if (tex) g_cbpool[i].tb = base; else g_cbpool[i].sb = base; }
else if (base) LOG("【红】绑了非首个描述符池的命令缓冲超过 64 个:多出的按首个池算");
else if (base) LOG("[RED] more than 64 command buffers bound a non-first descriptor pool: the extra ones count as the first pool");
pthread_mutex_unlock(&g_dpool_mu);
}
NVN(nvnTexturePoolInitialize, NVNboolean, NVNtexturePool *tp, const NVNmemoryPool *p, ptrdiff_t off, int n) {
*(dpool_t *)tp = (dpool_t){(const pool_t *)p, off, n}; OLOG("TexturePool n=%d", n);
dpool_add(g_tpb, &g_ntpb, &g_tnext, tp, n, "贴图"); mtl_set_tex_pool_n(g_tnext);
dpool_add(g_tpb, &g_ntpb, &g_tnext, tp, n, "texture"); mtl_set_tex_pool_n(g_tnext);
return 1;
}
NVN(nvnTexturePoolFinalize, void, NVNtexturePool *tp) { (void)tp; }
@@ -226,7 +226,7 @@ NVN(nvnTexturePoolRegisterImage, void, const NVNtexturePool *tp, int id, const N
mtl_reg_tex(dpool_base(g_tpb, g_ntpb, tp) + id, (const tex_t *)t, (const view_t *)v);
}
NVN(nvnSamplerPoolInitialize, NVNboolean, NVNsamplerPool *sp, const NVNmemoryPool *p, ptrdiff_t off, int n) {
*(dpool_t *)sp = (dpool_t){(const pool_t *)p, off, n}; OLOG("SamplerPool n=%d", n); dpool_add(g_spb, &g_nspb, &g_snext, sp, n, "采样器"); return 1;
*(dpool_t *)sp = (dpool_t){(const pool_t *)p, off, n}; OLOG("SamplerPool n=%d", n); dpool_add(g_spb, &g_nspb, &g_snext, sp, n, "sampler"); return 1;
}
NVN(nvnSamplerPoolFinalize, void, NVNsamplerPool *sp) { (void)sp; }
NVN(nvnSamplerPoolRegisterSampler, void, const NVNsamplerPool *sp, int id, const NVNsampler *s) { mtl_reg_samp(dpool_base(g_spb, g_nspb, sp) + id, &((const samp_t *)s)->p); }
@@ -257,13 +257,13 @@ NVN(nvnTextureBuilderGetFlags, NVNtextureFlags, const NVNtextureBuilder *b) { re
static uint64_t g_layout_logged;
static void log_layout(const tex_builder_t *b, const nvn_texlayout *L) {
if (b->has_layout && g_layout_logged++ < 16)
LOG("[nvn] 预排布贴图 %ux%ux%u 级%u 格式%#x 目标%d 布局字节 %02x%02x%02x%02x%02x%02x%02x%02x → 块高 log2=%u(取布局第 0 字节)尺寸 %#llx",
LOG("[nvn] packaged texture %ux%ux%u levels %u format %#x target %d layout bytes %02x%02x%02x%02x%02x%02x%02x%02x → block height log2=%u (from layout byte 0) size %#llx",
b->d.width, b->d.height, b->d.depth, b->d.levels, b->d.format, b->d.target, b->layout[0], b->layout[1], b->layout[2],
b->layout[3], b->layout[4], b->layout[5], b->layout[6], b->layout[7], L->tileH, L->size);
}
NVN(nvnTextureBuilderGetStorageSize, size_t, const NVNtextureBuilder *b) {
nvn_texlayout L; nvn_tex_layout(&((const tex_builder_t *)b)->d, &L);
if (!L.bpb) LOG("【红】TextureBuilderGetStorageSize 未知格式 %#x", ((const tex_builder_t *)b)->d.format);
if (!L.bpb) LOG("[RED] TextureBuilderGetStorageSize unknown format %#x", ((const tex_builder_t *)b)->d.format);
log_layout((const tex_builder_t *)b, &L);
return L.size;
}
@@ -284,7 +284,7 @@ static void ft_add(const tex_t *t) {
if (!DM_GAME_TEX_FLUSH_UPLOAD || !t->mem || !t->pool || t->has_layout || t->d.target == NVN_TEXTURE_TARGET_BUFFER || (t->pool->flags & NVN_MEMORY_POOL_FLAGS_CPU_NO_ACCESS) ||
(t->d.flags & (NVN_TEXTURE_FLAGS_LINEAR | NVN_TEXTURE_FLAGS_LINEAR_RENDER_TARGET | NVN_TEXTURE_FLAGS_COMPRESSIBLE))) return;
pthread_mutex_lock(&g_ft_mu);
if (g_nft == g_cft && !(g_ft = realloc(g_ft, (g_cft = g_cft ? 2 * g_cft : 1024) * sizeof *g_ft))) die("flush_tex 分配失败");
if (g_nft == g_cft && !(g_ft = realloc(g_ft, (g_cft = g_cft ? 2 * g_cft : 1024) * sizeof *g_ft))) die("flush_tex allocation failed");
uint64_t st = nvn_dirty_now(); if (DM_GAME_TEX_DIRTY_UPLOAD) nvn_dirty_protect(t->mem, t->L.size); // see nvn_ft_written
g_ft[g_nft++] = (flush_tex){t->mem, t->L.size, *t, st, nvn_dirty_span(t->mem, t->L.size)};
pthread_mutex_unlock(&g_ft_mu);
@@ -327,7 +327,7 @@ void nvn_ft_poll(void) {
if (cur >= g_nft) cur = 0;
scanned += g_ft[cur].size; left--;
if (nvn_dirty_since_span(g_ft[cur].span, g_ft[cur].mem, g_ft[cur].size, g_ft[cur].stamp)) {
if (nhit == chit && !(hit = realloc(hit, (chit = chit ? 2 * chit : 64) * sizeof *hit))) die("nvn_ft_poll 分配失败");
if (nhit == chit && !(hit = realloc(hit, (chit = chit ? 2 * chit : 64) * sizeof *hit))) die("nvn_ft_poll allocation failed");
hit[nhit++] = g_ft[cur].t; g_ft[cur] = g_ft[--g_nft];
} else cur++;
}
@@ -345,7 +345,7 @@ NVN(nvnTextureInitialize, NVNboolean, NVNtexture *t, const NVNtextureBuilder *b)
const tex_builder_t *s = (const tex_builder_t *)b; tex_t *o = TX(t);
// Re-initializing a texture object that is still live (without a Finalize first): release the host texture of the old serial first. SMBW re-initializes its transient render targets on the same object every frame and never Finalizes them;
// previously every init created a new private MTLTexture and the old one stayed in the serial table forever, so GPU memory grew about 13 MB per frame (killed by the memory watchdog at 24 GB)
if (o->magic == MAGIC('T', 'E', 'X', 0) && o->serial) { static uint64_t n; if (!n++) LOG("[nvn] TextureInitialize 对仍有效的贴图对象再初始化(无 Finalize):放掉旧宿主贴图(首例 ser=%llu)", (unsigned long long)o->serial); mtl_tex_release(o); }
if (o->magic == MAGIC('T', 'E', 'X', 0) && o->serial) { static uint64_t n; if (!n++) LOG("[nvn] TextureInitialize on a texture object that is still live (no Finalize): releasing the old host texture (first case ser=%llu)", (unsigned long long)o->serial); mtl_tex_release(o); }
memset(o, 0, sizeof *o);
o->magic = MAGIC('T', 'E', 'X', 0); o->d = s->d; o->pool = s->pool; o->off = s->off;
o->has_layout = s->has_layout; memcpy(o->layout, s->layout, 8); o->set_swz = s->set_swz; o->swz = s->swz;
@@ -356,7 +356,7 @@ NVN(nvnTextureInitialize, NVNboolean, NVNtexture *t, const NVNtextureBuilder *b)
if (s->pkg_data && o->mem) {
memcpy(o->mem, s->pkg_data, o->L.size);
if (!(o->d.flags & (NVN_TEXTURE_FLAGS_LINEAR | NVN_TEXTURE_FLAGS_LINEAR_RENDER_TARGET))) mtl_tex_cpu_written(o);
else if (o->pool->flags & NVN_MEMORY_POOL_FLAGS_CPU_NO_ACCESS) { static int once_; if (!once_++) LOG("[nvn] 【红】线性贴图带预排布数据、池 CPU 不可见:宿主建私有贴图,数据不上传(%ux%u 格式 %#x)", o->d.width, o->d.height, o->d.format); }
else if (o->pool->flags & NVN_MEMORY_POOL_FLAGS_CPU_NO_ACCESS) { static int once_; if (!once_++) LOG("[nvn] [RED] linear texture with packaged data in a pool the CPU cannot see: the host creates a private texture and does not upload the data (%ux%u format %#x)", o->d.width, o->d.height, o->d.format); }
}
OLOG("Texture %p %ux%ux%u lv%u fmt=%#x tgt=%d flags=%#x size=%#llx", (void *)t, o->d.width, o->d.height, o->d.depth,
o->d.levels, o->d.format, o->d.target, o->d.flags, o->L.size);
@@ -493,7 +493,7 @@ int prog_key(const prog_t *p, unsigned st, uint8_t key[8]) {
}
NVN(nvnProgramSetShaders, NVNboolean, NVNprogram *p, int n, const NVNshaderData *sd) {
prog_t *o = (prog_t *)p;
if (n < 0 || n > 6) { LOG("【红】ProgramSetShaders 阶段数 %d", n); return 0; }
if (n < 0 || n > 6) { LOG("[RED] ProgramSetShaders stage count %d", n); return 0; }
mtl_prog_release(o); o->nstage = n; memcpy(o->sd, sd, n * sizeof *sd); o->serial = nvn_new_serial();
// The keys are computed here (see prog_key in nvn_obj.h): the control section is only guaranteed valid during this call; the code area lives in a memory pool and stays valid while the program is in use
static int dump = -1; if (dump < 0) dump = getenv("DM_NVN_SHDUMP") != NULL;
@@ -515,7 +515,7 @@ NVN(nvnSyncFinalize, void, NVNsync *s) { ((sync_t *)s)->magic = 0; }
NVN(nvnQueueFenceSync, void, NVNqueue *q, NVNsync *s, NVNsyncCondition c, int f) { (void)q; (void)c; (void)f; ((sync_t *)s)->fenced = 1; ((sync_t *)s)->fence = mtl_last_fence(); }
// A sync object that was never fenced waits until timeout, as on a real device; a fenced one is released only after the GPU has actually completed up to that fence value
// A GPU-side wait on a sync: the host has a single Metal queue that executes submissions in order, so commands before the fence are always done first and nothing needs waiting. A sync not fenced yet (fenced later by another thread) is not waited for here; logged once
NVN(nvnQueueWaitSync, NVNboolean, NVNqueue *q, const NVNsync *s) { (void)q; static int once; if (!((const sync_t *)s)->fenced && !once++) LOG("[nvn] QueueWaitSync:同步对象还没围栏,宿主不等"); return 1; }
NVN(nvnQueueWaitSync, NVNboolean, NVNqueue *q, const NVNsync *s) { (void)q; static int once; if (!((const sync_t *)s)->fenced && !once++) LOG("[nvn] QueueWaitSync: the sync object has no fence yet, the host does not wait"); return 1; }
// ZCull data: the host does no ZCull, so there is nothing to save / restore
NVN(nvnCommandBufferRestoreZCullData, void, NVNcommandBuffer *c, NVNbufferAddress a, size_t n) { (void)c; (void)a; (void)n; }
NVN(nvnCommandBufferSaveZCullData, void, NVNcommandBuffer *c, NVNbufferAddress a, size_t n) { (void)c; (void)a; (void)n; }
@@ -529,7 +529,7 @@ NVN(nvnEventBuilderSetDefaults, void, NVNeventBuilder *b) { memset(b, 0, sizeof(
NVN(nvnEventBuilderSetStorage, void, NVNeventBuilder *b, const NVNmemoryPool *p, int64_t off) { *(ev_builder_t *)b = (ev_builder_t){(const pool_t *)p, off}; }
NVN(nvnEventInitialize, NVNboolean, NVNevent *e, const NVNeventBuilder *b) {
const ev_builder_t *s = (const ev_builder_t *)b;
if (!s->pool || !s->pool->mem) { LOG("【红】EventInitialize 没有存储"); return 0; }
if (!s->pool || !s->pool->mem) { LOG("[RED] EventInitialize has no storage"); return 0; }
*(ev_t *)e = (ev_t){MAGIC('E', 'V', 'N', 'T'), (volatile uint32_t *)(s->pool->mem + s->off)}; return 1;
}
NVN(nvnEventFinalize, void, NVNevent *e) { ((ev_t *)e)->magic = 0; }
@@ -588,8 +588,8 @@ static void cs_dump(void) { // Called once per frame (at present)
if (!cs_on()) return; uint64_t f = dm_nvn_frame_count(); if (f - g_cs.frame0 < 300) return;
pthread_mutex_lock(&g_cs_mu); double n = (double)(f - g_cs.frame0); char top[700]; int k = 0; uint8_t used[64] = {0};
for (int r = 0; r < 8; r++) { int bi = -1; for (int i = 0; i < 64; i++) if (!used[i] && g_cs.op_b[i] && (bi < 0 || g_cs.op_b[i] > g_cs.op_b[bi])) bi = i; if (bi < 0) break; used[bi] = 1;
k += snprintf(top + k, sizeof top - k, " 命令%d:%.0f 条×%.0f 字节=%.1f KB;", bi, g_cs.op_n[bi] / n, (double)g_cs.op_b[bi] / (double)g_cs.op_n[bi], g_cs.op_b[bi] / n / 1024.0); }
LOG("[nvn] 命令内存 至帧 %llu:每帧录 %.1f KB / %.0f 条;游戏给段 每帧 %.2f 次,每段 均 %.1f KB(最小 %.1f / 最大 %.1f);补内存回调 每帧 %.2f 次;宿主补段 每帧 %.3f 次;换段丢弃的段尾 每帧 %.1f KB|每帧%s",
k += snprintf(top + k, sizeof top - k, " op %d: %.0f × %.0f bytes = %.1f KB;", bi, g_cs.op_n[bi] / n, (double)g_cs.op_b[bi] / (double)g_cs.op_n[bi], g_cs.op_b[bi] / n / 1024.0); }
LOG("[nvn] command memory up to frame %llu: recorded per frame %.1f KB / %.0f records; segments from the game per frame %.2f, mean %.1f KB each (min %.1f / max %.1f); out-of-memory callbacks per frame %.2f; host-lent segments per frame %.3f; segment tails dropped on a switch per frame %.1f KB | per frame%s",
(unsigned long long)f, g_cs.bytes / n / 1024.0, g_cs.recs / n, g_cs.grants / n, g_cs.grants ? g_cs.grant_bytes / (double)g_cs.grants / 1024.0 : 0, g_cs.grant_min / 1024.0, g_cs.grant_max / 1024.0, g_cs.cbs / n, g_cs.hostseg / n, g_cs.waste / n / 1024.0, top);
int on = g_cs.on; memset(&g_cs, 0, sizeof g_cs); g_cs.on = on; g_cs.frame0 = f; pthread_mutex_unlock(&g_cs_mu);
}
@@ -634,8 +634,8 @@ static uint8_t *hostseg_take(const cmd_t *o) {
}
unsigned t = ++total; int n = g_nhostseg;
pthread_mutex_unlock(&g_hostseg_mu);
if (!m) die("NVN 命令内存:游戏池已空,宿主补段已达 %u MB 仍未被游戏的块回收", HOSTSEG_CAP >> 20);
if (t == 1 || t % 1024 == 0) LOG("[nvn] 游戏的命令内存不够,宿主补段(累计 %u 次,帧 %llu,段池 %d 段 × %u KB)", t, (unsigned long long)f, n, HOSTSEG_SZ >> 10);
if (!m) die("NVN command memory: the game's pool is empty and host-lent segments reached %u MB without being reclaimed by the game's blocks", HOSTSEG_CAP >> 20);
if (t == 1 || t % 1024 == 0) LOG("[nvn] the game's command memory ran short, the host lends a segment (%u times so far, frame %llu, segment pool %d segments × %u KB)", t, (unsigned long long)f, n, HOSTSEG_SZ >> 10);
return m;
}
// The game gives out [lo, hi): segments whose recording's guest bytes overlap it are dead, reclaim them
@@ -647,10 +647,10 @@ static void hostseg_release(uintptr_t lo, uintptr_t hi) {
NVN(nvnCommandBufferAddCommandMemory, void, NVNcommandBuffer *c, const NVNmemoryPool *p, ptrdiff_t off, size_t n) {
const pool_t *pl = (const pool_t *)p;
if (!pl || n < 64) { if (cs_on()) { pthread_mutex_lock(&g_cs_mu); g_cs.hostseg++; pthread_mutex_unlock(&g_cs_mu); } cmd_new_segment(CB(c), hostseg_take(CB(c)), HOSTSEG_SZ); return; }
if (!pl->mem) { LOG("【红】AddCommandMemory 池没有 CPU 存储"); return; }
if (!pl->mem) { LOG("[RED] AddCommandMemory: pool has no CPU storage"); return; }
hostseg_release((uintptr_t)(pl->mem + off), (uintptr_t)(pl->mem + off + n));
if (cs_on()) { pthread_mutex_lock(&g_cs_mu); g_cs.grants++; g_cs.grant_bytes += n; if (!g_cs.grant_min || n < g_cs.grant_min) g_cs.grant_min = n; if (n > g_cs.grant_max) g_cs.grant_max = n; pthread_mutex_unlock(&g_cs_mu); }
{ static int cblog = -1; if (cblog < 0) cblog = getenv("DM_NVN_CBLOG") != NULL; static uint64_t ng; if (cblog && (++ng <= 60 || ng % 500 == 0)) LOG("[nvn] AddCommandMemory #%llu cmd=%p 池 %p +%#lx 长 %#zx(录制中 %d)", (unsigned long long)ng, (void *)c, (const void *)pl, (long)off, n, CB(c)->recording); }
{ static int cblog = -1; if (cblog < 0) cblog = getenv("DM_NVN_CBLOG") != NULL; static uint64_t ng; if (cblog && (++ng <= 60 || ng % 500 == 0)) LOG("[nvn] AddCommandMemory #%llu cmd=%p pool %p +%#lx length %#zx (recording %d)", (unsigned long long)ng, (void *)c, (const void *)pl, (long)off, n, CB(c)->recording); }
cmd_new_segment(CB(c), pl->mem + off, n);
CB(c)->used = 0; // GetCommandMemoryUsed counts from the start of the memory the game last added, across recordings (see there)
}
@@ -661,12 +661,12 @@ static uint8_t *cmd_alloc(cmd_t *o, uint32_t size) {
for (int tries = 0; !o->cur || o->cur + size + OP_RESERVE > o->end; tries++) {
// The game's callback cannot supply enough (newer-SDK games hand out small chunks sized for real-device commands, or disable the callback and manage memory themselves): continue recording in a host segment instead of aborting
if (!o->cb || tries == 2) {
static int once; if (!once++) LOG("[nvn] 命令内存:游戏回调补不够(cb=%p,需 %u 字节),改用宿主补段", (void *)o->cb, size + OP_RESERVE);
static int once; if (!once++) LOG("[nvn] command memory: the game's callback did not add enough (cb=%p, need %u bytes), using a host-lent segment", (void *)o->cb, size + OP_RESERVE);
cmd_new_segment(o, hostseg_take(o), HOSTSEG_SZ); break;
}
if (cs_on()) { pthread_mutex_lock(&g_cs_mu); g_cs.cbs++; pthread_mutex_unlock(&g_cs_mu); }
{ static int cblog = -1; if (cblog < 0) cblog = getenv("DM_NVN_CBLOG") != NULL; static uint64_t ncb;
if (cblog && (++ncb <= 40 || ncb % 1000 == 0)) LOG("[nvn] 命令内存回调 #%llu cmd=%p 需 %u(本段已用 %ld)", (unsigned long long)ncb, (void *)o, size + OP_RESERVE, o->cur && o->end ? (long)(o->cur - (o->end - 1)) : -1L); }
if (cblog && (++ncb <= 40 || ncb % 1000 == 0)) LOG("[nvn] command memory callback #%llu cmd=%p need %u (used in this segment %ld)", (unsigned long long)ncb, (void *)o, size + OP_RESERVE, o->cur && o->end ? (long)(o->cur - (o->end - 1)) : -1L); }
o->cb((NVNcommandBuffer *)o, NVN_COMMAND_BUFFER_MEMORY_EVENT_OUT_OF_COMMAND_MEMORY, size + OP_RESERVE, o->cb_data);
}
if (cs_on()) { pthread_mutex_lock(&g_cs_mu); g_cs.bytes += size; g_cs.recs++; pthread_mutex_unlock(&g_cs_mu); }
@@ -674,7 +674,7 @@ static uint8_t *cmd_alloc(cmd_t *o, uint32_t size) {
}
NVN(nvnCommandBufferBeginRecording, void, NVNcommandBuffer *c) {
cmd_t *o = CB(c);
if (o->recording) LOG("【红】BeginRecording 重入");
if (o->recording) LOG("[RED] BeginRecording re-entered");
o->recording = 1;
uint8_t *p = cmd_alloc(o, 8); put_hdr(p, OP_BEGIN, 8); o->begin = p;
}
@@ -738,7 +738,7 @@ static const tex_t *tex_snap(void *rec_, size_t off, int k, const NVNtexture *t,
static int red;
if (red++ < 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("[RED] texture object invalid while recording (uninitialized or already finalized): %p index %d, call site %s+%#llx, object start %llx %llx %llx %llx %llx %llx; read again at submit", (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]);
}
@@ -748,7 +748,7 @@ static const tex_t *tex_snap(void *rec_, size_t off, int k, const NVNtexture *t,
}
NVN(nvnCommandBufferSetRenderTargets, void, NVNcommandBuffer *c, int n, const NVNtexture *const *ct, const NVNtextureView *const *cv,
const NVNtexture *dt, const NVNtextureView *dv) {
if (n < 0 || n > 8) { LOG("【红】SetRenderTargets 数量 %d", n); n = n < 0 ? 0 : 8; }
if (n < 0 || n > 8) { LOG("[RED] SetRenderTargets count %d", n); n = n < 0 ? 0 : 8; }
p_set_rt *r = REC_SNAP(c, OP_SET_RT, p_set_rt, n + 1); r->n = n;
for (int i = 0; i < n; i++) { r->color[i] = ct ? tex_snap(r, SNAP_OFF(p_set_rt), i, ct[i], (uint64_t)__builtin_return_address(0)) : NULL; copy_view(&r->cview[i], &r->has_cview[i], cv ? cv[i] : NULL); }
r->depth = tex_snap(r, SNAP_OFF(p_set_rt), n, dt, (uint64_t)__builtin_return_address(0)); copy_view(&r->dview, &r->has_dview, dv);
@@ -760,7 +760,7 @@ NVN(nvnCommandBufferClearDepthStencil, void, NVNcommandBuffer *c, float depth, u
p_clear_ds *r = REC(c, OP_CLEAR_DS, p_clear_ds); *r = (p_clear_ds){depth, (int)B(dmask), stencil, smask};
}
NVN(nvnCommandBufferSetViewports, void, NVNcommandBuffer *c, int first, int n, const float *v) {
if (n < 0 || first + n > 16) { LOG("【红】SetViewports first=%d n=%d", first, n); return; }
if (n < 0 || first + n > 16) { LOG("[RED] SetViewports first=%d n=%d", first, n); return; }
p_viewports *r = REC(c, OP_VIEWPORTS, p_viewports); r->first = first; r->n = n;
for (int i = 0; i < n; i++) memcpy(r->v[i], v + 4 * i, 16);
}
@@ -777,15 +777,15 @@ NVN(nvnCommandBufferBindMultisampleState, void, NVNcommandBuffer *c, const NVNmu
NVN(nvnCommandBufferBindPolygonState, void, NVNcommandBuffer *c, const NVNpolygonState *st) { bind_state(c, 4, st, sizeof(poly_st)); }
NVN(nvnCommandBufferBindDepthStencilState, void, NVNcommandBuffer *c, const NVNdepthStencilState *st) { bind_state(c, 5, st, sizeof(ds_st)); }
NVN(nvnCommandBufferBindVertexAttribState, void, NVNcommandBuffer *c, int n, const NVNvertexAttribState *st) {
if (n < 0 || n > 16) { LOG("【红】BindVertexAttribState n=%d", n); return; }
if (n < 0 || n > 16) { LOG("[RED] BindVertexAttribState n=%d", n); return; }
p_bind_vattr *r = RECN(c, OP_BIND_VATTR, p_bind_vattr, st, n); r->first = 0; r->n = n; memcpy(r->st, st, 4 * n);
}
NVN(nvnCommandBufferBindVertexStreamState, void, NVNcommandBuffer *c, int n, const NVNvertexStreamState *st) {
if (n < 0 || n > 16) { LOG("【红】BindVertexStreamState n=%d", n); return; }
if (n < 0 || n > 16) { LOG("[RED] BindVertexStreamState n=%d", n); return; }
p_bind_vstream *r = RECN(c, OP_BIND_VSTREAM, p_bind_vstream, st, n); r->first = 0; r->n = n; memcpy(r->st, st, 8 * n);
}
NVN(nvnCommandBufferBindVertexBuffers, void, NVNcommandBuffer *c, int first, int n, const NVNbufferRange *rg) {
if (n < 0 || first + n > 16) { LOG("【红】BindVertexBuffers first=%d n=%d", first, n); return; }
if (n < 0 || first + n > 16) { LOG("[RED] BindVertexBuffers first=%d n=%d", first, n); return; }
p_bind_vbuf *r = RECN(c, OP_BIND_VBUF, p_bind_vbuf, r, n); r->first = first; r->n = n; memcpy(r->r, rg, sizeof *rg * n);
}
NVN(nvnCommandBufferBindUniformBuffer, void, NVNcommandBuffer *c, NVNshaderStage st, int i, NVNbufferAddress a, size_t n) {
@@ -801,7 +801,7 @@ NVN(nvnCommandBufferBindStorageBuffers, void, NVNcommandBuffer *c, NVNshaderStag
for (int i = 0; i < n; i++) *REC(c, OP_BIND_SSBO, p_bind_buf) = (p_bind_buf){st, first + i, rg[i].address, rg[i].size};
}
static p_bind_handles *bind_handles(NVNcommandBuffer *c, int op, int st, int first, int n, const uint64_t *h) {
if (n < 0 || n > 32) { LOG("【红】绑定句柄数 %d", n); return NULL; }
if (n < 0 || n > 32) { LOG("[RED] bind handle count %d", n); return NULL; }
p_bind_handles *r = RECN(c, op, p_bind_handles, h, n); r->stage = st; r->first = first; r->n = n; r->defer = 0; r->src = 0; memcpy(r->h, h, 8 * n);
if (g_ncbpool) { // this command buffer may have bound a pool other than the first: move its IDs to that pool's part of the table (handle: texture ID in bits 0-19, sampler ID from bit 20)
int tb = 0, sb = 0;
@@ -928,7 +928,7 @@ NVN(nvnDepthStencilStateSetStencilOp, void, NVNdepthStencilState *s, NVNface f,
}
NVN(nvnVertexAttribStateSetDefaults, void, NVNvertexAttribState *s) { *ST(va_st, s) = (va_st){0, 0, 0}; }
NVN(nvnVertexAttribStateSetFormat, void, NVNvertexAttribState *s, NVNformat f, ptrdiff_t off) {
if (off < 0 || off >= (1 << 19)) LOG("【红】顶点属性偏移 %td 超出打包范围", off);
if (off < 0 || off >= (1 << 19)) LOG("[RED] vertex attribute offset %td exceeds the packed range", off);
ST(va_st, s)->fmt = f; ST(va_st, s)->offset = (uint32_t)off;
}
NVN(nvnVertexAttribStateSetStreamIndex, void, NVNvertexAttribState *s, int i) { ST(va_st, s)->stream = i; }
@@ -937,7 +937,7 @@ NVN(nvnVertexStreamStateSetStride, void, NVNvertexStreamState *s, ptrdiff_t st)
NVN(nvnVertexStreamStateSetDivisor, void, NVNvertexStreamState *s, int d) { ST(vs_st, s)->divisor = d; }
static _Atomic uint64_t g_sub_wall_ns, g_sub_cpu_ns, g_sub_instr; // Wall clock / this thread's CPU time / this thread's retired instruction count for submit (excluding present) (taken away per frame)
static _Atomic uint64_t g_cum_sub_instr, g_cum_sub_cpu_ns; // Cumulative versions of the above (the 「翻译」(translation) summary row every 300 frames takes the difference; on a real device read through the log port: instructions per draw / submit CPU per frame)
static _Atomic uint64_t g_cum_sub_instr, g_cum_sub_cpu_ns; // Cumulative versions of the above (the "translation" summary row every 300 frames takes the difference; on a real device read through the log port: instructions per draw / submit CPU per frame)
extern int thread_selfcounts(int type, void *buf, size_t nbytes); // This thread's cumulative {instructions, cycles} (libSystem private interface, present on both Mac and iOS)
static uint64_t thr_instr(void) { uint64_t c[2] = {0, 0}; thread_selfcounts(1, c, sizeof c); return c[0]; }
// Supplementary readings for the submit path (on a real device Metal 4 instruction count is -18% while submit CPU time is +49%; need to tell apart kernel mode, execution efficiency, or frequency throttling):
@@ -969,10 +969,10 @@ void dm_gfx_suspend(int on) {
if (on) while (g_inflight_api) pthread_cond_wait(&g_susp_cv, &g_susp_mu);
else pthread_cond_broadcast(&g_susp_cv);
pthread_mutex_unlock(&g_susp_mu);
if (on) { dm_vk_drain(); mtl_drain(); LOG("[nvn] 图形挂起:在途 GPU 工作已清空"); } else LOG("[nvn] 图形恢复"); // the Vulkan queue first: presents on the NVN queue wait for its frames
if (on) { dm_vk_drain(); mtl_drain(); LOG("[nvn] graphics suspended: in-flight GPU work drained"); } else LOG("[nvn] graphics resumed"); // the Vulkan queue first: presents on the NVN queue wait for its frames
}
// One submission at a time: the Metal replay keeps one command buffer and one upload batch for the submission in progress (nvn_mtl.m g_cb). Fire Emblem: Three Houses initializes two queues and
// submits on both from two threads; the second submission arrived while the first was replaying and the host stopped with "提交重入" at the ninth frame. NVN orders work on different queues only
// submits on both from two threads; the second submission arrived while the first was replaying and the host stopped with "submit re-entered" at the ninth frame. NVN orders work on different queues only
// through syncs, so replaying the two queues' submissions in lock order is one valid order
static pthread_mutex_t g_sub_mu = PTHREAD_MUTEX_INITIALIZER;
NVN(nvnQueueSubmitCommands, void, NVNqueue *q, int n, const NVNcommandHandle *h) {
@@ -1022,8 +1022,8 @@ __attribute__((constructor)) static void nvn_nops_register(void) {
for (unsigned k = 0; k < sizeof g_nops / sizeof *g_nops; k++) {
unsigned i = 0;
while (i < NVN_NFUNC && strcmp(nvn_names[i], g_nops[k])) i++;
if (i == NVN_NFUNC) die("NVN 空实现名 %s 不在 main 名字表里", g_nops[k]);
if (nvn_slot[i].fn != (void *)nvn_unimpl_entry) die("NVN %s 既有实现又列为空实现", g_nops[k]);
if (i == NVN_NFUNC) die("NVN empty implementation %s is not in main's name table", g_nops[k]);
if (nvn_slot[i].fn != (void *)nvn_unimpl_entry) die("NVN %s has an implementation and is also listed as an empty implementation", g_nops[k]);
nvn_slot[i].fn = (void *)nvn_nop;
}
unsigned nnop = sizeof g_nops / sizeof *g_nops;
@@ -1037,7 +1037,7 @@ __attribute__((constructor)) static void nvn_nops_register(void) {
if (getenv("DM_NVN_PROF"))
for (unsigned i = 0; i < NVN_NFUNC; i++)
if (nvn_slot[i].fn != (void *)nvn_unimpl_entry && strcmp(nvn_names[i], "nvnQueuePresentTexture")) { nvn_real[i] = nvn_slot[i].fn; nvn_slot[i].fn = (void *)nvn_prof_entry; }
LOG("[nvn] 空设备:名字 %u,已实现 %u(语义 %u + 空实现 %u,其中 %u 个走宿主大栈),其余 %u 个接报红桩",
LOG("[nvn] null device: %u names, %u implemented (%u semantic + %u empty, %u of them on the large host stack), the other %u go to the [RED] stub",
NVN_NFUNC, g_nsem + nnop, g_nsem, nnop, nbig, NVN_NFUNC - g_nsem - nnop);
}
@@ -1047,7 +1047,7 @@ NVN(nvnWindowBuilderSetDefaults, void, NVNwindowBuilder *b) { NVNdevice *dev = W
NVN(nvnWindowBuilderSetDevice, void, NVNwindowBuilder *b, NVNdevice *d) { WB(b)->dev = d; }
NVN(nvnWindowBuilderSetNativeWindow, void, NVNwindowBuilder *b, NVNnativeWindow w) { WB(b)->native = w; }
NVN(nvnWindowBuilderSetTextures, void, NVNwindowBuilder *b, int n, NVNtexture *const *t) {
if (n < 0 || n > 4) { LOG("【红】WindowBuilderSetTextures 数量 %d", n); n = n < 0 ? 0 : 4; }
if (n < 0 || n > 4) { LOG("[RED] WindowBuilderSetTextures count %d", n); n = n < 0 ? 0 : 4; }
WB(b)->ntex = n; for (int i = 0; i < n; i++) WB(b)->tex[i] = t[i];
}
NVN(nvnWindowBuilderSetPresentInterval, void, NVNwindowBuilder *b, int v) { WB(b)->interval = v; }
@@ -1056,12 +1056,12 @@ NVN(nvnWindowInitialize, NVNboolean, NVNwindow *w, const NVNwindowBuilder *b) {
memset(o, 0, sizeof *o); o->magic = MAGIC('W', 'I', 'N', 'D'); o->native = s->native; o->ntex = s->ntex; o->interval = s->interval;
memcpy(o->tex, s->tex, sizeof o->tex);
for (int i = 0; i < o->ntex; i++) if (o->tex[i]) mtl_xv_window_tex(CTX(o->tex[i])->serial);
LOG("[nvn] WindowInitialize 交换链 %d 张 间隔 %d 首张 %dx%d 格式 %#x", o->ntex, o->interval,
LOG("[nvn] WindowInitialize swap chain %d textures, interval %d, first %dx%d format %#x", o->ntex, o->interval,
o->ntex ? (int)CTX(o->tex[0])->d.width : 0, o->ntex ? (int)CTX(o->tex[0])->d.height : 0, o->ntex ? CTX(o->tex[0])->d.format : 0);
return 1;
}
NVN(nvnWindowGetPresentInterval, int, const NVNwindow *w) { return ((const win_t *)w)->interval; } // Link's Awakening reads it back every frame
NVN(nvnWindowSetPresentInterval, void, NVNwindow *w, int v) { if (((win_t *)w)->interval != v) LOG("[nvn] 游戏设出图间隔 %d → %d", ((win_t *)w)->interval, v); ((win_t *)w)->interval = v; }
NVN(nvnWindowSetPresentInterval, void, NVNwindow *w, int v) { if (((win_t *)w)->interval != v) LOG("[nvn] the game sets present interval %d → %d", ((win_t *)w)->interval, v); ((win_t *)w)->interval = v; }
NVN(nvnWindowSetCrop, void, NVNwindow *w, int x, int y, int ww, int hh) {
win_t *o = (win_t *)w; int v[4] = {x, y, ww, hh};
if (memcmp(o->crop, v, sizeof v)) LOG("[nvn] WindowCrop %d,%d %dx%d", x, y, ww, hh);
@@ -1124,8 +1124,8 @@ static void dump_frame(void) {
inited = 1;
const char *e = getenv("DM_NVN_FRAMES"); if (e) nframes = atoi(e);
const char *c = getenv("DM_NVN_TSV_CALLS"); if (c) calls = atoi(c); // 0 = write only the @ timing rows (pulling files from a real device has a size limit, and the call-count rows take about 2/3)
const char *t = getenv("DM_NVN_TSV"); if (t && !(g_tsv = fopen(t, "w"))) LOG("【红】打不开 %s", t);
if (g_tsv) fprintf(g_tsv, "# kind: nvn-per-frame name 为 NVN 函数名时值 = 调用次数;@ 开头为计时埋点(ms / 个)\nframe\tname\tvalue\n");
const char *t = getenv("DM_NVN_TSV"); if (t && !(g_tsv = fopen(t, "w"))) LOG("[RED] cannot open %s", t);
if (g_tsv) fprintf(g_tsv, "# kind: nvn-per-frame value = call count when name is an NVN function name; names starting with @ are timing probes (ms / count)\nframe\tname\tvalue\n");
}
uint64_t total = 0; unsigned nfn = 0;
char line[8192]; size_t o = 0; line[0] = 0;
@@ -1138,15 +1138,15 @@ static void dump_frame(void) {
if (g_frame <= (uint64_t)nframes && o < sizeof line - 96) o += snprintf(line + o, sizeof line - o, " %s=%llu", nvn_names[i] + 3, d);
}
if (g_tsv) fflush(g_tsv);
if (g_frame <= (uint64_t)nframes) LOG("[nvn] %s %llu:%u 个函数 %llu 次调用 |%s", g_frame ? "帧" : "初始化", g_frame, nfn, total, line);
else if (g_frame % 300 == 0) LOG("[nvn] 帧 %llu:%u 个函数 %llu 次调用", g_frame, nfn, total);
if (g_frame <= (uint64_t)nframes) LOG("[nvn] %s %llu: %u functions %llu calls |%s", g_frame ? "frame" : "init", g_frame, nfn, total, line);
else if (g_frame % 300 == 0) LOG("[nvn] frame %llu: %u functions %llu calls", g_frame, nfn, total);
g_frame++; cs_dump();
// Every 300 frames read resident memory and hand it to the graphics layer to print the memory readout line (mtl_mem_stat, low frequency, capped). Memory watchdog: when resident memory exceeds the cap, log a failure and exit on its own. On by default only on the Mac host (24 GB: runaway host memory
// drags the whole machine into a kernel panic, which happened twice with other programs on the same machine), or set for tests with DM_MEM_CAP_GB=<integer>; the player's iOS app has no cap (exiting before the system kills it is pointless and only adds another kind of crash)
if (g_frame % 300 == 0) { task_vm_info_data_t vi; mach_msg_type_number_t cnt = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vi, &cnt) == KERN_SUCCESS) { unsigned long long mb = (unsigned long long)(vi.phys_footprint >> 20); void mtl_mem_stat(unsigned long long, unsigned long long); mtl_mem_stat(g_frame, mb);
static long cap = -1; if (cap < 0) { const char *e = getenv("DM_MEM_CAP_GB"); cap = e ? atol(e) : DM_MEM_CAP_DEFAULT_GB; }
if (cap > 0 && mb > (unsigned long long)cap * 1024) { LOG("【红】内存看门狗:常驻 %llu MB 超过上限 %ld GB(帧 %llu),自行退出", mb, cap, (unsigned long long)g_frame); _exit(86); } } }
if (cap > 0 && mb > (unsigned long long)cap * 1024) { LOG("[RED] memory watchdog: resident %llu MB over the limit of %ld GB (frame %llu), exiting", mb, cap, (unsigned long long)g_frame); _exit(86); } } }
dm_guest_frame_tick(); // Deterministic guest clock (DM_DETCLOCK, host hle_os.c): each present advances a fixed step; a no-op when not enabled
}
// ---------- Timing instrumentation (per-frame rows written to DM_NVN_TSV: frame / @metric / value; every 300 frames a summary of p50 / p95 / max) ----------
@@ -1161,9 +1161,9 @@ __attribute__((weak)) int dm_thermal_now(void) { return -1; } // Default (Mac)
#define LFN 16
typedef struct { uint64_t f; double fr, nv, t_s, make_ms, up_mb, up_ms; unsigned pso, lib, up, rup; int ls; } lf_rec; // ls: whether the loading screen was covering the picture when this frame was presented
static lf_rec g_lf[LFN];
__attribute__((weak)) void dm_longframe_note(const char *what) { LOG("[长帧] %s", what); } // Default (graphics unit tests); the strong definition in the host's diag.c adds scene context and writes perf.txt
__attribute__((weak)) void dm_longframe_note(const char *what) { LOG("[longframe] %s", what); } // Default (graphics unit tests); the strong definition in the host's diag.c adds scene context and writes perf.txt
extern void mtl_create_ns(uint64_t *lib_ns, uint64_t *pso_ns); extern uint64_t mtl_upload_bytes(void); extern void mtl_pkg_stats(uint64_t *uploads, uint64_t *reuploads);
static const char *const g_tname[6] = {"帧时间", "出帧工作", "NVN 提交+出图", "GPU", "翻译层录制期", "翻译层提交期"};
static const char *const g_tname[6] = {"frame time", "frame work", "NVN submit+present", "GPU", "translation layer recording", "translation layer submit"};
static void tpush(int k, double v) { g_tw[k][g_tn[k]++ % TWIN] = v; }
// Thermal attribution (iOS DM_HEAT): per-frame GPU milliseconds are kept in a separate ring, taking the mean / p95 "since the last read" (at most the last HRN frames), without interfering with the timing summary's 300-frame window
#define HRN 1024
@@ -1192,17 +1192,17 @@ static void power_line(uint64_t frame) {
uint64_t iw = pw.cpu_energy.task_interrupt_wakeups, idw = pw.cpu_energy.task_platform_idle_wakeups, g = pw.gpu_energy.task_gpu_utilisation;
if (pt && t > pt) { double s = (t - pt) / 1e9; char yt[192]; dm_yield_top(yt, sizeof yt);
double nf = frame > pf ? (double)(frame - pf) : 1; // per frame: the fair comparison when one build holds the frame rate better than another
LOG("[nvn] 功耗 至帧 %llu:CPU 能耗 %.2f W,每帧 %.1f mJ(%.1f 帧/秒);CPU %.0f%%(100 = 一个核)其中性能核 %.0f%%;唤醒 %.0f 次/秒(其中出平台空闲 %.0f);GPU %.1f ms/秒;温控 %d;让出最多:%s", frame,
LOG("[nvn] power up to frame %llu: CPU energy %.2f W, %.1f mJ per frame (%.1f fps); CPU %.0f%% (100 = one core), %.0f%% of it on performance cores; wakeups %.0f/s (out of platform idle %.0f); GPU %.1f ms/s; thermal %d; top yielders: %s", frame,
(e - pe) / 1e9 / s, (e - pe) / 1e6 / nf, nf / s, (cpu - pcpu) / 1e7 / s, cpu > pcpu ? 100.0 * (double)(p - pp) / (double)(cpu - pcpu) : 0, (iw - piw) / s, (idw - ppw) / s, (g - pg) / 1e6 / s, dm_thermal_now(), yt);
char rs[400]; mtl_res_stats(rs, sizeof rs); static uint64_t pbs, pbf; uint64_t bs, bf; mtl_barrier_stats(&bs, &bf);
uint64_t mtl_cut_count(void), mtl_blit_splits(void); void mtl_ubd_stats(uint64_t *, uint64_t *); static uint64_t pcut, pbsp, pud, puc; uint64_t cut = mtl_cut_count(), bsp = mtl_blit_splits(), ud, uc; mtl_ubd_stats(&ud, &uc);
uint64_t ld, st, fu, udc = mtl_udc_saved_bytes(); mtl_bw(&ld, &st, &fu);
LOG("[nvn] 驻留集 至帧 %llu:%s;绘制通道开头屏障 全 %llu / 省 %llu;分段提交 %llu 次;拷贝 / 缓冲更新打断通道 %llu 次;通道内 uniform 更新推迟 %llu 次、因读取需要先落地结束通道 %llu 次;"
"附件每帧 读入 %.0f MB / 写回 %.0f MB,全读全写通道 %.0f 个,未写附件不写回省 %.1f MB", frame, rs, (unsigned long long)(bf - pbf), (unsigned long long)(bs - pbs),
LOG("[nvn] residency set up to frame %llu: %s; draw-pass entry barriers full %llu / saved %llu; split submits %llu; passes broken by copies / buffer updates %llu; in-pass uniform updates deferred %llu, passes ended to land them for a read %llu; "
"attachments per frame load %.0f MB / store %.0f MB, load-all store-all passes %.0f, saved by not storing unwritten attachments %.1f MB", frame, rs, (unsigned long long)(bf - pbf), (unsigned long long)(bs - pbs),
(unsigned long long)(cut - pcut), (unsigned long long)(bsp - pbsp), (unsigned long long)(ud - pud), (unsigned long long)(uc - puc),
(ld - pld) / 1048576.0 / nf, (st - pst) / 1048576.0 / nf, (fu - pfu) / nf, (udc - pudc) / 1048576.0 / nf); pbs = bs; pbf = bf; pcut = cut; pbsp = bsp; pud = ud; puc = uc;
char tc[400]; dm_thread_cpu(tc, sizeof tc, s);
LOG("[nvn] 线程 至帧 %llu:CPU %s", frame, tc); }
LOG("[nvn] threads up to frame %llu: CPU %s", frame, tc); }
else { char yt[192], rs[400], tc[400]; dm_yield_top(yt, sizeof yt); mtl_res_stats(rs, sizeof rs); dm_thread_cpu(tc, sizeof tc, 1); } // the first call only takes the baselines
pt = t; pe = e; pcpu = cpu; pp = p; piw = iw; ppw = idw; pg = g; pf = frame; mtl_bw(&pld, &pst, &pfu); pudc = mtl_udc_saved_bytes();
}
@@ -1211,37 +1211,37 @@ static void tsummary(uint64_t frame) {
for (int k = 0; k < 6; k++) {
unsigned n = g_tn[k] < TWIN ? g_tn[k] : TWIN; if (!n) continue;
double v[TWIN]; memcpy(v, g_tw[k], n * sizeof *v); qsort(v, n, sizeof *v, dcmp);
o += snprintf(line + o, sizeof line - o, " %s p50 %.2f / p95 %.2f / 最大 %.2f ms(%u 帧);", g_tname[k], v[n / 2], v[(n * 95) / 100 < n ? (n * 95) / 100 : n - 1], v[n - 1], n);
o += snprintf(line + o, sizeof line - o, " %s p50 %.2f / p95 %.2f / max %.2f ms (%u frames);", g_tname[k], v[n / 2], v[(n * 95) / 100 < n ? (n * 95) / 100 : n - 1], v[n - 1], n);
}
LOG("[nvn] 计时 至帧 %llu:%s", frame, line);
LOG("[nvn] timing up to frame %llu:%s", frame, line);
double tgt = 1000.0 / mtl_fps_target(); // milliseconds one frame should take: 16.7 at 60 fps, 33.3 when the game itself asks for 30 (the over-time / dropped-frame counts below use it)
{ unsigned n = g_tn[0] < TWIN ? g_tn[0] : TWIN, over = 0; double v[TWIN]; // frame interval gets its own line (M1 baseline): number of frames longer than one frame period in this window and p99
if (n) { memcpy(v, g_tw[0], n * sizeof *v); qsort(v, n, sizeof *v, dcmp); for (unsigned i = 0; i < n; i++) over += v[i] > tgt;
LOG("[nvn] 帧间隔 至帧 %llu:>%.1fms %u / %u 帧,p99 %.2f ms", frame, tgt, over, n, v[(n * 99) / 100 < n ? (n * 99) / 100 : n - 1]); } }
LOG("[nvn] frame interval up to frame %llu: >%.1fms %u / %u frames, p99 %.2f ms", frame, tgt, over, n, v[(n * 99) / 100 < n ? (n * 99) / 100 : n - 1]); } }
{ unsigned n = g_presn < TWIN ? g_presn : TWIN, k = 0, drop = 0, discard = 0; double v[TWIN]; // on-screen present: dropped frame = interval > one frame period × 1.5 or discarded
for (unsigned i = 0; i < n; i++) { double x = g_pres_iv[i]; if (x < 0) { drop++; discard++; } else if (x > 0) { v[k++] = x; drop += x > tgt * 1.5; } }
task_vm_info_data_t vi; mach_msg_type_number_t vc = TASK_VM_INFO_COUNT; double fp = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vi, &vc) == KERN_SUCCESS ? vi.phys_footprint / 1048576.0 : -1;
double br = dm_brightness_now(); // footprint / brightness are carried on the on-screen present row: does not depend on the tick-entry observation gate (camp runs have it too)
if (k) { qsort(v, k, sizeof *v, dcmp); LOG("[nvn] 上屏 至帧 %llu:%u 帧,掉帧 %u(丢弃 %u),p99 %.2f ms;footprint %.0f MiB;亮度 %.2f;温控 %d", frame, k + discard, drop, discard, v[(k * 99) / 100 < k ? (k * 99) / 100 : k - 1], fp, br, dm_thermal_now()); }
if (k) { qsort(v, k, sizeof *v, dcmp); LOG("[nvn] on-screen up to frame %llu: %u frames, dropped %u (discarded %u), p99 %.2f ms; footprint %.0f MiB; brightness %.2f; thermal %d", frame, k + discard, drop, discard, v[(k * 99) / 100 < k ? (k * 99) / 100 : k - 1], fp, br, dm_thermal_now()); }
g_presn = 0; }
power_line(frame);
{ static uint64_t pi, pc, pd, pf; uint64_t ci = atomic_load(&g_cum_sub_instr), cc = atomic_load(&g_cum_sub_cpu_ns), ps_, dr_; mtl_stats(&ps_, &dr_); // translation layer
if (pf && frame > pf && dr_ > pd) LOG("[nvn] 翻译 至帧 %llu:每次绘制 %.0f 条指令,每帧提交 %.2f M 条 / CPU %.3f ms,每帧绘制 %.0f", frame,
if (pf && frame > pf && dr_ > pd) LOG("[nvn] translation up to frame %llu: %.0f instructions per draw, submit per frame %.2f M instructions / CPU %.3f ms, %.0f draws per frame", frame,
(double)(ci - pi) / (double)(dr_ - pd), (ci - pi) / 1e6 / (double)(frame - pf), (cc - pc) / 1e6 / (double)(frame - pf), (double)(dr_ - pd) / (double)(frame - pf));
{ static uint64_t py, pk, pw; uint64_t cy = atomic_load(&g_cum_sub_cyc), ck = atomic_load(&g_cum_sub_sys_ns), cw = atomic_load(&g_cum_sub_wall_ns);
if (pf && frame > pf && ci > pi && cy > py) { double nf = (double)(frame - pf), cyc = (double)(cy - py), cpu = (double)(cc - pc);
qos_class_t qc = QOS_CLASS_UNSPECIFIED; int rp = 0; pthread_get_qos_class_np(pthread_self(), &qc, &rp);
uint64_t aw = 0, ag = 0; int an = 0; void mtl_alloc_stats(uint64_t *, uint64_t *, int *); mtl_alloc_stats(&aw, &ag, &an);
static uint64_t pyn, pyns; uint64_t yn = 0, yns = 0; dm_main_yield_stats(&yn, &yns);
LOG("[nvn] 翻译细项 至帧 %llu:每帧提交 周期 %.2f M(每条指令 %.2f 周期)、CPU %.3f ms 其中内核态 %.3f ms、墙钟 %.3f ms;平均主频 %.2f GHz(周期 / CPU 时间);本线程 QoS %#x;分配器池 %d 个、累计等 GPU %llu 次;实时声明计算 %.2f ms;主线程让出 每帧 %.0f 次 / %.3f ms", frame,
LOG("[nvn] translation detail up to frame %llu: submit per frame %.2f M cycles (%.2f cycles per instruction), CPU %.3f ms of which kernel %.3f ms, wall clock %.3f ms; mean clock %.2f GHz (cycles / CPU time); this thread's QoS %#x; allocator pools %d, GPU waits so far %llu; real-time declared computation %.2f ms; main thread yields per frame %.0f / %.3f ms", frame,
cyc / 1e6 / nf, cyc / (double)(ci - pi), cpu / 1e6 / nf, (double)(ck - pk) / 1e6 / nf, (double)(cw - pw) / 1e6 / nf, cpu > 0 ? cyc / cpu : 0, (unsigned)qc, an, (unsigned long long)aw, g_rt_comp_ms, (double)(yn - pyn) / nf, (double)(yns - pyns) / 1e6 / nf); pyn = yn; pyns = yns; }
if (frame % 300 == 0) { unsigned wn = 0; uint32_t wq = dm_guest_work_qos(&wn); thread_extended_info_data_t xi; mach_msg_type_number_t xc = THREAD_EXTENDED_INFO_COUNT; int okx = thread_info(pthread_mach_thread_np(pthread_self()), THREAD_EXTENDED_INFO, (thread_info_t)&xi, &xc) == KERN_SUCCESS;
thread_time_constraint_policy_data_t tp; mach_msg_type_number_t tc = THREAD_TIME_CONSTRAINT_POLICY_COUNT; boolean_t def = 0; int rt_live = thread_policy_get(pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&tp, &tc, &def) == KERN_SUCCESS && !def; // def = 1: the real-time policy is not in effect (never set / revoked)
LOG("[nvn] 线程策略:主线程 实时 计算 %.2f ms / 周期 %.2f ms,实时策略%s,当前优先级 %d(基础 %d,调度类 %d);工作线程 %u 条 QoS %#x;出图 QoS %#x", g_rt_comp_ms, g_rt_period_ms, rt_live ? "在生效" : "未生效", okx ? xi.pth_curpri : -1, okx ? xi.pth_priority : -1, okx ? xi.pth_policy : -1, wn, wq, mtl_present_qos()); }
LOG("[nvn] thread policy: main thread real-time computation %.2f ms / period %.2f ms, real-time policy %s, current priority %d (base %d, policy %d); %u worker threads QoS %#x; present QoS %#x", g_rt_comp_ms, g_rt_period_ms, rt_live ? "in effect" : "not in effect", okx ? xi.pth_curpri : -1, okx ? xi.pth_priority : -1, okx ? xi.pth_policy : -1, wn, wq, mtl_present_qos()); }
py = cy; pk = ck; pw = cw; }
pi = ci; pc = cc; pd = dr_; pf = frame; }
uint64_t up, re, fa, pr; mtl_pkg_stats(&up, &re); nvn_dirty_stats(&fa, &pr);
LOG("[nvn] 预排布贴图 首传 %llu 张、池内容变化重传 %llu 次;写保护缺页 %llu 次、宿主写前解保护 %llu 页;漏检 %llu(累计)", up, re, fa, pr, mtl_pkg_miss());
LOG("[nvn] packaged textures: first uploads %llu, re-uploads on pool content change %llu; write-protection faults %llu, pages unprotected before host writes %llu; missed %llu (cumulative)", up, re, fa, pr, mtl_pkg_miss());
memset(g_tn, 0, sizeof g_tn);
}
// DM_SLOWFRAME=1 (test switch, read over the log port on a real device): per-frame timings are first stored in a ring of the last 16 frames; dropped frames are judged 8 frames late (GPU / on-screen present times are back by then),
@@ -1275,14 +1275,14 @@ static void slow_emit(uint64_t f) {
snprintf(b + o, sizeof b - o, "\t@prev3_max_frame_ms=%.3f", m);
fprintf(stderr, "%s\n", b);
}
if (f % 300 == 0) fprintf(stderr, "[slowframe]\t#\t至帧 %llu\tjudged=%llu\tslow=%llu\t判据=%s\n", f, judged, slow, has ? "上屏间隔" : "出图调用间隔(无窗口)");
if (f % 300 == 0) fprintf(stderr, "[slowframe]\t#\tup to frame %llu\tjudged=%llu\tslow=%llu\tcriterion=%s\n", f, judged, slow, has ? "on-screen interval" : "present call interval (no window)");
}
// Declaration for the time-constraint (real-time) thread: period = present period, constraint = period, computation = the CPU time this thread actually needs per frame.
// The computation used to be hard-coded at 2 ms, while the game main thread (game logic + recording + submit replay) takes about 10 ms per frame when hot - a 5× gap between declared and actual:
// a real-time thread that exceeds its declared computation gets demoted by the system, and the performance controller also sets frequency from the declaration (this declaration is how the system learns the frame deadline and the workload).
// DM_RT_COMP=<ms> fixes the declaration; default auto = every 120 frames take the p90 of this thread's per-frame CPU time and re-declare if it differs from the declared value by > 15% (capped at 0.9 × period).
// DM_RT=0 leaves the acquire thread on the standard policy (its QoS only), for power A/B: the real-time declaration is how the performance controller learns the frame deadline, and it may hold the clock up for it
static int rt_on(void) { static int v = -1; if (v < 0) { const char *e = getenv("DM_RT"); v = !(e && !strcmp(e, "0")); if (!v) LOG("[nvn] DM_RT=0:出图线程不设时间约束策略(只按 QoS)"); } return v; }
static int rt_on(void) { static int v = -1; if (v < 0) { const char *e = getenv("DM_RT"); v = !(e && !strcmp(e, "0")); if (!v) LOG("[nvn] DM_RT=0: the present thread gets no time-constraint policy (QoS only)"); } return v; }
static void set_time_constraint_comp(uint64_t period_ns, uint64_t comp_ns) {
if (!rt_on()) return;
static mach_timebase_info_data_t tb; if (!tb.denom) mach_timebase_info(&tb);
@@ -1292,7 +1292,7 @@ static void set_time_constraint_comp(uint64_t period_ns, uint64_t comp_ns) {
.constraint = (uint32_t)(period_ns * tb.denom / tb.numer), .preemptible = 1};
kern_return_t kr = thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&p, THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (kr == KERN_SUCCESS) { g_rt_comp_ms = comp_ns / 1e6; g_rt_period_ms = period_ns / 1e6; }
LOG("[nvn] 出图线程时间约束策略(周期 %.3f ms、计算 %.3f ms)%s", period_ns / 1e6, comp_ns / 1e6, kr == KERN_SUCCESS ? "已设" : "设置失败");
LOG("[nvn] present thread time-constraint policy (period %.3f ms, computation %.3f ms) %s", period_ns / 1e6, comp_ns / 1e6, kr == KERN_SUCCESS ? "set" : "failed to set");
}
static int rt_comp_fixed_ms(void) { static int v = -2; if (v == -2) { const char *e = getenv("DM_RT_COMP"); v = e && *e && strcmp(e, "auto") ? atoi(e) : -1; } return v; } // −1 = auto
static void set_time_constraint(uint64_t period_ns) { int f = rt_comp_fixed_ms(); set_time_constraint_comp(period_ns, f > 0 ? (uint64_t)f * 1000000ull : 2000000ull); } // first time: the fixed value, or auto's starting value of 2 ms
@@ -1328,14 +1328,14 @@ static void present_cfg(const win_t *o) {
// The interval mode comes from the game profile (present_interval, default game): whether frames follow the present interval the game sets depends on whether the game's logic counts frames, which differs per game. Environment variables override it, and can override the fixed 60 Hz.
int scr = mtl_display_hz();
const char *e = getenv("DM_PRESENT_HZ"); g_hz = e ? atoi(e) : 60;
if (!g_hz) LOG("[nvn] 【警告】DM_PRESENT_HZ=0:不限帧率,帧节奏非真机,界面逻辑可能偏离(仅供性能剖析,不能用于判据 / 对拍)");
if (!g_hz) LOG("[nvn] [WARNING] DM_PRESENT_HZ=0: frame rate uncapped, frame pacing unlike the device, UI logic may drift (for profiling only, not for verdicts / cross-checks)");
// Cut (intentional deviation from the original game's behavior): with DM_PRESENT_INTERVAL=display the present interval set by the game is not followed and one frame is presented per display-link tick,
// with a fixed hard floor of 60 fps; the logic stays at 25 ticks, and the client's frame-counted UI logic is unaffected -
// with the same save and the same input, the UI message streams of the two modes match message by message. Proof (before character select): r9 game / display 60 ×3 / display 120 ×2, 6 groups in total, uicmp consistent
// (the save indicator is exempted as a timing race), and the timing differences of key messages are within game mode's own noise band; proof after entering a run is still missing, pending action script coverage
const char *m = getenv("DM_PRESENT_INTERVAL"); g_display_mode = m ? !strcmp(m, "display") : DM_GAME_PRESENT_DISPLAY;
LOG("[nvn] 出图节拍 %d Hz,间隔模式 %s(%s;游戏设的出图间隔 %d;%s)", g_hz, g_display_mode ? "display(每节拍一帧,剪口)" : "game(原版语义)",
m ? "DM_PRESENT_INTERVAL" : "游戏档案 present_interval", o->interval, scr > 0 ? "有窗口,固定 60 Hz" : "无窗口,固定 60 Hz");
LOG("[nvn] present tick %d Hz, interval mode %s (%s; present interval set by the game %d; %s)", g_hz, g_display_mode ? "display (one frame per tick, a cut)" : "game (original semantics)",
m ? "DM_PRESENT_INTERVAL" : "game profile present_interval", o->interval, scr > 0 ? "window, fixed 60 Hz" : "no window, fixed 60 Hz");
}
static int present_itv(const win_t *o) { return g_display_mode ? 1 : o->interval > 0 ? o->interval : 1; } // vsyncs per frame that presentation follows
static uint64_t present_period(const win_t *o) {
@@ -1355,11 +1355,11 @@ NVN(nvnQueuePresentTexture, void, NVNqueue *q, NVNwindow *w, int idx) {
static uint64_t prev_in, prev_out, prev_pass, prev_draw;
// Loading screen on first entering a run: if the background pipeline warm-up has not finished, wait for it here (the loading screen stays a bit longer), so that after entering the run no pipelines are compiled on the fly on the submit thread. When warm-up finished long ago this passes with a single check
{ static int gate;
if (!gate && g_nvn_loadscreen_up && g_nvn_loadscreen_up()) { gate = 1; int ms = mtl_warm_wait(15000); if (ms) LOG("[nvn] 进局读取画面等管线预热 %d ms", ms); } }
if (!gate && g_nvn_loadscreen_up && g_nvn_loadscreen_up()) { gate = 1; int ms = mtl_warm_wait(15000); if (ms) LOG("[nvn] the loading screen into the run waited %d ms for pipeline prewarm", ms); } }
present_cfg(o);
{
// Test switch DM_TEST_PRESENT_LAG=every N presents,milliseconds: sleep that long before the Nth, 2Nth… present, imitating an occasional slow frame on a phone (Mario 1.2.1 then sets present interval 2; checks that the display link holds frames by interval). Not set in the product
static int lag_n = -1, lag_ms; if (lag_n < 0) { const char *e = getenv("DM_TEST_PRESENT_LAG"); lag_n = 0; if (e && sscanf(e, "%d,%d", &lag_n, &lag_ms) == 2 && lag_n > 0) LOG("DM_TEST_PRESENT_LAG:每 %d 次出图多睡 %d ms", lag_n, lag_ms); else lag_n = 0; }
static int lag_n = -1, lag_ms; if (lag_n < 0) { const char *e = getenv("DM_TEST_PRESENT_LAG"); lag_n = 0; if (e && sscanf(e, "%d,%d", &lag_n, &lag_ms) == 2 && lag_n > 0) LOG("DM_TEST_PRESENT_LAG: every %d presents, sleep an extra %d ms", lag_n, lag_ms); else lag_n = 0; }
if (lag_n && o->presents % lag_n == 0) usleep((useconds_t)lag_ms * 1000);
}
gfx_enter();
@@ -1377,8 +1377,8 @@ NVN(nvnQueuePresentTexture, void, NVNqueue *q, NVNwindow *w, int idx) {
if ((g_frame + 1) % TWIN == 0) { // every 300 frames list the 5 most time-consuming functions
char line[1024]; size_t o = 0;
for (int k = 0; k < 5; k++) { unsigned best = 0; for (unsigned i = 1; i < NVN_NFUNC; i++) if (acc[i] > acc[best]) best = i;
if (!acc[best]) break; o += snprintf(line + o, sizeof line - o, " %s %.2f ms;", nvn_names[best] + 3, acc[best] * tb.numer / tb.denom / 1e6); acc[best] = 0; }
LOG("[nvn] 翻译层耗时前 5(近 300 帧合计):%s", line); memset(acc, 0, sizeof acc);
if (!acc[best]) break; o += snprintf(line + o, sizeof line - o, " %s %.2f ms;", nvn_names[best] + 3, acc[best] * tb.numer / tb.denom / 1e6); acc[best] = 0; }
LOG("[nvn] translation layer top 5 by time (sum over the last 300 frames):%s", line); memset(acc, 0, sizeof acc);
}
trow(g_frame, "@rec_ms", rec * tb.numer / tb.denom / 1e6); trow(g_frame, "@submit_ms", sub * tb.numer / tb.denom / 1e6);
tpush(4, rec * tb.numer / tb.denom / 1e6); tpush(5, sub * tb.numer / tb.denom / 1e6);
@@ -1436,11 +1436,11 @@ NVN(nvnQueuePresentTexture, void, NVNqueue *q, NVNwindow *w, int idx) {
static double ef = 16.7, eg = 8; int lf = r->fr > 20.0 && r->fr > 1.4 * ef, lg = g > 16.7 && g > 1.3 * eg;
if (r->f == f - 8) { ef += ((r->fr < 3 * ef ? r->fr : 3 * ef) - ef) / 16; eg += ((g < 3 * eg ? g : 3 * eg) - eg) / 16; }
if (on && r->f == f - 8 && f - 8 > 0 && (lf || lg)) { char b[512];
snprintf(b, sizeof b, "帧 %llu(%.2f s)整帧 %.1f ms,提交+出图 %.1f ms,GPU %.1f ms;新建管线 %u 条 + 着色器库 %u 个共 %.1f ms;上传贴图 首传 %u 张 + 重传 %u 次 %.1f MB / %.1f ms",
snprintf(b, sizeof b, "frame %llu (%.2f s) whole frame %.1f ms, submit+present %.1f ms, GPU %.1f ms; new pipelines %u + shader libraries %u, %.1f ms in all; texture uploads %u first + %u re-uploads, %.1f MB / %.1f ms",
(unsigned long long)r->f, r->t_s, r->fr, r->nv, g, r->pso, r->lib, r->make_ms, r->up, r->rup, r->up_mb, r->up_ms);
// Visibility: during the time a long frame "comes out late", what stays on screen is the previous frame; if the previous frame was a loading screen (or this frame itself is one), all the player sees is the loading screen staying a bit longer, with no motion interrupted
const lf_rec *pv = &g_lf[(f - 9) % LFN]; int hidden = r->ls || (f >= 9 && pv->f == f - 9 && pv->ls);
if (hidden) strlcat(b, ";接读取画面,不可见", sizeof b);
if (hidden) strlcat(b, "; next to a loading screen, not visible", sizeof b);
dm_longframe_note(b); } }
uint64_t g0, g1; if (frame_rec() && mtl_gpu_span(f - 8, &g0, &g1)) { trow(f - 8, "@gpu_beg_ms", (g0 - g_t0_ns) / 1e6); trow(f - 8, "@gpu_end_ms", (g1 - g_t0_ns) / 1e6); } }
prev_in = t0; prev_pass = ps; prev_draw = dr;
@@ -1479,8 +1479,8 @@ NVN(nvnBufferBuilderSetDevice, void, NVNbufferBuilder *b, NVNdevice *d) { BB(b)-
NVN(nvnBufferBuilderSetStorage, void, NVNbufferBuilder *b, NVNmemoryPool *p, ptrdiff_t off, size_t n) { BB(b)->pool = (const pool_t *)p; BB(b)->off = off; BB(b)->size = n; }
NVN(nvnBufferInitialize, NVNboolean, NVNbuffer *buf, const NVNbufferBuilder *b) {
const buf_builder_t *bb = (const buf_builder_t *)b;
if (!bb->pool || !bb->pool->mem) { LOG("【红】BufferInitialize:池没有存储(池 %p flags %#x 偏移 %#llx 大小 %#llx)", (const void *)bb->pool, bb->pool ? bb->pool->flags : 0, (unsigned long long)bb->off, (unsigned long long)bb->size); return 0; }
if ((uint64_t)bb->off + bb->size > bb->pool->size) LOG("【红】BufferInitialize:越过池尾(偏移 %#llx 大小 %#llx 池 %#llx)", (unsigned long long)bb->off, (unsigned long long)bb->size, (unsigned long long)bb->pool->size);
if (!bb->pool || !bb->pool->mem) { LOG("[RED] BufferInitialize: pool has no storage (pool %p flags %#x offset %#llx size %#llx)", (const void *)bb->pool, bb->pool ? bb->pool->flags : 0, (unsigned long long)bb->off, (unsigned long long)bb->size); return 0; }
if ((uint64_t)bb->off + bb->size > bb->pool->size) LOG("[RED] BufferInitialize: past the end of the pool (offset %#llx size %#llx pool %#llx)", (unsigned long long)bb->off, (unsigned long long)bb->size, (unsigned long long)bb->pool->size);
*(buf_t *)buf = (buf_t){MAGIC('B', 'U', 'F', 0), bb->pool, bb->off, bb->size};
OLOG("BufferInitialize %p pool=%p off=%#llx size=%#llx", (void *)buf, (const void *)bb->pool, (unsigned long long)bb->off, (unsigned long long)bb->size);
return 1;
@@ -1518,18 +1518,18 @@ NVN(nvnMemoryPoolMapVirtual, NVNboolean, NVNmemoryPool *p, int n, const NVNmappi
for (int i = 0; i < n; i++) {
const pool_t *ph = (const pool_t *)r[i].phys;
if (!ph) continue; // Unmapping (null physical pool): the host keeps the storage as is
if (!ph->mem) { LOG("【红】MapVirtual:物理池 %p 没有存储", (const void *)ph); return 0; }
if (!ph->mem) { LOG("[RED] MapVirtual: physical pool %p has no storage", (const void *)ph); return 0; }
uint8_t *base = ph->mem + r[i].poff - r[i].voff;
if (r[i].voff < 0 || (uint64_t)r[i].voff + r[i].size > o->size || (uint64_t)r[i].poff + r[i].size > ph->size) { LOG("【红】MapVirtual:请求越界(虚拟 %#llx+%#llx / 池 %#llx,物理 %#llx / 池 %#llx)", (unsigned long long)r[i].voff, (unsigned long long)r[i].size, (unsigned long long)o->size, (unsigned long long)r[i].poff, (unsigned long long)ph->size); return 0; }
if (o->mem && o->mem != base) { LOG("【红】MapVirtual:映射不连续(真稀疏)未实现:虚拟池 %p 已对 %p,本条要 %p", (void *)o, (void *)o->mem, (void *)base); return 0; }
if (!o->mem) LOG("[nvn] MapVirtual:虚拟池 %p(%#llx 字节)的存储指向物理池 %p + %#llx", (void *)o, (unsigned long long)o->size, (const void *)ph, (unsigned long long)(r[i].poff - r[i].voff));
if (r[i].voff < 0 || (uint64_t)r[i].voff + r[i].size > o->size || (uint64_t)r[i].poff + r[i].size > ph->size) { LOG("[RED] MapVirtual: request out of range (virtual %#llx+%#llx / pool %#llx, physical %#llx / pool %#llx)", (unsigned long long)r[i].voff, (unsigned long long)r[i].size, (unsigned long long)o->size, (unsigned long long)r[i].poff, (unsigned long long)ph->size); return 0; }
if (o->mem && o->mem != base) { LOG("[RED] MapVirtual: non-contiguous mapping (true sparse) not implemented: virtual pool %p already maps to %p, this request wants %p", (void *)o, (void *)o->mem, (void *)base); return 0; }
if (!o->mem) LOG("[nvn] MapVirtual: the storage of virtual pool %p (%#llx bytes) points at physical pool %p + %#llx", (void *)o, (unsigned long long)o->size, (const void *)ph, (unsigned long long)(r[i].poff - r[i].voff));
o->mem = base;
}
return 1;
}
// Samplers: reduction (min / max) does not exist on Metal samplers, logged once; the getters read the parameters in the object
NVN(nvnSamplerBuilderSetReductionFilter, void, NVNsamplerBuilder *b, NVNsamplerReduction r) { (void)b; if (r) ONCE_LOG("SamplerBuilderSetReductionFilter %d:宿主按平均采样(min/max 归约不支持)", (int)r); }
NVN(nvnSamplerBuilderSetReductionFilter, void, NVNsamplerBuilder *b, NVNsamplerReduction r) { (void)b; if (r) ONCE_LOG("SamplerBuilderSetReductionFilter %d: the host samples with averaging (min/max reduction unsupported)", (int)r); }
NVN(nvnSamplerSetDebugLabel, void, NVNsampler *s, const char *l) { (void)s; (void)l; }
#define SP(s) (&((const samp_t *)(s))->p)
NVN(nvnSamplerGetMinMagFilter, void, const NVNsampler *s, NVNminFilter *mi, NVNmagFilter *ma) { *mi = (NVNminFilter)SP(s)->minf; *ma = (NVNmagFilter)SP(s)->magf; }
@@ -1552,9 +1552,9 @@ NVN(nvnTextureBuilderSetSize2D, void, NVNtextureBuilder *b, int w, int h) { TB(b
NVN(nvnTextureBuilderSetSize3D, void, NVNtextureBuilder *b, int w, int h, int d) { TB(b)->d.width = w; TB(b)->d.height = h; TB(b)->d.depth = d; }
NVN(nvnTextureBuilderSetSwizzle, void, NVNtextureBuilder *b, NVNtextureSwizzle r, NVNtextureSwizzle g, NVNtextureSwizzle bl, NVNtextureSwizzle a) {
tex_builder_t *o = (tex_builder_t *)b; o->set_swz = 1; o->swz = (uint16_t)((r & 7) | (g & 7) << 3 | (bl & 7) << 6 | (a & 7) << 9);
if (r != NVN_TEXTURE_SWIZZLE_R || g != NVN_TEXTURE_SWIZZLE_G || bl != NVN_TEXTURE_SWIZZLE_B || a != NVN_TEXTURE_SWIZZLE_A) ONCE_LOG("TextureBuilderSetSwizzle 非恒等(%d %d %d %d):采样时用 Metal 视图重排", (int)r, (int)g, (int)bl, (int)a);
if (r != NVN_TEXTURE_SWIZZLE_R || g != NVN_TEXTURE_SWIZZLE_G || bl != NVN_TEXTURE_SWIZZLE_B || a != NVN_TEXTURE_SWIZZLE_A) ONCE_LOG("TextureBuilderSetSwizzle not identity (%d %d %d %d): remapped with a Metal view when sampling", (int)r, (int)g, (int)bl, (int)a);
}
NVN(nvnTextureBuilderSetDepthStencilMode, void, NVNtextureBuilder *b, NVNtextureDepthStencilMode m) { (void)b; if (m) ONCE_LOG("TextureBuilderSetDepthStencilMode %d:宿主按深度读", (int)m); }
NVN(nvnTextureBuilderSetDepthStencilMode, void, NVNtextureBuilder *b, NVNtextureDepthStencilMode m) { (void)b; if (m) ONCE_LOG("TextureBuilderSetDepthStencilMode %d: the host reads depth", (int)m); }
NVN(nvnTextureBuilderGetStorageClass, NVNstorageClass, const NVNtextureBuilder *b) { (void)b; return 0; }
// Used by Bowser's Fury: the texture's channel swizzle (set by TextureBuilderSetSwizzle at build time, identity R G B A when unset) and storage class (the host has none, 0 as for the builder)
NVN(nvnTextureGetSwizzle, void, const NVNtexture *t, NVNtextureSwizzle *r, NVNtextureSwizzle *g, NVNtextureSwizzle *b, NVNtextureSwizzle *a) {
@@ -1570,10 +1570,10 @@ NVN(nvnTextureGetTarget, NVNtextureTarget, const NVNtexture *t) { return (NVNtex
NVN(nvnTextureSetDebugLabel, void, NVNtexture *t, const char *s) { (void)t; (void)s; }
NVN(nvnTextureViewGetLevels, NVNboolean, const NVNtextureView *v, int *base, int *n) { const view_t *w = (const view_t *)v; *base = w->base_level; *n = w->nlevels; return w->set_levels; }
NVN(nvnTextureViewGetTarget, NVNboolean, const NVNtextureView *v, NVNtextureTarget *t) { const view_t *w = (const view_t *)v; *t = (NVNtextureTarget)w->target; return w->set_target; }
NVN(nvnTextureViewSetDepthStencilMode, void, NVNtextureView *v, NVNtextureDepthStencilMode m) { VW(v)->set_dsmode = 1; VW(v)->dsmode = (uint8_t)m; if (m) ONCE_LOG("TextureViewSetDepthStencilMode %d:宿主按深度读", (int)m); }
NVN(nvnTextureViewSetDepthStencilMode, void, NVNtextureView *v, NVNtextureDepthStencilMode m) { VW(v)->set_dsmode = 1; VW(v)->dsmode = (uint8_t)m; if (m) ONCE_LOG("TextureViewSetDepthStencilMode %d: the host reads depth", (int)m); }
NVN(nvnTextureViewSetSwizzle, void, NVNtextureView *v, NVNtextureSwizzle r, NVNtextureSwizzle g, NVNtextureSwizzle bl, NVNtextureSwizzle a) {
view_t *w = VW(v); w->set_swizzle = 1; w->swz[0] = (uint8_t)r; w->swz[1] = (uint8_t)g; w->swz[2] = (uint8_t)bl; w->swz[3] = (uint8_t)a;
if (r != NVN_TEXTURE_SWIZZLE_R || g != NVN_TEXTURE_SWIZZLE_G || bl != NVN_TEXTURE_SWIZZLE_B || a != NVN_TEXTURE_SWIZZLE_A) ONCE_LOG("TextureViewSetSwizzle 非恒等(%d %d %d %d):采样时用 Metal 视图重排", (int)r, (int)g, (int)bl, (int)a);
if (r != NVN_TEXTURE_SWIZZLE_R || g != NVN_TEXTURE_SWIZZLE_G || bl != NVN_TEXTURE_SWIZZLE_B || a != NVN_TEXTURE_SWIZZLE_A) ONCE_LOG("TextureViewSetSwizzle not identity (%d %d %d %d): remapped with a Metal view when sampling", (int)r, (int)g, (int)bl, (int)a);
}
// Color state: alpha test function, logic op (the fields already exist in color_st)
@@ -1589,8 +1589,8 @@ NVN(nvnDeviceGetCurrentTimestampInNanoseconds, uint64_t, const NVNdevice *d) { (
NVN(nvnDeviceRegisterFastClearColor, NVNboolean, NVNdevice *d, const float *c, NVNformat f) { (void)d; (void)c; (void)f; return 1; }
// Window: number of active swapchain textures (the host rotates through the count given at creation; logged once)
NVN_NOPROTO(nvnWindowBuilderSetNumActiveTextures, void, NVNwindowBuilder *b, int n) { (void)b; ONCE_LOG("WindowBuilderSetNumActiveTextures %d(宿主按 SetTextures 的张数轮转)", n); }
NVN_NOPROTO(nvnWindowSetNumActiveTextures, void, NVNwindow *w, int n) { (void)w; ONCE_LOG("WindowSetNumActiveTextures %d(宿主按 SetTextures 的张数轮转)", n); }
NVN_NOPROTO(nvnWindowBuilderSetNumActiveTextures, void, NVNwindowBuilder *b, int n) { (void)b; ONCE_LOG("WindowBuilderSetNumActiveTextures %d (the host rotates through the SetTextures count)", n); }
NVN_NOPROTO(nvnWindowSetNumActiveTextures, void, NVNwindow *w, int n) { (void)w; ONCE_LOG("WindowSetNumActiveTextures %d (the host rotates through the SetTextures count)", n); }
// Command buffer memory usage: record bytes written in this recording (accumulated across segments; the host does not use control memory, always 0); the memory callback switch is ignored.
// It used to be cur - begin: once a recording overflowed into a host segment, cur is in host memory (an address below the guest begin) and it returned 0. SMBW asks for this value after recording each material setup handle,
@@ -1608,9 +1608,9 @@ NVN_NOPROTO(nvnCommandBufferSetCommandMemoryCallbackEnabled, void, NVNcommandBuf
// The host identifies stages by the control section and does not use the extension field: convert to plain NVNshaderData and hand it to SetShaders.
typedef struct { NVNbufferAddress data; const void *control; uint64_t ext; } shader_data_ext;
NVN_NOPROTO(nvnProgramSetShadersExt, NVNboolean, NVNprogram *p, int n, const shader_data_ext *sd) {
if (n < 0 || n > 6) { LOG("【红】ProgramSetShadersExt 阶段数 %d", n); return 0; }
if (n < 0 || n > 6) { LOG("[RED] ProgramSetShadersExt stage count %d", n); return 0; }
NVNshaderData plain[6];
for (int i = 0; i < n; i++) { plain[i].data = sd[i].data; plain[i].control = sd[i].control; if (sd[i].ext) ONCE_LOG("ProgramSetShadersExt 扩展字段非 0(%#llx):宿主不用", (unsigned long long)sd[i].ext); }
for (int i = 0; i < n; i++) { plain[i].data = sd[i].data; plain[i].control = sd[i].control; if (sd[i].ext) ONCE_LOG("ProgramSetShadersExt extension field nonzero (%#llx): unused by the host", (unsigned long long)sd[i].ext); }
return h_nvnProgramSetShaders(p, n, plain);
}
@@ -1627,7 +1627,7 @@ NVN(nvnCommandBufferBindTexture, void, NVNcommandBuffer *c, NVNshaderStage st, i
NVN_NOPROTO(nvnCommandBufferBindTexturesDeferred, void, NVNcommandBuffer *c, NVNshaderStage st, int first, int n, const NVNtextureHandle *h) {
p_bind_handles *r = bind_handles(c, OP_BIND_TEX, st, first, n, (const uint64_t *)h);
if (r && h) { r->defer = 1; r->src = (uint64_t)(uintptr_t)h; }
if (g_ncbpool) for (int i = 0; i < g_ncbpool; i++) if (g_cbpool[i].cmd == (const void *)c && (g_cbpool[i].tb || g_cbpool[i].sb)) ONCE_LOG("【红】延后绑定的句柄在回放时才读:不加这个命令缓冲所绑描述符池的编号基数(池 %d / %d)", g_cbpool[i].tb, g_cbpool[i].sb);
if (g_ncbpool) for (int i = 0; i < g_ncbpool; i++) if (g_cbpool[i].cmd == (const void *)c && (g_cbpool[i].tb || g_cbpool[i].sb)) ONCE_LOG("[RED] deferred-bound handles are read only at replay: the numbering base of this command buffer's bound descriptor pools is not added (pools %d / %d)", g_cbpool[i].tb, g_cbpool[i].sb);
}
NVN(nvnCommandBufferBindImage, void, NVNcommandBuffer *c, NVNshaderStage st, int i, NVNimageHandle h) { uint64_t v = (uint64_t)h; bind_handles(c, OP_BIND_IMG, st, i, 1, &v); }
NVN(nvnCommandBufferBindVertexBuffer, void, NVNcommandBuffer *c, int i, NVNbufferAddress a, size_t n) { NVNbufferRange r = {a, n}; h_nvnCommandBufferBindVertexBuffers(c, i, 1, &r); }
@@ -1647,11 +1647,11 @@ NVN(nvnCommandBufferDrawElementsIndirect, void, NVNcommandBuffer *c, NVNdrawPrim
NVN(nvnCommandBufferSetViewport, void, NVNcommandBuffer *c, int x, int y, int w, int h) { float v[4] = {(float)x, (float)y, (float)w, (float)h}; h_nvnCommandBufferSetViewports(c, 0, 1, v); }
NVN(nvnCommandBufferSetScissors, void, NVNcommandBuffer *c, int first, int n, const int *r) {
if (first == 0 && n >= 1) h_nvnCommandBufferSetScissor(c, r[0], r[1], r[2], r[3]);
if (first + n > 1) ONCE_LOG("SetScissors first=%d n=%d:宿主只用 0 号", first, n);
if (first + n > 1) ONCE_LOG("SetScissors first=%d n=%d: the host uses only index 0", first, n);
}
NVN(nvnCommandBufferSetDepthRanges, void, NVNcommandBuffer *c, int first, int n, const float *r) {
if (first == 0 && n >= 1) h_nvnCommandBufferSetDepthRange(c, r[0], r[1]);
if (first + n > 1) ONCE_LOG("SetDepthRanges first=%d n=%d:宿主只用 0 号", first, n);
if (first + n > 1) ONCE_LOG("SetDepthRanges first=%d n=%d: the host uses only index 0", first, n);
}
// GPU value write: ReportValue = write a 32-bit value to the address when this point is reached (the replay layer's event write, applied on completion)
@@ -1667,20 +1667,20 @@ NVN(nvnCommandBufferClearBuffer, void, NVNcommandBuffer *c, NVNbufferAddress a,
// Render enable (profile render_cond, see OP_RENDER_COND in nvn_mtl.m): recorded and evaluated at replay; without the key everything draws, as before
NVN(nvnCommandBufferSetRenderEnable, void, NVNcommandBuffer *c, uint32_t en) {
if (DM_GAME_RENDER_COND) *REC(c, OP_RENDER_COND, p_render_cond) = (p_render_cond){(en & 0xff) ? 2 : 3, 0, 0};
else if (!(en & 0xff)) ONCE_LOG("【红】SetRenderEnable(false):宿主暂按恒画");
else if (!(en & 0xff)) ONCE_LOG("[RED] SetRenderEnable(false): the host draws anyway for now");
}
NVN(nvnCommandBufferSetRenderEnableConditional, void, NVNcommandBuffer *c, NVNconditionalRenderMode m, NVNbufferAddress a) {
if (DM_GAME_RENDER_COND) *REC(c, OP_RENDER_COND, p_render_cond) = (p_render_cond){(int)m & 1, 0, a};
else ONCE_LOG("【红】SetRenderEnableConditional:宿主暂按恒画");
else ONCE_LOG("[RED] SetRenderEnableConditional: the host draws anyway for now");
}
// Fire Emblem: Three Houses turns depth clamp on for its scenes; ignored, the ground right under its close-up battle cameras was clipped away and the lighting pass painted the sky's fog colour there
NVN(nvnCommandBufferSetDepthClamp, void, NVNcommandBuffer *c, uint32_t en) { REC(c, OP_DEPTH_CLAMP, p_depth_clamp)->on = (en & 0xff) != 0; }
NVN(nvnCommandBufferSetLineWidth, void, NVNcommandBuffer *c, float w) { (void)c; if (w != 1.0f) ONCE_LOG("SetLineWidth %.2f:Metal 只有 1 像素线", w); }
NVN(nvnCommandBufferSetPointSize, void, NVNcommandBuffer *c, float s) { (void)c; (void)s; ONCE_LOG("SetPointSize:点大小由着色器给"); }
NVN(nvnCommandBufferSetPrimitiveRestart, void, NVNcommandBuffer *c, uint32_t en, int idx) { (void)c; if ((en & 0xff) && idx != -1 && idx != 0xffff) ONCE_LOG("【红】SetPrimitiveRestart 索引 %#x:Metal 只认全 1", idx); }
NVN(nvnCommandBufferSetRasterizerDiscard, void, NVNcommandBuffer *c, uint32_t en) { (void)c; if (en & 0xff) ONCE_LOG("【红】SetRasterizerDiscard 开:宿主暂忽略"); }
NVN(nvnCommandBufferSetSampleMask, void, NVNcommandBuffer *c, int m) { (void)c; if ((m & 0xffff) != 0xffff) ONCE_LOG("SetSampleMask %#x:宿主暂忽略", m); }
NVN(nvnCommandBufferSetConservativeRasterEnable, void, NVNcommandBuffer *c, uint32_t en) { (void)c; if (en & 0xff) ONCE_LOG("SetConservativeRasterEnable 开:宿主暂忽略"); }
NVN(nvnCommandBufferSetLineWidth, void, NVNcommandBuffer *c, float w) { (void)c; if (w != 1.0f) ONCE_LOG("SetLineWidth %.2f: Metal has only 1-pixel lines", w); }
NVN(nvnCommandBufferSetPointSize, void, NVNcommandBuffer *c, float s) { (void)c; (void)s; ONCE_LOG("SetPointSize: the point size comes from the shader"); }
NVN(nvnCommandBufferSetPrimitiveRestart, void, NVNcommandBuffer *c, uint32_t en, int idx) { (void)c; if ((en & 0xff) && idx != -1 && idx != 0xffff) ONCE_LOG("[RED] SetPrimitiveRestart index %#x: Metal only accepts all ones", idx); }
NVN(nvnCommandBufferSetRasterizerDiscard, void, NVNcommandBuffer *c, uint32_t en) { (void)c; if (en & 0xff) ONCE_LOG("[RED] SetRasterizerDiscard on: ignored by the host for now"); }
NVN(nvnCommandBufferSetSampleMask, void, NVNcommandBuffer *c, int m) { (void)c; if ((m & 0xffff) != 0xffff) ONCE_LOG("SetSampleMask %#x: ignored by the host for now", m); }
NVN(nvnCommandBufferSetConservativeRasterEnable, void, NVNcommandBuffer *c, uint32_t en) { (void)c; if (en & 0xff) ONCE_LOG("SetConservativeRasterEnable on: ignored by the host for now"); }
NVN(nvnCommandBufferSetTiledCacheAction, void, NVNcommandBuffer *c, NVNtiledCacheAction a) { (void)c; (void)a; } // Tegra tiled cache hint, no Metal equivalent
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
@@ -1717,9 +1717,9 @@ static int write_region_bl(const tex_t *t, unsigned level, unsigned layer, const
return 0;
}
static void write_texels(const tex_t *t, const view_t *v, const NVNcopyRegion *r, const uint8_t *src, size_t row, size_t img) {
if (!t->mem) { LOG("【红】WriteTexels:贴图没有存储"); return; }
if (!t->mem) { LOG("[RED] WriteTexels: texture has no storage"); return; }
int bw, bh, bpb = nvn_fmt_bpb(t->d.format, &bw, &bh);
if (!bpb) { ONCE_LOG("【红】WriteTexels:未知格式 %#x", t->d.format); return; }
if (!bpb) { ONCE_LOG("[RED] WriteTexels: unknown format %#x", t->d.format); return; }
unsigned level = v && v->set_levels ? (unsigned)v->base_level : 0, layer0 = v && v->set_layers ? (unsigned)v->min_layer : 0;
int is3d = t->d.target == NVN_TEXTURE_TARGET_3D, nz = r->depth ? r->depth : 1;
if (!is3d) {
@@ -1734,13 +1734,13 @@ static void write_texels(const tex_t *t, const view_t *v, const NVNcopyRegion *r
size_t wb = (size_t)((r->width + bw - 1) / bw) * bpb, hb = (size_t)(r->height + bh - 1) / bh;
if (!row) row = wb;
if (!img) img = row * hb;
uint8_t *lin = malloc(lp * lr * lz); if (!lin) die("WriteTexels 分配失败");
uint8_t *lin = malloc(lp * lr * lz); if (!lin) die("WriteTexels allocation failed");
dm_guest_write_prepare(t->mem, t->L.size);
// when the region is exactly the whole level (2D) every texel is overwritten, so the old content need not be decoded first (movies write whole frames; saves one deswizzle per 1080p frame)
int whole = !is3d && !r->xoffset && !r->yoffset && (uint32_t)r->width == lw && (uint32_t)r->height == lh;
for (int z = 0; z < nz; z++) {
unsigned layer = is3d ? 0 : layer0 + (unsigned)(r->zoffset + z);
if (((is3d && z == 0) || !is3d) && !whole) if (nvn_deswizzle(&t->d, level, layer, t->mem, lin, lp)) { ONCE_LOG("【红】WriteTexels 解排布失败(格式 %#x 目标 %d)", t->d.format, t->d.target); free(lin); return; }
if (((is3d && z == 0) || !is3d) && !whole) if (nvn_deswizzle(&t->d, level, layer, t->mem, lin, lp)) { ONCE_LOG("[RED] WriteTexels deswizzle failed (format %#x target %d)", t->d.format, t->d.target); free(lin); return; }
uint8_t *dst = lin + (is3d ? (size_t)(r->zoffset + z) * lp * lr : 0) + (size_t)(r->yoffset / bh) * lp + (size_t)(r->xoffset / bw) * bpb;
for (size_t y = 0; y < hb; y++) memcpy(dst + y * lp, src + (size_t)z * img + y * row, wb);
if (!is3d || z == nz - 1) nvn_swizzle(&t->d, level, layer, lin, lp, t->mem);
+4 -4
View File
@@ -54,7 +54,7 @@ static int busy_wait(range_t *r, uintptr_t p) {
if (n & 0x3ff) continue;
uint64_t now = mach_absolute_time(); if (!t0) t0 = now;
if ((now - t0) * g_tb.numer / g_tb.denom > (uint64_t)BUSY_WAIT_MS * 1000000ull) {
if (!atomic_exchange(&g_busy_red, 1)) LOG("【红】脏页跟踪:页 %p 停在「忙」超过 %d 毫秒(改保护的线程没走完),不再等", (void *)(r->base + (p << g_shift)), BUSY_WAIT_MS);
if (!atomic_exchange(&g_busy_red, 1)) LOG("[RED] dirty-page tracking: page %p stuck 'busy' for over %d ms (the thread changing its protection has not finished), no longer waiting", (void *)(r->base + (p << g_shift)), BUSY_WAIT_MS);
return 0; }
sched_yield();
}
@@ -128,12 +128,12 @@ void nvn_dirty_add_range(const void *mem, size_t size) {
if (slot < 0) {
if (n >= MAX_RANGE) {
int expected = 0;
if (atomic_compare_exchange_strong(&g_overflow_logged, &expected, 1)) LOG("【红】脏页跟踪区间已满(%d),池 %p 不跟踪", MAX_RANGE, mem);
if (atomic_compare_exchange_strong(&g_overflow_logged, &expected, 1)) LOG("[RED] dirty-page tracking ranges are full (%d), pool %p not tracked", MAX_RANGE, mem);
pthread_mutex_unlock(&g_slot_mu); return;
}
slot = n;
if (!atomic_load_explicit(&g_blk[n >> BLK_SHIFT], memory_order_acquire)) { // published before g_nr counts the slot (the scans read g_nr first)
range_t *b = calloc(BLK, sizeof *b); if (!b) die("脏页跟踪区间表分配失败(第 %d 块)", n >> BLK_SHIFT);
range_t *b = calloc(BLK, sizeof *b); if (!b) die("dirty-page tracking range table allocation failed (block %d)", n >> BLK_SHIFT);
atomic_store_explicit(&g_blk[n >> BLK_SHIFT], b, memory_order_release);
}
}
@@ -145,7 +145,7 @@ void nvn_dirty_add_range(const void *mem, size_t size) {
_Atomic uint64_t *gen = r->gen;
if (r->cap < np) {
state = calloc(np, 1); gen = calloc(np, sizeof *gen);
if (!state || !gen) die("脏页表分配失败(%zu 页)", np);
if (!state || !gen) die("dirty-page table allocation failed (%zu pages)", np);
// The old array is not freed: the signal handler may still have a very brief stale reader; leak = 9 bytes per page.
r->cap = np;
} else {
+423 -423
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -8,7 +8,7 @@
#include "nvn_tex.h"
#define MAGIC(a, b, c, d) ((uint32_t)(a) | (uint32_t)(b) << 8 | (uint32_t)(c) << 16 | (uint32_t)(d) << 24)
#define FITS(T, H) _Static_assert(sizeof(T) <= sizeof(H), #T " 超出 " #H " 的保留大小")
#define FITS(T, H) _Static_assert(sizeof(T) <= sizeof(H), #T " exceeds the reserved size of " #H)
typedef struct { uint32_t flags; } dev_builder_t; FITS(dev_builder_t, NVNdeviceBuilder);
typedef struct { uint32_t magic, flags, origin, depth_mode; void *dbg_cb, *dbg_data; } dev_t_; FITS(dev_t_, NVNdevice);
@@ -36,13 +36,13 @@ typedef struct {
uint32_t magic; nvn_texdesc d; uint8_t has_layout, set_swz; uint16_t swz; nvn_texlayout L; uint8_t *mem; pool_t *pool; int64_t off; uint8_t layout[8];
uint64_t serial; // Initialization serial: the replay layer uses it to find the MTLTexture in the host table (the game may copy the object by value, so object pointers cannot be stored)
} tex_t; FITS(tex_t, NVNtexture);
_Static_assert(sizeof(tex_t) <= 0x80, "tex_t 超出较新 SDK 的 NVNtexture(0x80)");
_Static_assert(sizeof(tex_t) <= 0x80, "tex_t exceeds the newer SDKs' NVNtexture (0x80)");
// The newer SDK's (NVN 55, SMBW) NVNtextureView is only 16 bytes: the game keeps it in a 16-byte stack slot (main +0x5796c), and the former 28-byte record wrote out of bounds over the saved fp / lr,
// so the function returned to 0x100000000 and crashed. Packed into 16 bytes: levels / layers / format / target + swizzle and depth-stencil mode (the last two only recorded for now)
typedef struct { uint8_t set_levels : 1, set_layers : 1, set_format : 1, set_target : 1, set_swizzle : 1, set_dsmode : 1, dsmode : 2;
uint8_t target, base_level, nlevels; uint16_t format, min_layer, nlayers; uint8_t swz[4]; uint16_t _rsv; } view_t;
FITS(view_t, NVNtextureView);
_Static_assert(sizeof(view_t) <= 16, "view_t 超出较新 SDK 的 NVNtextureView(16 字节)");
_Static_assert(sizeof(view_t) <= 16, "view_t exceeds the newer SDKs' NVNtextureView (16 bytes)");
// Sampler parameters (one copy shared by builder and object)
typedef struct { uint8_t minf, magf, ws, wt, wr, cmp_mode, cmp_func; float lod_min, lod_max, lod_bias, aniso, border[4]; } samp_par; // Enum values are all < 256
+11 -11
View File
@@ -20,11 +20,11 @@ static void dummy_kind(NSString *t, uint8_t *tt, uint8_t *pf) {
int nvn_refl_from_json(NSDictionary *j, nvn_refl *o, char *err, int errlen) {
memset(o, 0, sizeof *o);
#define FAIL(...) do { free(o->c1); o->c1 = NULL; snprintf(err, errlen, __VA_ARGS__); return 1; } while (0)
if (![j[@"route"] isEqual:@"B"]) FAIL("不是 B 反射(route ≠ B;A 路线 / PC 链反射已退役)");
if (![j[@"route"] isEqual:@"B"]) FAIL("not a B reflection (route ≠ B; route A / PC-chain reflection is retired)");
{ // Translated original Switch shaders: kind and NVN slot number are already parsed from the MSL member names by dmsh_build.py
NSArray *bs = j[@"bindings"];
if (![bs isKindOfClass:[NSArray class]]) FAIL("缺 bindings");
if (bs.count > NVN_REFL_MAXB) FAIL("绑定 %lu 个,超上限 %d", (unsigned long)bs.count, NVN_REFL_MAXB);
if (![bs isKindOfClass:[NSArray class]]) FAIL("missing bindings");
if (bs.count > NVN_REFL_MAXB) FAIL("%lu bindings, over the limit %d", (unsigned long)bs.count, NVN_REFL_MAXB);
o->route_b = 1; o->abi = 0; // spirv-cross set 0 → [[buffer(0)]]
for (NSDictionary *b in bs) {
NSString *k = b[@"kind"]; nvn_rbind *r = &o->b[o->nb];
@@ -43,9 +43,9 @@ int nvn_refl_from_json(NSDictionary *j, nvn_refl *o, char *err, int errlen) {
else if ([k isEqualToString:@"support"]) r->kind = NVN_RB_SUPPORT;
else if ([k isEqualToString:@"c1"]) r->kind = NVN_RB_C1;
else if ([k isEqualToString:@"drvcb"]) r->kind = [b[@"bank"] intValue] == 2 ? NVN_RB_DRV2 : NVN_RB_DRV0;
else FAIL("B 绑定种类 %s 未实现(%s)", k.UTF8String, [b[@"msl_name"] UTF8String]);
else FAIL("B binding kind %s not implemented (%s)", k.UTF8String, [b[@"msl_name"] UTF8String]);
if ((r->kind == NVN_RB_CBUF || r->kind == NVN_RB_SAMP || r->kind == NVN_RB_SAMPT || r->kind == NVN_RB_TEX || r->kind == NVN_RB_IMG || r->kind == NVN_RB_TBUF || r->kind == NVN_RB_SBUFP || r->kind == NVN_RB_UBOP || r->kind == NVN_RB_TEXU || r->kind == NVN_RB_SAMPU) && reg < 0)
FAIL("B 绑定 %s 没解出槽号", [b[@"msl_name"] UTF8String]);
FAIL("B binding %s has no resolved slot number", [b[@"msl_name"] UTF8String]);
o->nb++;
}
NSDictionary *dr = j[@"drv_reads"];
@@ -54,14 +54,14 @@ int nvn_refl_from_json(NSDictionary *j, nvn_refl *o, char *err, int errlen) {
// shaders reading c0 got an all-zero segment (Link's Awakening's light culling multiplied the tile row by a zero workgroup count: every row's lights landed in row 0)
for (int i = 0; i < o->nb; i++) { if (o->b[i].kind == NVN_RB_DRV0) o->drv_read[0] = 1; else if (o->b[i].kind == NVN_RB_DRV2) o->drv_read[2] = 1; }
NSArray *uf = j[@"drv_unfillable"];
if ([uf isKindOfClass:[NSArray class]] && uf.count) FAIL("运行时读驱动段的回放层填不了的位置:%s", [[uf componentsJoinedByString:@" "] UTF8String]);
if ([uf isKindOfClass:[NSArray class]] && uf.count) FAIL("reads driver-bank locations at run time that the replay layer cannot fill: %s", [[uf componentsJoinedByString:@" "] UTF8String]);
NSString *c1 = j[@"c1"];
if (c1.length % 2 || c1.length / 2 > 0xffff) FAIL("c1 长 %lu 字节,超上限或不成对", (unsigned long)c1.length / 2);
if (c1.length && !(o->c1 = malloc(c1.length / 2))) FAIL("c1 %lu 字节分配失败", (unsigned long)c1.length / 2);
if (c1.length % 2 || c1.length / 2 > 0xffff) FAIL("c1 is %lu bytes: over the limit or an odd hex length", (unsigned long)c1.length / 2);
if (c1.length && !(o->c1 = malloc(c1.length / 2))) FAIL("c1 allocation of %lu bytes failed", (unsigned long)c1.length / 2);
if (c1.length) { o->has_c1 = 1; o->c1_len = (uint16_t)(c1.length / 2); for (unsigned i = 0; i < o->c1_len; i++) o->c1[i] = (uint8_t)strtoul([[c1 substringWithRange:NSMakeRange(2 * i, 2)] UTF8String], NULL, 16); }
for (NSDictionary *v in j[@"vertex_inputs"]) { // [[attribute(n)]]: n is the NVN vertex attribute number
int loc = [v[@"location"] intValue]; NSString *t = v[@"msl_type"];
if (loc < 0 || loc >= NVN_REFL_MAXV || o->nv >= NVN_REFL_MAXV) FAIL("B 顶点输入 %d 越界", loc);
if (loc < 0 || loc >= NVN_REFL_MAXV || o->nv >= NVN_REFL_MAXV) FAIL("B vertex input %d out of range", loc);
o->v[o->nv].loc = o->v[o->nv].nvn = (uint8_t)loc;
o->v[o->nv].ct = [t hasPrefix:@"uint"] ? NVN_CT_UINT : [t hasPrefix:@"int"] ? NVN_CT_INT : NVN_CT_FLOAT;
o->nv++;
@@ -89,10 +89,10 @@ int nvn_refl_from_json(NSDictionary *j, nvn_refl *o, char *err, int errlen) {
NSDictionary *fe = j[@"fetch"];
if ([fe isKindOfClass:[NSDictionary class]]) {
for (NSDictionary *c in fe[@"calls"]) { int i = [c[@"fc"] intValue], r = [c[@"tex_reg"] intValue], l = [c[@"varying_locn"] intValue];
if (i != o->nfc || i >= 32 || r < 0 || r >= 128 || l < 0 || l >= 32) FAIL("fetch 调用表不连续或越界(第 %d 条)", i);
if (i != o->nfc || i >= 32 || r < 0 || r >= 128 || l < 0 || l >= 32) FAIL("fetch call table not contiguous or out of range (entry %d)", i);
o->fc_reg[o->nfc] = (uint8_t)r; o->fc_locn[o->nfc] = (uint8_t)l; o->nfc++; }
for (NSNumber *r in fe[@"mixed_regs"]) if (r.intValue >= 0 && r.intValue < 128) o->fc_mixed[r.intValue >> 6] |= 1ull << (r.intValue & 63);
for (NSDictionary *u in fe[@"outputs"]) { if (o->nfo >= 4) FAIL("fetch 输出超过 4 个"); o->fo_color[o->nfo++] = (uint8_t)[u[@"color"] intValue]; }
for (NSDictionary *u in fe[@"outputs"]) { if (o->nfo >= 4) FAIL("more than 4 fetch outputs"); o->fo_color[o->nfo++] = (uint8_t)[u[@"color"] intValue]; }
}
NSArray *tg = j[@"threadgroup_size"];
if ([tg isKindOfClass:[NSArray class]] && tg.count == 3) for (int i = 0; i < 3; i++) o->tg[i] = (uint16_t)[tg[i] intValue];
+5 -5
View File
@@ -64,12 +64,12 @@ static int rx_setup(MTLPixelFormat fc, MTLPixelFormat fds) {
if (g_rx_pso1 && g_rx_fmt_c == fc && g_rx_fmt_ds == fds) return 1;
static int failed; if (failed) return 0;
NSError *e = nil; id<MTLLibrary> lib = [g_dev newLibraryWithSource:@(k_rx_msl) options:nil error:&e];
if (!lib) { MLOG("【红】运行时外推:着色器编译失败 %s", e.localizedDescription.UTF8String); failed = 1; return 0; }
if (!lib) { MLOG("[RED] runtime extrapolation: shader compile failed %s", e.localizedDescription.UTF8String); failed = 1; return 0; }
MTLRenderPipelineDescriptor *pd = [MTLRenderPipelineDescriptor new]; pd.vertexFunction = [lib newFunctionWithName:@"rx_vs"]; pd.fragmentFunction = [lib newFunctionWithName:@"rx_fs1"];
pd.colorAttachments[0].pixelFormat = fc; pd.depthAttachmentPixelFormat = fds;
if (fds == MTLPixelFormatDepth32Float_Stencil8) pd.stencilAttachmentPixelFormat = fds;
g_rx_pso1 = [g_dev newRenderPipelineStateWithDescriptor:pd error:&e];
if (!g_rx_pso1) { MLOG("【红】运行时外推:管线建不出 %s", e.localizedDescription.UTF8String); g_rx_pso1 = nil; failed = 1; return 0; }
if (!g_rx_pso1) { MLOG("[RED] runtime extrapolation: cannot build the pipeline %s", e.localizedDescription.UTF8String); g_rx_pso1 = nil; failed = 1; return 0; }
MTLDepthStencilDescriptor *dd = [MTLDepthStencilDescriptor new]; dd.depthCompareFunction = MTLCompareFunctionAlways; dd.depthWriteEnabled = YES;
if (fds == MTLPixelFormatDepth32Float_Stencil8) { MTLStencilDescriptor *sd = [MTLStencilDescriptor new]; sd.stencilCompareFunction = MTLCompareFunctionEqual; sd.readMask = 0x2; sd.writeMask = 0x80; sd.depthStencilPassOperation = MTLStencilOperationReplace; // Hole pixels / pixels newly entering from off-screen get 0x80 exported by the fragment, and the later LDR region is recomputed accordingly // Pixels covered by this frame's UI (the UI stencil pass already wrote 0x2) do not count
dd.frontFaceStencil = sd; dd.backFaceStencil = sd; }
@@ -77,8 +77,8 @@ static int rx_setup(MTLPixelFormat fc, MTLPixelFormat fds) {
if (!g_rx_args) { MTL4ArgumentTableDescriptor *ad = [MTL4ArgumentTableDescriptor new]; ad.maxTextureBindCount = 3; ad.maxBufferBindCount = 1;
g_rx_args = [g_dev newArgumentTableWithDescriptor:ad error:&e];
for (int i = 0; i < 3; i++) { g_rx_const[i] = [g_dev newBufferWithLength:256 options:MTLResourceStorageModeShared]; res_add(g_rx_const[i]); } }
if (!g_rx_args || !g_rx_dss) { MLOG("【红】运行时外推:参数表 / 深度状态建不出"); g_rx_pso1 = nil; failed = 1; return 0; }
g_rx_fmt_c = fc; g_rx_fmt_ds = fds; MLOG("运行时外推:着色器建好(颜色格式 %lu,深度格式 %lu)", (unsigned long)fc, (unsigned long)fds);
if (!g_rx_args || !g_rx_dss) { MLOG("[RED] runtime extrapolation: cannot build the argument table / depth state"); g_rx_pso1 = nil; failed = 1; return 0; }
g_rx_fmt_c = fc; g_rx_fmt_ds = fds; MLOG("runtime extrapolation: shaders built (color format %lu, depth format %lu)", (unsigned long)fc, (unsigned long)fds);
return 1;
}
static void rx_inv4(const double *m, double *o) { // 4×4 inverse (adjugate method, double precision)
@@ -111,7 +111,7 @@ int rt_ex_encode(id<MTLTexture> dst_hdr, id<MTLTexture> dst_ds, id<MTLTexture> s
if (g_rx_stv_src != src_ds) {
if (g_rx_stv) defer_obj_release(g_rx_stv);
g_rx_stv = [src_ds newTextureViewWithPixelFormat:MTLPixelFormatX32_Stencil8];
if (!g_rx_stv) { static int n; if (!n++) MLOG("【红】运行时外推:开不出模板视图(src_ds 要带 PixelFormatView 用途)"); return 0; }
if (!g_rx_stv) { static int n; if (!n++) MLOG("[RED] runtime extrapolation: cannot create the stencil view (src_ds needs the PixelFormatView usage)"); return 0; }
res_add(g_rx_stv); g_rx_stv_src = src_ds;
}
// Constant: M = inv(vp_prev) · vp_cur (row vectors: clip_cur = (ndc_prev, z, 1) · M); Metal's float4x4 is stored by column, and row i of the row-vector matrix serves exactly as column i ⇒ write M * v in the shader
+1 -1
View File
@@ -1,4 +1,4 @@
# kind: evidence main NVN 取址槽与静态调用点(nvn_slots.py)
# kind: evidence main NVN fetch slots and static use sites (nvn_slots.py)
name slot nuses first_use
nvnDeviceBuilderSetDefaults 320e910 1 173cb6c
nvnDeviceBuilderSetFlags 320e918 1 173cb7c
1 # kind: evidence main NVN 取址槽与静态调用点(nvn_slots.py) # kind: evidence main NVN fetch slots and static use sites (nvn_slots.py)
2 name
3 nvnDeviceBuilderSetDefaults
4 nvnDeviceBuilderSetFlags
+7 -7
View File
@@ -39,13 +39,13 @@ static void rr_finish(int ranged) {
id<MTLBuffer> before=g_rr_before;int use_before=g_rr_use_before;NSArray *actualvis=@[g_rr_vis[0],g_rr_vis[1]];int slot0=g_rr_slot[0],slot1=g_rr_slot[1];uint64_t pass=g_stat_pass,draws=g_stat_draw,load=g_bw_load,store=g_bw_store,full=g_pass_full_rw,dead=g_sd_dead_bytes,ndead=g_sd_n_dead,nall=g_sd_n_all;
id<MTLTexture> c=tex_mtl(&g_rr_t[0]),d=tex_mtl(&g_rr_t[1]);id<MTLTexture> saved[2];id<MTLTexture> source[2]={c,d};
for(int k=0;k<2;k++){id<MTLTexture> t=source[k];MTLTextureDescriptor *td=[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:t.pixelFormat width:t.width height:t.height mipmapped:NO];td.storageMode=MTLStorageModePrivate;td.usage=MTLTextureUsageRenderTarget|MTLTextureUsageShaderRead;saved[k]=[g_dev newTextureWithDescriptor:td];if(!saved[k])abort();rr_copy(t,saved[k]);}
if(g_rr_reg!=g_reg_gen||g_rr_ser!=g_ser_gen)MDIE("RT范围oracle资源代号变化,提交前停止未比");
if(g_rr_reg!=g_reg_gen||g_rr_ser!=g_ser_gen)MDIE("RT range oracle: resource generation changed, stopped before submit without comparing");
NSArray *candidate=rr_read(c,d);rr_copy(g_rr_initial[0],c);rr_copy(g_rr_initial[1],d);g_vis=[g_dev newBufferWithLength:VIS_SLOTS*8 options:MTLResourceStorageModeShared];g_vis_n=0;g_vis_cur=-1;g_vis_on=g_vis_counting=1;g_vs=NULL;g_nvs=g_vscap=0;g_exec_next=g_exec_cur=NULL;
id<MTLBuffer> refvis[2];int refslot[2];
for(int i=0;i<2;i++){if(i)rp_end();g_vis_cur=-1;memcpy(&S,&g_rr_s[i].s,sizeof S);R=g_rr_s[i].r;g_pso_dirty=g_dss_dirty=1;rp_begin_ex(4); // In the original trace both passes are mode4; do not attach an extra stencil with mode0, which has no scan start point
draw(g_rr_s[i].indexed?NULL:&g_rr_s[i].dw,g_rr_s[i].indexed?&g_rr_s[i].di:NULL);refvis[i]=g_vis;refslot[i]=g_vis_cur;}rp_end();
uint64_t original_passes=g_stat_pass-pass,original_bytes=g_bw_load-load+g_bw_store-store;NSArray *reference=rr_read(c,d);
if(g_rr_reg!=g_reg_gen||g_rr_ser!=g_ser_gen||g_rr_dirty!=nvn_dirty_now())MDIE("RT范围oracle绑定期间资源/数据代号变化,提交前停止未比");
if(g_rr_reg!=g_reg_gen||g_rr_ser!=g_ser_gen||g_rr_dirty!=nvn_dirty_now())MDIE("RT range oracle: resource/data generation changed while bound, stopped before submit without comparing");
rr_copy(saved[0],c);rr_copy(saved[1],d); // Subsequent commands read the candidate; the oracle must not overwrite it to force the screenshot green
NSArray *referencevis=@[refvis[0],refvis[1]];int refslot0=refslot[0],refslot1=refslot[1];vis_clear();free(g_vs);g_vs=oldvs;g_nvs=vsn;g_vscap=vscap;g_vis=oldvis;g_vis_n=visn;
memcpy(&S,&ss,sizeof S);R=rs;g_exec_next=next;g_exec_cur=cur_op;g_vis_on=vis;g_vis_counting=counting;g_vis_cur=cur;g_vis_cur_pass=curpass;g_pso_dirty=g_dss_dirty=1;g_stat_pass=pass;g_stat_draw=draws;g_bw_load=load;g_bw_store=store;g_pass_full_rw=full;g_sd_dead_bytes=dead;g_sd_n_dead=ndead;g_sd_n_all=nall;
@@ -53,10 +53,10 @@ static void rr_finish(int ranged) {
[g_cb addCompletedHandler:^(id<MTLCommandBuffer> cb){unsigned long long diff[3]={0};
unsigned long long av[2],rv[2];int as[2]={slot0,slot1},rs[2]={refslot0,refslot1};for(int k=0;k<2;k++){id<MTLBuffer> a=actualvis[k],r=referencevis[k];av[k]=((uint64_t *)a.contents)[as[k]];rv[k]=((uint64_t *)r.contents)[rs[k]];}if(use_before)av[0]-=*(uint64_t *)before.contents;const char *names[3]={"color","depth","stencil"};
for(int k=0;k<3;k++){id<MTLBuffer> a=candidate[k],b=reference[k];size_t bp=k==2?1:4;for(NSUInteger j=0;j<n;j++)diff[k]+=memcmp((uint8_t *)a.contents+j*bp,(uint8_t *)b.contents+j*bp,bp)!=0;
for(int m=0;m<2;m++){id<MTLBuffer> x=m?b:a;NSString *path=[NSString stringWithFormat:@"%@/F%llu_P%llu_%s_%s.raw",out,(unsigned long long)frame,(unsigned long long)pass,m?"reference":"candidate",names[k]];FILE *f=fopen(path.UTF8String,"wb");if(!f||fwrite(x.contents,1,n*bp,f)!=n*bp)MLOG("【红】RT范围检查raw写盘失败 %s",path.UTF8String);if(f)fclose(f);}}
NSString *qp=[out stringByAppendingPathComponent:@"QUERY_RESULTS.tsv"];FILE *qf=fopen(qp.UTF8String,"a");if(qf){fprintf(qf,"%llu\t%llu\t%llu\t%llu\t%llu\t%llu\n",(unsigned long long)frame,(unsigned long long)pass,rv[0],rv[1],av[0],av[1]);fclose(qf);}else MLOG("【红】RT范围query写盘失败");
NSString *result=[out stringByAppendingPathComponent:@"CHECK_RESULTS.tsv"];FILE *rf=fopen(result.UTF8String,"a");if(rf){fprintf(rf,"%llu\t%llu\t%u\t%llu\t1\t%llu\t%llu\t%llu\t%llu\t%llu\t%lu\t%lu\n",(unsigned long long)frame,(unsigned long long)pass,vc,(unsigned long long)original_passes,(unsigned long long)original_bytes,(unsigned long long)candidate_bytes,diff[0],diff[1],diff[2],(unsigned long)n,(unsigned long)cb.status);fclose(rf);}else MLOG("【红】RT范围结果写盘失败");
MLOG("%sRT范围query F%llu P%llu 原=%llu,%llu 候选=%llu,%llu",av[0]!=rv[0]||av[1]!=rv[1]?"【红】":"",(unsigned long long)frame,(unsigned long long)pass,rv[0],rv[1],av[0],av[1]);
MLOG("%sRT范围双渲染 F%llu P%llu vc=%u 原pass=%llu 候选pass=1 原模型=%llu 候选模型=%llu diff=%llu,%llu,%llu/%lu CB=%lu",diff[0]||diff[1]||diff[2]?"【红】":"",(unsigned long long)frame,(unsigned long long)pass,vc,(unsigned long long)original_passes,(unsigned long long)original_bytes,(unsigned long long)candidate_bytes,diff[0],diff[1],diff[2],(unsigned long)n,(unsigned long)cb.status);
for(int m=0;m<2;m++){id<MTLBuffer> x=m?b:a;NSString *path=[NSString stringWithFormat:@"%@/F%llu_P%llu_%s_%s.raw",out,(unsigned long long)frame,(unsigned long long)pass,m?"reference":"candidate",names[k]];FILE *f=fopen(path.UTF8String,"wb");if(!f||fwrite(x.contents,1,n*bp,f)!=n*bp)MLOG("[RED] RT range check: writing raw file failed %s",path.UTF8String);if(f)fclose(f);}}
NSString *qp=[out stringByAppendingPathComponent:@"QUERY_RESULTS.tsv"];FILE *qf=fopen(qp.UTF8String,"a");if(qf){fprintf(qf,"%llu\t%llu\t%llu\t%llu\t%llu\t%llu\n",(unsigned long long)frame,(unsigned long long)pass,rv[0],rv[1],av[0],av[1]);fclose(qf);}else MLOG("[RED] RT range query: writing to disk failed");
NSString *result=[out stringByAppendingPathComponent:@"CHECK_RESULTS.tsv"];FILE *rf=fopen(result.UTF8String,"a");if(rf){fprintf(rf,"%llu\t%llu\t%u\t%llu\t1\t%llu\t%llu\t%llu\t%llu\t%llu\t%lu\t%lu\n",(unsigned long long)frame,(unsigned long long)pass,vc,(unsigned long long)original_passes,(unsigned long long)original_bytes,(unsigned long long)candidate_bytes,diff[0],diff[1],diff[2],(unsigned long)n,(unsigned long)cb.status);fclose(rf);}else MLOG("[RED] RT range result: writing to disk failed");
MLOG("%sRT range query F%llu P%llu original=%llu,%llu candidate=%llu,%llu",av[0]!=rv[0]||av[1]!=rv[1]?"[RED] ":"",(unsigned long long)frame,(unsigned long long)pass,rv[0],rv[1],av[0],av[1]);
MLOG("%sRT range double render F%llu P%llu vc=%u original passes=%llu candidate passes=1 original model=%llu candidate model=%llu diff=%llu,%llu,%llu/%lu CB=%lu",diff[0]||diff[1]||diff[2]?"[RED] ":"",(unsigned long long)frame,(unsigned long long)pass,vc,(unsigned long long)original_passes,(unsigned long long)original_bytes,(unsigned long long)candidate_bytes,diff[0],diff[1],diff[2],(unsigned long)n,(unsigned long)cb.status);
}];g_rr_initial[0]=g_rr_initial[1]=nil;g_rr_checking=0;
}
+1 -1
View File
@@ -2,7 +2,7 @@
This document lists what each kind of NVN (the Switch's native graphics interface) object and command lands on in Metal within D2R Mobile's graphics layer. For the overall structure of the graphics layer see [README.md](README.md).
Scope: the 216 NVN functions that have call sites in the game's main program (listed in `nvn/nvn_slots.tsv`). The remaining 307 are connected to a red-reporting stub that, when called, prints `【红】NVN 未实现被调用:名字` in the log ("[red] unimplemented NVN function called: name").
Scope: the 216 NVN functions that have call sites in the game's main program (listed in `nvn/nvn_slots.tsv`). The remaining 307 are connected to a red-reporting stub that, when called, prints `[RED] NVN unimplemented function called: <name>` in the log.
Two conventions that run through the whole table:
+1 -1
View File
@@ -3,7 +3,7 @@
# Game profile (same convention as the host Makefile: GAME=base game title ID, required: there is no default game): game_profile.h is generated by the host Makefile, dmsh_chain.h lives in the profile folder
ifeq ($(strip $(GAME)),)
ifneq ($(filter-out clean,$(or $(MAKECMDGOALS),t_nvn)),)
$(error 没设 GAME | GAME is not set: make GAME=<基础版标题号 | base game title ID>: $(shell python3 ../../games/game.py list | awk -F'\t' '{printf "%s (%s) ", $$1, $$3}'))
$(error GAME is not set: make GAME=<base game title ID>: $(shell python3 ../../games/game.py list | awk -F'\t' '{printf "%s (%s) ", $$1, $$3}'))
endif
endif
GAME_DIR = ../../games/$(GAME)
+4 -4
View File
@@ -22,7 +22,7 @@ int main(void) {
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
NSError *err = nil;
id<MTLLibrary> lib = [dev newLibraryWithSource:@(k_nvn_builtin_msl) options:nil error:&err];
if (!lib) { printf("MSL 编译失败 %s\n", err.localizedDescription.UTF8String); return 1; }
if (!lib) { printf("MSL compile failed %s\n", err.localizedDescription.UTF8String); return 1; }
id<MTLCommandQueue> q = [dev newCommandQueue];
// Source data: NREC 32-byte records (= 2×NREC 16-byte records), with values that have high bits set and wraparound cases where "the later is smaller than the earlier"
uint64_t src[4 * NREC];
@@ -46,10 +46,10 @@ int main(void) {
row[j] = !memcmp(bd.contents, e, sizeof e);
}
int ok = row[k] && !row[(k + 1) % 4] && !row[(k + 2) % 4] && !row[(k + 3) % 4];
printf("%s(%s):对自身期望 %s;对其余 3 个期望全等数 %d(期望 0)→ %s\n", k_nvn_builtin_map[k].fn, k_nvn_builtin_map[k].ctl,
row[k] ? "全等(含回绕、哨兵 27 个未动)" : "不等", row[(k + 1) % 4] + row[(k + 2) % 4] + row[(k + 3) % 4], ok ? "✓" : "✗");
printf("%s (%s): against its own expectation %s; matches against the other 3 expectations %d (expected 0) → %s\n", k_nvn_builtin_map[k].fn, k_nvn_builtin_map[k].ctl,
row[k] ? "identical (wrap-around included, 27 sentinels untouched)" : "different", row[(k + 1) % 4] + row[(k + 2) % 4] + row[(k + 3) % 4], ok ? "✓" : "✗");
fail += !ok;
}
printf("== 4 项,失败 %d ==\n", fail);
printf("== 4 checks, %d failed ==\n", fail);
return fail != 0;
}
+16 -16
View File
@@ -7,7 +7,7 @@
static int fail, total;
static void t(const char *name, int want, int got) {
total++; fail += want != got;
printf("%-44s 期望 %s → 真结果 %s %s\n", name, want ? "成立" : "不成立", got ? "成立" : "不成立", want == got ? "✓" : "✗");
printf("%-44s expected %s → got %s %s\n", name, want ? "covers" : "does not cover", got ? "covers" : "does not cover", want == got ? "✓" : "✗");
}
int main(void) {
const float big[3][4] = {{-1, -1, 0, 1}, {3, -1, 0, 1}, {-1, 3, 0, 1}}; // counterclockwise (NDC y up)
@@ -18,20 +18,20 @@ int main(void) {
const float w2[3][4] = {{-2, -2, 0, 2}, {6, -2, 0, 2}, {-2, 6, 0, 2}};
const float vp_full[4] = {0, 0, 1600, 900}, vp_half[4] = {0, 0, 800, 900}, vp_off[4] = {0, 450, 1600, 900};
const int sc_full[4] = {0, 0, 1600, 900}, sc_part[4] = {0, 0, 1600, 450};
t("全屏三角形,视口 = 整块,不剔除", 1, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("全屏三角形,无视口设置(缺省整块)", 1, nvn_cover(big, 3, 4, 0, 1, 0, NULL, 0, NULL, 1600, 900));
t("全屏三角形,剔背面,逆时针为正面", 1, nvn_cover(big, 3, 4, 2, 1, 1, vp_full, 1, sc_full, 1600, 900));
t("负:同一三角形顺时针,剔背面(被剔)", 0, nvn_cover(big_cw, 3, 4, 2, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:视口只占附件左半", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_half, 0, NULL, 1600, 900));
t("负:视口下移(不从 0 起)", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_off, 0, NULL, 1600, 900));
t("负:视口整块 1600x900,附件是 1920x1080", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1920, 1080));
t("负:剪裁只占上半", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 1, sc_part, 1600, 900));
t("负:三角形缺 (1,1) 角", 0, nvn_cover(small, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:z 超出 [0,1]", 0, nvn_cover(zout, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:w ≠ 1", 0, nvn_cover(w2, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:全屏四边形条带(单个三角形包不住,保守)", 0, nvn_cover(quad, 4, 5, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:正背面都剔", 0, nvn_cover(big, 3, 4, 3, 1, 1, vp_full, 0, NULL, 1600, 900));
t("负:图元是线(非三角形)", 0, nvn_cover(big, 3, 1, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
printf("== %d 项,失败 %d ==\n", total, fail);
t("full-screen triangle, viewport = whole attachment, no culling", 1, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("full-screen triangle, no viewport set (defaults to whole attachment)", 1, nvn_cover(big, 3, 4, 0, 1, 0, NULL, 0, NULL, 1600, 900));
t("full-screen triangle, back-face culling, counter-clockwise front", 1, nvn_cover(big, 3, 4, 2, 1, 1, vp_full, 1, sc_full, 1600, 900));
t("neg: same triangle clockwise, back-face culling (culled)", 0, nvn_cover(big_cw, 3, 4, 2, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: viewport covers only the left half of the attachment", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_half, 0, NULL, 1600, 900));
t("neg: viewport shifted down (does not start at 0)", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_off, 0, NULL, 1600, 900));
t("neg: viewport 1600x900, attachment is 1920x1080", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1920, 1080));
t("neg: scissor covers only the top half", 0, nvn_cover(big, 3, 4, 0, 1, 1, vp_full, 1, sc_part, 1600, 900));
t("neg: triangle misses the (1,1) corner", 0, nvn_cover(small, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: z outside [0,1]", 0, nvn_cover(zout, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: w ≠ 1", 0, nvn_cover(w2, 3, 4, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: full-screen quad strip (no single triangle covers it; conservative)", 0, nvn_cover(quad, 4, 5, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: both faces culled", 0, nvn_cover(big, 3, 4, 3, 1, 1, vp_full, 0, NULL, 1600, 900));
t("neg: primitive is lines (not triangles)", 0, nvn_cover(big, 3, 1, 0, 1, 1, vp_full, 0, NULL, 1600, 900));
printf("== %d checks, %d failed ==\n", total, fail);
return fail != 0;
}
+7 -7
View File
@@ -6,7 +6,7 @@ module_t g_mod[MAX_MOD]; int g_nmod;
void svc_thread_dump(void) {}
uint64_t now_ns(void) { return bp_now(); }
static int fail, count;
#define EXPECT(name, want, got) do { uint64_t w=(want), g=(got); count++; printf("%s 期望 %llu → 真结果 %llu %s\n", name, (unsigned long long)w, (unsigned long long)g, w==g?"✓":"✗"); fail += w != g; } while (0)
#define EXPECT(name, want, got) do { uint64_t w=(want), g=(got); count++; printf("%s expected %llu → got %llu %s\n", name, (unsigned long long)w, (unsigned long long)g, w==g?"✓":"✗"); fail += w != g; } while (0)
typedef struct { uint8_t *p; size_t n; } chain;
static chain chain_new(void) { chain c={0}; if (posix_memalign((void **)&c.p,getpagesize(),65536)) abort(); memset(c.p,0,65536); mtl_pool_add(c.p,65536); return c; }
static void emit(chain *c, unsigned op, const void *a, size_t n) { size_t z=(8+n+7)&~7ull; op_hdr h={op,(uint32_t)z}; memcpy(c->p+c->n,&h,8); if(n) memcpy(c->p+c->n+8,a,n); c->n+=z; }
@@ -19,7 +19,7 @@ static void bind_tex(chain *c, int id) { p_bind_handles b={.stage=1,.first=0,.n=
static void draw_cmd(chain *c) { p_draw d={NVN_DRAW_PRIMITIVE_TRIANGLES,0,3}; emit(c,OP_DRAW,&d,sizeof d); }
static void blend(chain *c) { color_st col={1,NVN_LOGIC_OP_COPY,NVN_ALPHA_FUNC_ALWAYS}; p_bind_state x={.kind=1};memcpy(x.bytes,&col,sizeof col);emit(c,OP_BIND_STATE,&x,sizeof x); blend_st b={0,NVN_BLEND_FUNC_SRC_ALPHA,NVN_BLEND_FUNC_ONE_MINUS_SRC_ALPHA,NVN_BLEND_FUNC_ONE,NVN_BLEND_FUNC_ZERO,NVN_BLEND_EQUATION_ADD,NVN_BLEND_EQUATION_ADD};x.kind=0;memcpy(x.bytes,&b,sizeof b);emit(c,OP_BIND_STATE,&x,sizeof x); }
static void ds_off(chain *c) { ds_st d={0,0,NVN_DEPTH_FUNC_ALWAYS,0,NVN_STENCIL_FUNC_ALWAYS,NVN_STENCIL_FUNC_ALWAYS,NVN_STENCIL_OP_KEEP,NVN_STENCIL_OP_KEEP,NVN_STENCIL_OP_KEEP,NVN_STENCIL_OP_KEEP,NVN_STENCIL_OP_KEEP,NVN_STENCIL_OP_KEEP}; p_bind_state x={.kind=5}; memcpy(x.bytes,&d,sizeof d); emit(c,OP_BIND_STATE,&x,sizeof x); }
static void readback(const tex_t *t, uint8_t out[256]) { id<MTLBuffer> b=[g_dev newBufferWithLength:256 options:MTLResourceStorageModeShared]; [blit() copyFromTexture:tex_mtl(t) sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0,0,0) sourceSize:MTLSizeMake(8,8,1) toBuffer:b destinationOffset:0 destinationBytesPerRow:32 destinationBytesPerImage:256]; uint64_t f=mtl_sub_commit(); EXPECT("提交完成",1,mtl_wait(f,5000000000ull)); EXPECT("提交反馈无错误",0,mtl_last_commit_error()); memcpy(out,b.contents,256); }
static void readback(const tex_t *t, uint8_t out[256]) { id<MTLBuffer> b=[g_dev newBufferWithLength:256 options:MTLResourceStorageModeShared]; [blit() copyFromTexture:tex_mtl(t) sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0,0,0) sourceSize:MTLSizeMake(8,8,1) toBuffer:b destinationOffset:0 destinationBytesPerRow:32 destinationBytesPerImage:256]; uint64_t f=mtl_sub_commit(); EXPECT("submit completed",1,mtl_wait(f,5000000000ull)); EXPECT("no error in commit feedback",0,mtl_last_commit_error()); memcpy(out,b.contents,256); }
static NvProg *g_tkeep[256]; static int g_tnkeep; /* Program objects in the tests are permanent: after a free, a new object may land at the same address, and the pipeline cache, keyed by object address, would return the old pipeline (in production program objects are never freed) */
static void setup_prog(prog_t *pg) { NSError *e=nil; id<MTLLibrary> l=[g_dev newLibraryWithSource:@"#include <metal_stdlib>\nusing namespace metal; vertex float4 v(uint n[[vertex_id]]){float2 p[3]={float2(-1,-1),float2(3,-1),float2(-1,3)};return float4(p[n],0,1);}\nfragment float4 red(){return float4(1,0,0,0.5);}\nstruct A{texture2d<float> t[[id(0)]];}; fragment float4 sample(constant A&a[[buffer(0)]]){return a.t.read(uint2(0));}\n" options:nil error:&e]; if(!l){fprintf(stderr,"%s\n",e.localizedDescription.UTF8String);abort();} for(int i=0;i<2;i++)pg[i].serial=8000+i; for(int i=0;i<2;i++){NvProg *p=[NvProg new];p->sh[0]=[NvShader new];p->sh[0]->fn=[l newFunctionWithName:@"v"];p->sh[1]=[NvShader new];p->sh[1]->stage=1;p->sh[1]->fn=[l newFunctionWithName:i?@"sample":@"red"];if(i){p->sh[1]->rf.nb=1;p->sh[1]->rf.b[0]=(nvn_rbind){.kind=NVN_RB_TEX,.reg=0,.dtt=2};p->sh[1]->ae=[p->sh[1]->fn newArgumentEncoderWithBufferIndex:0];}if(g_tnkeep<256)g_tkeep[g_tnkeep++]=p;serial_put(pg[i].serial,p);} }
static uint64_t run_case(int mode, int enabled, uint8_t px[256]) { tex_t a=tex(8),b=tex(8),q2=tex(8); int aid=view(&a,0), a1=view(&a,1); chain c=chain_new(); prog_t pg[2]={0}; setup_prog(pg); set_rt(&c,&a,NULL,0);clear_color(&c,0,0,0,0);ds_off(&c);bind_prog(&c,&pg[0]);blend(&c);draw_cmd(&c);set_rt(&c,&b,NULL,0);clear_color(&c,0,0,0,0);if(mode==1){uint64_t t=NVN_COUNTER_TYPE_SAMPLES_PASSED;emit(&c,OP_RESET_COUNTER,&t,sizeof t);}if(mode==2){bind_prog(&c,&pg[1]);bind_tex(&c,aid);}ds_off(&c);draw_cmd(&c);set_rt(&c,&a,&q2,0);if(mode==3)clear_color(&c,0,0,0,0);bind_prog(&c,&pg[1]);bind_tex(&c,mode==5?aid:a1);draw_cmd(&c);emit(&c,OP_END,NULL,0);state_reset();memset(&R,0,sizeof R);vis_clear();g_vis_on=g_vis_counting=0;if(!enabled)setenv("DM_NVN_DEFER","0",1);else setenv("DM_NVN_DEFER","1",1);uint64_t start=g_stat_pass;mtl_sub_begin();uint64_t hh=(uint64_t)c.p;mtl_sub_index(&hh,1);mtl_exec(c.p);readback(&a,px);return g_stat_pass-start; }
@@ -42,8 +42,8 @@ static uint64_t run_chain(int enabled, uint8_t px[256]) { /* Chained: P1 draws
set_rt(&c,&a,&d,0);bind_prog(&c,&pg[0]);draw_cmd(&c);emit(&c,OP_END,NULL,0);
state_reset();memset(&R,0,sizeof R);vis_clear();g_vis_on=g_vis_counting=0;setenv("DM_NVN_DEFER",enabled?"1":"0",1);
uint64_t start=g_stat_pass;mtl_sub_begin();uint64_t hh=(uint64_t)c.p;mtl_sub_index(&hh,1);mtl_exec(c.p);readback(&a,px);return g_stat_pass-start; }
int main(void){@autoreleasepool{setvbuf(stdout,NULL,_IONBF,0);setenv("DM_NVN_TBDR","1",1);mtl_init();uint8_t p[256];uint8_t off[256];uint64_t n0=run_case(0,0,off);uint64_t d0=g_defer_n;uint64_t n1=run_case(0,1,p);uint64_t d1=g_defer_n;EXPECT("正例推迟计数",1,d1-d0);EXPECT("正例开通道少一",1,n0-n1);EXPECT("正例像素非黑",1,p[0]>0);EXPECT("正例开关逐字节相同",0,(uint64_t)memcmp(p,off,256));
uint64_t m0=run_multi(0,off);uint64_t md0=g_defer_n;uint64_t m1=run_multi(1,p);EXPECT("同Q两组推迟计数(B 在 Q 里下标不同,只成立 A)",1,g_defer_n-md0);EXPECT("同Q两组开通道少一",1,m0-m1);EXPECT("同Q两组开关逐字节相同",0,(uint64_t)memcmp(p,off,256));
uint64_t k0=run_chain(0,off);uint64_t kd0=g_defer_n;uint64_t k1=run_chain(1,p);EXPECT("链式下标不同不推迟",0,g_defer_n-kd0);EXPECT("链式下标不同开通道数不变",0,k0-k1);EXPECT("链式开关逐字节相同",0,(uint64_t)memcmp(p,off,256));
uint64_t j0=run_chain_id(0,off);uint64_t jd0=g_defer_n;uint64_t j1=run_chain_id(1,p);EXPECT("链式恒等两组成立",2,g_defer_n-jd0);EXPECT("链式恒等开通道少二",2,j0-j1);EXPECT("链式恒等开关逐字节相同",0,(uint64_t)memcmp(p,off,256));
for(int m=1;m<=5;m++){run_case(m,0,off);uint64_t before=g_defer_n;run_case(m,1,p);EXPECT(m==2||m==3||m==5?"反例不推迟":"另一个正例仍推迟",m==2||m==3||m==5?0:1,g_defer_n-before);EXPECT("开关逐字节相同",0,(uint64_t)memcmp(p,off,256));}printf("== %d 项,失败 %d ==\n",count,fail);return fail!=0;}}
int main(void){@autoreleasepool{setvbuf(stdout,NULL,_IONBF,0);setenv("DM_NVN_TBDR","1",1);mtl_init();uint8_t p[256];uint8_t off[256];uint64_t n0=run_case(0,0,off);uint64_t d0=g_defer_n;uint64_t n1=run_case(0,1,p);uint64_t d1=g_defer_n;EXPECT("positive case: defer count",1,d1-d0);EXPECT("positive case: one pass fewer",1,n0-n1);EXPECT("positive case: pixel not black",1,p[0]>0);EXPECT("positive case: on/off byte-identical",0,(uint64_t)memcmp(p,off,256));
uint64_t m0=run_multi(0,off);uint64_t md0=g_defer_n;uint64_t m1=run_multi(1,p);EXPECT("two groups in one Q: defer count (B has a different index in Q, only A qualifies)",1,g_defer_n-md0);EXPECT("two groups in one Q: one pass fewer",1,m0-m1);EXPECT("two groups in one Q: on/off byte-identical",0,(uint64_t)memcmp(p,off,256));
uint64_t k0=run_chain(0,off);uint64_t kd0=g_defer_n;uint64_t k1=run_chain(1,p);EXPECT("chain with different indices: not deferred",0,g_defer_n-kd0);EXPECT("chain with different indices: pass count unchanged",0,k0-k1);EXPECT("chain: on/off byte-identical",0,(uint64_t)memcmp(p,off,256));
uint64_t j0=run_chain_id(0,off);uint64_t jd0=g_defer_n;uint64_t j1=run_chain_id(1,p);EXPECT("identity chain: both groups qualify",2,g_defer_n-jd0);EXPECT("identity chain: two passes fewer",2,j0-j1);EXPECT("identity chain: on/off byte-identical",0,(uint64_t)memcmp(p,off,256));
for(int m=1;m<=5;m++){run_case(m,0,off);uint64_t before=g_defer_n;run_case(m,1,p);EXPECT(m==2||m==3||m==5?"negative case: not deferred":"another positive case: still deferred",m==2||m==3||m==5?0:1,g_defer_n-before);EXPECT("on/off byte-identical",0,(uint64_t)memcmp(p,off,256));}printf("== %d checks, %d failed ==\n",count,fail);return fail!=0;}}
Loaded 100 of 256 files, more files were not shown because too many files have changed in this diff. Show more