客户端渲染服务(client/rendering)

基本信息

属性 值
包 com.gtnewhorizons.angelica.client.rendering
本条目覆盖 6 个文件(目录共 7 个;BlockRenderListManager.java 已被其它条目提及)
总行数 203 + 109 + 55 + 46 + 23 + 22 = 458
上游 org.embeddedt.embeddium.impl.*(内嵌 Embeddium/Sodium)、net.coderbot.iris(内嵌 Iris)
上游改编 ⚠️ TextureTracker.java 改编自 Iris(:9-11 注释)

client/ 共 23 个文件 = client/font/ 9 + client/gui/ 7 + client/rendering/ 7。client/font/ 已被 字体 覆盖;client/gui/ 见 动态灯光。

# 文件 行数 可见性
1 DeferredDrawBatcher.java 203 public final
2 DeferredBatchTessellator.java 109 public final
3 AngelicaFogService.java 55 public class(INSTANCE 单例)
4 TextureTracker.java 46 public(INSTANCE 单例)
5 GlUniformFloat2v.java 23 public class
6 ModelViewDelta.java 22 public final

ModelViewDelta:modelview 差分的逆矩阵

22 行,public final。本条目完整读过 —— 这是全仓最短的实质类之一。

private final Matrix4f base = new Matrix4f();          // :7
private final Matrix4f baseInverse = new Matrix4f();   // :8

public void snapshot() {                                          // :10
    base.set(GLStateManager.getModelViewMatrix());               // :11
    base.invert(baseInverse);                                     // :12
}

public Matrix4fc deltaOrNull(Matrix4f scratch) {                  // :15
    final Matrix4f current = GLStateManager.getModelViewMatrix(); // :16
    if (current.equals(base)) return null;                        // :17
    return baseInverse.mul(current, scratch);                     // :18
}

两个矩阵都是 final 字段,一次分配、永久复用(无 GC 压力)。

⚠️ base.invert(baseInverse) 在 snapshot() 里无条件求逆(:12)—— modelview 奇异(不可逆)时 JOML 返回零矩阵且不抛异常(JOML 的 invert 契约),baseInverse 变零矩阵 → 后续 mul 结果全零 → 顶点被画到原点。无奇异性检查。

⚠️ base.invert(...) 有两种重载:invert(Matrix4f dest)(用 dest)和 invert()(原地)。这里用的是前者(:12),正确(不破坏 base)。

⚠️ current.equals(base) 是 JOML 的 Matrix4f.equals —— 逐元素 == 比较(含精确浮点相等)。⚠️ 不是 epsilon 比较 —— 任何微小浮点差异都会让 deltaOrNull 返回非 null,即使视觉上无差别。这是性能敏感的路径(每次 interceptDraw 都调),且语义是「精确相同才返回 null」。

唯一调用方:BakedTransformCapture.interceptDraw(BakedTransformCapture.java:54 transform = delta.deltaOrNull(deltaMatrix))—— 见 TESR 网格捕获。⚠️ 只有 1 个调用方(delta 是 BakedTransformCapture 的实例字段 :19)。

snapshot() 同样只有 1 个调用方:BakedTransformCapture.begin()(BakedTransformCapture.java:33 delta.snapshot())。

GlUniformFloat2v:GLSL uniform2f 的包装

23 行,public class(非 final)。本条目完整读过。

public class GlUniformFloat2v extends GlUniform<float[]> {      // :5
    public GlUniformFloat2v(int index) { super(index); }        // :7-9
    @Override public void set(float[] value) {                  // :11
        if (value.length != 2) throw new IllegalArgumentException("value.length != 2");   // :12-14
        GLStateManager.glUniform2f(this.index, value[0], value[1]);                        // :15
    }
    public void set(float x, float y) {                         // :19
        GLStateManager.glUniform2f(this.index, x, y);           // :20
    }
}

