调试叠加层(debug/ 图形与区块小地图)
基本信息
| 属性 | 值 |
|---|---|
| 包 | com.gtnewhorizons.angelica.debug |
| 本条目覆盖 | 5 个文件(目录共 15 个;DebugKeyHandler / TracyCaptureNotifier / FlybyFallGuard / FlybyRunner 已被其它条目提及) |
| 总行数 | 258 + 157 + 62 + 14 + 14 = 505 |
| 上游来源 | ChunkDebugMinimap.java 改编自 Beddium(LGPL 2.1) |
| # | 文件 | 行数 | 可见性 |
|---|---|---|---|
| 1 | F3Graph.java |
258 | public abstract |
| 2 | ChunkDebugMinimap.java |
157 | public |
| 3 | F3Direction.java |
62 | public |
| 4 | FrametimeGraph.java |
14 | public |
| 5 | TPSGraph.java |
14 | public |
F3Graph:F3 调试屏的折线图基类
258 行,public abstract class(抽象类)。两个具体子类各 14 行。
8 个布局常量(:24-31)
| 常量 | 值 | 行 |
|---|---|---|
NUM_SAMPLES |
240 | :24 |
VERT_FLOATS |
2 |
:26(注释 :25:Two floats (x,y)) |
VERT_COUNT |
4 |
:27 |
BORDER |
1 |
:28 |
HEIGHT |
60 | :29 |
WIDTH |
NUM_SAMPLES = 240 |
:30 |
FONT_COLOR |
0xFFE0E0E0 |
:31 |
WIDTH = NUM_SAMPLES = 240 —— 图表宽度恰好等于样本数,即 1 样本 1 像素。sampleBuf(:33)也是 createFloatBuffer(NUM_SAMPLES)。
⚠️ 注释 :32-33 说明了用 float 而非 long 的原因:
Due to GLSL 120 limitations, it’s just easier to use floats
即顶点属性用 float(GL 限制)但计算用 long(:34 的 private final long[] samples)—— 双份数据,GPU 用 float、CPU 算 sum 用 long。
⚠️ 两份数据可能失步:putSample(:58-66)同时写 samples[samplesHead](long)与 sampleBuf.put(samplesHead, (float) time)。(float) time 对纳秒量级的 long 会严重丢精度(float 只有 24 位有效数字,约 1.7e7;纳秒时间戳可达 1e15)—— 但因为每帧重置为相对值,实际写入的是帧间隔(通常 1e6~1e8 量级),float 精度足够。sum(:50)用 long 累加是正确的。
⚠️ sampleBuf.put(int index, float x) 是绝对定位写(:64),不改 position;而 samplesHead 手动推进(:65)。sampleBuf 的 position 永远不动。
环形缓冲的 sum 维护(:59-62)
sum -= samples[samplesHead];
sum += time;
samples[samplesHead] = time;
滑窗和的标准做法:先减旧值再加新值。⚠️ 首次调用时 samples[samplesHead] 是 0(数组初值),所以 sum -= 0 无害。initialized 标志(:37)用于区分首帧。
构造器(:52-56)
protected F3Graph(ResourceLocation texture, float pxPerNs, boolean left) {
3 个参数,pxPerNs 是「纳秒到像素」的换算系数,left 决定靠屏幕左还是右(getVertX 在 :68 用 if (left) 分支 + switch)。
着色器与 uniform
| 字段 | 行 | 说明 |
|---|---|---|
shader |
:38 |
gtnhlib ShaderProgram |
aPos |
:39 |
顶点位置 attribute |
uFBWidth / uFBHeight |
:40-41 |
|
uScaleFactor |
:42 |
|
uHeadIdx |
:43 |
环形缓冲的 head 传给 GPU |
uSamples |
:44 |
|
vertBuf |
:45 |
VBO |
vao |
:46 |
VAO |
用 GL_TRIANGLE_STRIP + VERT_COUNT = 4(:19 静态 import)画一个矩形条带。混合模式 GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA(:17-18)—— 预乘 alpha。
⚠️ uHeadIdx 把环形缓冲的 head 索引传给 GPU(:43),说明着色器内部也维护环形读取 —— CPU 与 GPU 两侧的「head」必须严格一致,偏移 1 帧就会画错。samplesHead 的注释(:36)明确说「one ahead of the position of the last sample」。
⚠️ ShaderProgram / MemoryStack 来自 gtnhlib(:4、:15),MemoryStack.stackPush 是栈式直接内存分配 —— 着色器 uniform 上传在栈上完成,离开栈帧即失效。
FrametimeGraph / TPSGraph:两个 14 行子类
两个类的结构完全对称,只有 3 个参数不同:
| 参数 | FrametimeGraph |
TPSGraph |
|---|---|---|
| 贴图 | angelica:textures/frametimes_fg.png(:9) |
angelica:textures/tps_fg.png(:9) |
pxPerNs |
0.0000018f |
0.0000012f |
left |
true |
false |
两个常量都可验算(源码注释 :7 明确给出推导):
| 类 | 注释原文 | 验算 |
|---|---|---|
FrametimeGraph |
At 1x scale, 30 FPS should be 60 px. 30 FPS = 33_333_333ns per frame, 60px/33_333_333ns = 0.0000018f px/ns |
60 / 33_333_333 = 1.8e-6 ✓ |
TPSGraph |
At 1x scale, 20 TPS should be 60 px. 20 FPS = 50_000_000ns per frame, 60px/50_000_000ns = 0.0000012 px/ns |
60 / 50_000_000 = 1.2e-6 ✓ |
⚠️ 注释与字面量的有效数字数不一致:FrametimeGraph 写 0.0000018f(7 位有效数字,含 f 后缀),TPSGraph 写 0.0000012f(同样 7 位 + f),但注释正文里写的是 0.0000018f 与 0.0000012(后者无 f)。注释与代码不完全对齐(TPSGraph.java:7 注释里是 0.0000012 无后缀,代码 :10 是 0.0000012f)。不是错误,但复制注释时容易出错。
⚠️ 「30 FPS should be 60 px」是设计目标(HEIGHT = 60),不是量程上限。超过 30 FPS 的帧会被压在图表顶端(截断)—— 60 FPS 的帧间隔 16.7ms 会算出 100px,超出图表高度。图表对 >30 FPS 的情况无表达。
⚠️ 两个 left 值相反(true / false)—— 帧时间图靠左、TPS 图靠右。若两者同时显示,靠右的那个紧贴屏幕中线。
⚠️ 两个类都 extends F3Graph 但没有 @Override 任何方法 —— 它们只调父类构造器,全部行为在基类。它们的存在只是为了绑定两个常量组合。
F3Direction:世界坐标系三轴指示
62 行,public,是在屏幕中心画 X/Y/Z 三条彩色轴线。
触发条件(:13)
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
⚠️ 要求 F3 开启(showDebugInfo)且必须是第一人称(thirdPersonView == 0)。第三人称不画。
⚠️ 整个方法体(:18-30)在 if 内,glPushMatrix/glPopMatrix 配对(:18、:30)—— 但 if 内没有 try/finally。renderWorldDirections() 抛异常则 glPopMatrix 不执行,modelview 栈泄漏。
变换链(:20-26)
| 行 | 动作 |
|---|---|
:20 |
glTranslatef(width/2, height/2, **-90**) |
:23 |
按 event.partialTicks 插值 pitch,绕 X 轴 |
:24 |
按 event.partialTicks 插值 yaw,绕 Y 轴 |
:26 |
glScalef(-1, -1, -1) —— 三轴全反 |
⚠️ glTranslatef 的第三个参数是 -90(:20)—— 不是 0。这是把指示器推到相机前方 90 单位(在放大 -1 之后即 90 单位的前方)。源码无注释解释 -90。
⚠️ glScalef(-1,-1,-1) 会翻转三角形绕序,需要 glDisable(GL_CULL_FACE) 才能看到 —— 而 renderWorldDirections 确实 disableTexture() 但没关 cull(:34)。若 cull 是开着的,glScalef(-1,-1,-1) 后线条会因绕序反转而被剔除。实际能显示说明 cull 已被上层关闭。这是隐式依赖。
⚠️ (float)(width/2) 是整数除法后再转 float(:20)—— 奇数宽度时偏左半个像素。原版同样如此。
轴定义(:39-56)
glLineWidth(2.0F)(:38)→ tessellator.startDrawing(GL11.GL_LINES)(:39)→ 3 组 setColorRGBA_F + 2 个 addVertex:
| 轴 | 行 | 颜色 | 终点 |
|---|---|---|---|
| X | :42-44 |
(255, 0, 0) 红 |
(10, 0, 0) |
| Z | :47-49 |
(0, 0, 255) 蓝 |
(0, 0, 10) |
| Y | :52-54 |
(0, 255, 0) 绿 |
(0, 10, 0) |
⚠️ setColorRGBA_F 的第 4 个参数传的是 1(int)而不是 1.0F(:42、:47、:52)—— 隐式 int → float 转换。RGB 传的是 255 而不是 1.0 —— 依赖 setColorRGBA_F 内部除以 255。若方法语义是「0~1 浮点」,这里会全白。实际能显示正确颜色说明该方法接受 0~255。这是 1.7.10 原版 API 的语义,非 Angelica 引入。
⚠️ 绘制顺序是 X、Z、Y(不是 X、Y、Z)—— 无功能影响(线段不重叠),但与直觉顺序不同。
收尾(:58-60):glLineWidth(1.0F) → glDepthMask(true) → enableTexture()。⚠️ 三个动作无 try/finally,且没有恢复 glEnable(GL_LIGHTING) 等其它可能被 disableTexture 影响的状态(disableTexture 只关纹理,OK)。
ChunkDebugMinimap:区块加载/渲染状态小地图
157 行,public,但有完整的上游版权头(:1-21)。
上游归属(必须标注)
/*
* Adapated from: Beddium for usage in Angelica // :2 注意原文拼写 "Adapated"
*
* Copyright (C) 2025 Ven, FalsePattern // :4
* ... GNU Lesser General Public License v3.0 ... // :11
* http://www.gnu.org/licenses/ // :20
*/
⚠️ 本文件改编自 Beddium(作者:Ven, FalsePattern),LGPL 2.1/3.0 双许可声明。这不是 Angelica 原创代码。 修改与再分发须遵守 LGPL。
⚠️ :2 的 Adapated 是原作者的拼写错误(应为 Adapted),照抄时不要「修正」 —— LGPL 头部需保持原样。
⚠️ 版权头写的是 2025 年,而文件 mtime 是 2025-09-28。同目录其它文件无此头部。
单例与开关(:42-44)
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap(); // :42
@Getter private static volatile boolean enabled = false; // :43-44
⚠️ enabled 是 volatile 的 public static 可写字段(靠 @Getter 生成 isEnabled()),无 setter。与 WitherArmorState.pendingInflate(非 volatile)不同 —— 这个做了 volatile,是本仓少数正确处理的全局开关。
INSTANCE 是 eager 初始化的静态单例(类加载即构造)。
依赖的 3 个 Celeritas 类
import ...rendering.celeritas.AngelicaRenderSectionManager; // :26
import ...rendering.celeritas.CeleritasWorldRenderer; // :27
⚠️ 本调试工具直接依赖 rendering/celeritas/ 的内部类(见 Celeritas)—— 不经过 api/ 抽象。celeritas 重构会直接打断这个调试工具。
事件订阅
| 项 | 行 |
|---|---|
@SubscribeEvent |
:28(Lombok 之外手写) |
RenderGameOverlayEvent.Pre |
:34 |
MinecraftForge.EVENT_BUS |
:35(import) |
EmptyChunk |
:33 |
ChunkProviderClient |
:31 |
⚠️ @SubscribeEvent + MinecraftForge.EVENT_BUS 的 register 调用点在本条目未确认(可能在文件后半部,或由 mixin/其它类注册)。若未注册,小地图不会显示。
开关方式
类注释(:39)明确:
Toggle via /angelica minimap
即通过 /angelica minimap 命令切换 —— 命令的实现见 AngelicaCommand。
⚠️ 命令与实现分离,且 enabled 是 public static volatile —— 任何代码都能直接改 enabled,不只命令能改。
已知问题 / 风险
ChunkDebugMinimap.java改编自 Beddium(LGPL),不是 Angelica 原创;Adapated拼写错误在版权头内,不可修正。ChunkDebugMinimap直接依赖rendering/celeritas/内部类,绕过api/抽象。ChunkDebugMinimap.enabled是public static volatile可写字段无 setter(:44),任何代码都能改。F3Graph维护两份样本数据(float 给 GPU、long 给 CPU,:33-34),失步会导致图与数字不符。F3Graph的图表对 >30 FPS 无表达(HEIGHT = 60按 30 FPS 定标),高帧率被截断。uHeadIdx要求 CPU/GPU 两侧环形 head 严格一致(:43),偏移 1 帧即画错。FrametimeGraph/TPSGraph的注释有效数字数与代码不一致(TPSGraph.java:7注释无f后缀,:10代码有)。F3Direction的glPushMatrix/glPopMatrix在if内无 try/finally(:18-30),异常则 modelview 栈泄漏。F3Direction依赖上层已关闭 cull face(:26的glScalef(-1,-1,-1)翻转绕序),本文件未显式关闭 —— 隐式跨文件依赖。F3Direction.glTranslatef的-90无注释(:20),语义靠推断。F3Direction的setColorRGBA_F传 0~255 而非 0~1(:42等),依赖原版 API 语义。FrametimeGraph/TPSGraph无@Override,只绑定常量组合,继承全部行为。ChunkDebugMinimap的 EVENT_BUS 注册点未确认,可能存在「工具存在但未启用」的情况。
相关条目
- Flyby 巡航录制 -
debug/flyby/的 5 个文件 - AngelicaCommand -
/angelica minimap的实现 - Celeritas(内嵌地形渲染引擎) - 小地图的数据来源
- FPS / 性能剖析 -
debug/profiling/AsprofRecorder的归属 - 配置与模块开关 - 调试功能的门控
- 资源与语言键 - 图表贴图
angelica:textures/*_fg.png的位置