调试叠加层(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,不只命令能改。

已知问题 / 风险

  1. ChunkDebugMinimap.java 改编自 Beddium(LGPL),不是 Angelica 原创;Adapated 拼写错误在版权头内,不可修正。
  2. ChunkDebugMinimap 直接依赖 rendering/celeritas/ 内部类,绕过 api/ 抽象。
  3. ChunkDebugMinimap.enabled 是 public static volatile 可写字段无 setter(:44),任何代码都能改。
  4. F3Graph 维护两份样本数据(float 给 GPU、long 给 CPU,:33-34),失步会导致图与数字不符。
  5. F3Graph 的图表对 >30 FPS 无表达(HEIGHT = 60 按 30 FPS 定标),高帧率被截断。
  6. uHeadIdx 要求 CPU/GPU 两侧环形 head 严格一致(:43),偏移 1 帧即画错。
  7. FrametimeGraph / TPSGraph 的注释有效数字数与代码不一致(TPSGraph.java:7 注释无 f 后缀,:10 代码有)。
  8. F3Direction 的 glPushMatrix/glPopMatrix 在 if 内无 try/finally(:18-30),异常则 modelview 栈泄漏。
  9. F3Direction 依赖上层已关闭 cull face(:26 的 glScalef(-1,-1,-1) 翻转绕序),本文件未显式关闭 —— 隐式跨文件依赖。
  10. F3Direction.glTranslatef 的 -90 无注释(:20),语义靠推断。
  11. F3Direction 的 setColorRGBA_F 传 0~255 而非 0~1(:42 等),依赖原版 API 语义。
  12. FrametimeGraph / TPSGraph 无 @Override,只绑定常量组合,继承全部行为。
  13. ChunkDebugMinimap 的 EVENT_BUS 注册点未确认,可能存在「工具存在但未启用」的情况。

相关条目