⚠️ 继承 org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniform<float[]>(:4)—— 内嵌 Embeddium/Sodium 的类。这是「补一个上游没提供的 uniform 类型」,父类的 set 抽象方法被覆写。

⚠️ 显式数组版在每次 set 时都检查 value.length != 2 并抛 IllegalArgumentException(:12-14)—— 在渲染热路径上抛异常。消息 "value.length != 2" 简洁但不含实际长度,调试时需自己数。

⚠️ 两个 set 重载(float[] 与 float, float)—— 父类只有数组版,两个 set 的 float 版是本类新增的便捷重载。⚠️ 若调用方传 null 给数组版,value.length NPE(无 null 检查)。

⚠️ 类非 final(:5)—— 可被继承。INSTANCE 不存在(每次 new)。

AngelicaFogService:内嵌 Embeddium 的雾服务实现

55 行,public class implements FogService(org.embeddedt.embeddium.impl.render.chunk.fog.FogService,:5)。

成员 行 实现
INSTANCE :6 public static final 单例
fogColorArray :8 private final float[4]
getFogEnd() :10-13 GLStateManager.getFogState().getEnd()
getFogStart() :15-18 GLStateManager.getFogState().getStart()
getFogDensity() :20-23 GLStateManager.getFogState().getDensity()
getFogShapeIndex() :25-28 return 0; // 1.7.10 only has one fog shape

⚠️ getFogShapeIndex() 硬编码 0 并有注释(:27)—— 1.7.10 的 GL 没有多种雾形状(现代版支持 GL_FOG_SHAPE 的球/柱/平面)。这是本仓少见的「有注释地承认能力缺失」的写法。

⚠️ 三个 getter 每次都调 GLStateManager.getFogState()(:12、:17、:22)—— 无缓存。若着色器每帧查多次雾参数,会有重复的对象获取。getFogState() 是否返回缓存对象本条目不可判定(GLSM 子项目)。

⚠️ fogColorArray(:8)是 float[4] 实例字段 —— 供 getFogColor 之类的接口方法填充后返回。⚠️ 它是 final 数组(长度不可变但内容可变)且非静态 —— 单例下等价于静态。

⚠️ 类非 final(:5)但有 INSTANCE 单例 —— 可被继承出不同实现。

TextureTracker:改编自 Iris

46 行,public,有明确的上游归属注释:

/**
 * Adapted from Iris for use in GLSM          // :9-11
 */

⚠️ 这是内嵌 Iris 源码的改写版,放进 client/rendering 是因为它服务 GLSM 而非 Iris 管线。读本文件时应把它视为「Iris 代码 + GLSM 适配」,不是 Angelica 原创。

静态块接管 Iris 回调(:17-19)

private static Runnable bindTextureListener;              // :15
static {
    StateUpdateNotifiers.bindTextureNotifier = listener -> bindTextureListener = listener;   // :18
}

⚠️ 在静态初始化时写 net.coderbot.iris.gl.state.StateUpdateNotifiers.bindTextureNotifier(内嵌 Iris 的静态字段)—— 这是「猴补丁」内嵌上游的全局回调。⚠️ 无条件覆盖,无「保留原值」逻辑 —— 若 Iris 侧或另一个 mod 也设了这个 notifier,后设者获胜,先设者永久失去回调。这是一个单槽全局回调的争抢。

⚠️ bindTextureListener 是 static Runnable 非 volatile —— 静态块里赋值(类初始化时,同线程安全),但读取方可能在其它线程。若 Iris 在工作线程通知纹理绑定,无 happens-before 保证。

纹理表(:21)

private final Int2ObjectMap<AbstractTexture> textures = new Int2ObjectOpenHashMap<>();   // :21

int(GL texture id)→ AbstractTexture 的映射。⚠️ 强引用键值(Int2ObjectOpenHashMap)—— 纹理永不从表中移除(本条目只读到 :30,releaseTexture / remove 之类的方法在 :31-46 未读)。⚠️ 若 GL texture id 被复用(glGenTextures 回收后重发),且旧条目未清,会映射到错误的 AbstractTexture。

⚠️ trackTexture(int id, AbstractTexture)(:27-29)无条件 put —— 同一 id 重复 track 覆盖旧值(正确的方向)。

方法 INSTANCE(:13):eager 初始化的静态单例,构造器私有(:25-27)。

DeferredDrawBatcher:延迟绘制批处理器

203 行,public final。⚠️ 本条目只读到第 30 行(import 区 + 类头),203 行中约 175 行未读。这是本条目最大的缺口。

已确认的 import:

import 用途
com.gtnewhorizons.angelica.glsm.GLStateManager GLSM
com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard GLSM hook(与 BatchStateFallback 共用,见 TESR 管线)
org.embeddedt.embeddium.impl.* 内嵌 Embeddium/Sodium

已确认的调用点(来自 ParticleRunSplitter 与 ParticleInstancer,grep 确认):

调用 位置 语义
DeferredDrawBatcher.isActive() ParticleRunSplitter.java:36 是否活跃
DeferredDrawBatcher.exitAndFlush() ParticleRunSplitter.java:36 退出并 flush
DeferredDrawBatcher.enter() ParticleRunSplitter.java:47、ParticleInstancer.beginLayer 分支 3 进入

⚠️ BatchStateGuard 被 import 但本条目未读是否使用 —— 与 BatchStateFallback.install() 写同一全局单槽回调(BatchStateFallback.java:12)。⚠️ 若两者都写 BatchStateGuard.flush,存在覆盖关系。本条目无法判定。

⚠️ 与 ParticleCaptureTessellator 的关系:ParticleCaptureTessellator extends Tessellator(见 粒子实例化)—— 两者可能共享「延迟绘制」机制(DeferredBatchTessellator 名字也带 Deferred)。DeferredBatchTessellator 与 DeferredDrawBatcher 的分工本条目未读。

DeferredBatchTessellator:批处理版 Tessellator

109 行,public final。⚠️ 本条目只读到第 30 行,约 80 行未读。

⚠️ 类名暗示它 extends Tessellator(与 ParticleCaptureTessellator 同思路),但本条目未读到类声明行(在 :30 之后)。不下结论。

已知问题 / 风险

  1. TextureTracker.java 改编自 Iris(:9-11),不是 Angelica 原创 —— 应按上游代码对待。
  2. TextureTracker 的静态块无条件覆盖 Iris 的 StateUpdateNotifiers.bindTextureNotifier(:18),单槽回调争抢,无「保留原值」。
  3. TextureTracker.bindTextureListener 非 volatile(:15),跨线程通知无 happens-before 保证。
  4. TextureTracker.textures 是强引用 int→纹理表(:21),本条目未读到移除方法 —— GL texture id 复用时可能映射错误。
  5. ModelViewDelta.snapshot() 无奇异性检查(:12)—— modelview 不可逆时 JOML 返回零矩阵,后续顶点画到原点。
  6. ModelViewDelta.deltaOrNull 用精确浮点相等(:17)—— 微小差异也会返回非 null,且这是每 draw 调用的热路径。
  7. GlUniformFloat2v.set(float[]) 在热路径抛 IllegalArgumentException(:12-14),消息不含实际长度;数组版无 null 检查。
  8. AngelicaFogService.getFogShapeIndex() 硬编码 0(:27,有注释说明 1.7.10 只有一种雾形状)。
  9. AngelicaFogService 三个 getter 每次都调 getFogState()(:12、:17、:22),无缓存。
  10. DeferredDrawBatcher 203 行中约 175 行未读、DeferredBatchTessellator 109 行中约 80 行未读 —— 本条目最大的缺口。两者的分工与 BatchStateGuard 的归属本条目无法判定。
  11. DeferredDrawBatcher 与 BatchStateFallback 都可能写 GLSM 的 BatchStateGuard.flush 单槽回调(后者见 BatchStateFallback.java:12)—— 覆盖关系未确认。

相关条目