TESR 保留组(rendering/tesr 缓存与实体批渲染)
基本信息
| 属性 | 值 |
|---|---|
| 包 | com.gtnewhorizons.angelica.rendering.tesr |
| 本条目覆盖 | 7 个文件 |
| 总行数 | 1083 + 172 + 136 + 121 + 108 + 98 + 78 = 1796 |
| 主题 | 跨帧保留的实例组、原版方块实体网格、实体材质表、实体阴影批渲染、锚点量化哈希 |
RetainedTesrGroups.java(1083 行)是 com.gtnewhorizons.angelica(379 个文件)里第二大的单个 java 文件——第一是 client/font/BatchingFontRenderer.java(1574 行,见 字体子系统)。若按全仓计,第一则是 GLSM 子项目的 GLStateManager.java(8561 行)。
| # | 文件 | 行数 | 可见性 | 角色 |
|---|---|---|---|---|
| 1 | RetainedTesrGroups.java |
1083 | 包私有 final class |
跨帧保留的实例组 + 晋升/降级 |
| 2 | EntityShadowBatcher.java |
172 | public final |
实体阴影 quad 批渲染 |
| 3 | VanillaModelMeshes.java |
136 | public final |
箱子/牌子/头颅的原版网格 |
| 4 | EntityMaterials.java |
121 | public final |
14 个共享 TesrMaterial 常量 |
| 5 | BeaconBeamMeshes.java |
108 | public final |
信标光柱内外两层网格 |
| 6 | ShadowQuadMath.java |
98 | public final |
阴影 quad 的 11 字段记录格式 |
| 7 | TesrAnchorMath.java |
78 | public final |
锚点网格 + 相对化 + 实例哈希 |
RetainedTesrGroups:跨帧保留
5 个调优常量(:34-38,全部包私有)
| 常量 | 值 | 行 | 语义 |
|---|---|---|---|
PROMOTE_AFTER_REBUILDS |
3 | :34 |
重建几次后晋升为常驻组 |
DEMOTE_AFTER_STABLE_FRAMES |
4 | :35 |
稳定几帧后降级 |
IDLE_RESET_FRAMES |
100 | :36 |
空闲多少帧后重置标记 |
GROUP_TTL_MS |
AngelicaTesrMeshCache.LRU_TIMEOUT_MS |
:37 |
组存活期(复用 LRU 常量) |
MERGE_SCAN |
4 | :38 |
合并扫描上限 |
⚠️ PROMOTE_AFTER_REBUILDS = 3 与 DEMOTE_AFTER_STABLE_FRAMES = 4 互不对称(3 次重建升,4 帧稳定降)—— 这是**滞回(hysteresis)**设计:需要连续 4 帧稳定才降级,避免在阈值附近抖动。不是笔误。
⚠️ GROUP_TTL_MS 与 InstancedTemplateRenderer.TEMPLATE_TTL_MS 都直接引用 AngelicaTesrMeshCache.LRU_TIMEOUT_MS —— 三处共用一个常量(含 AngelicaTesrMeshCache 自身)。改一处即改三处。
⚠️ MERGE_SCAN = 4 是「最多检查 4 个已有组」(而非全部)。组数多时本可合并的组可能因扫描上限被漏掉,退化为新建组。性能与正确性的折中。
implements AngelicaBufferSource.LayerDrawHook(:32)
final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
⚠️ 类整体包私有(final class 无 public),但实现的接口来自内嵌上游包 net.coderbot.batchedentityrendering.impl —— 就是 TESR 实例化管线 里标注过的那条「上游包内的 mod 私有类」线索。AngelicaBufferSource 也是 Angelica 写的类,放在上游包命名空间里。
这条线索的含义:RetainedTesrGroups 是通过在内嵌上游的钩子接口上挂载来被驱动的,没有 main 里的显式 new。这是「内嵌改写」而非「独立子系统」,其生命周期由上游的 AngelicaBufferSource 持有。
4 个内部类
| 内部类 | 行 | 说明 |
|---|---|---|
TexRun |
:75 |
纹理运行 |
InstanceColumns |
:94 |
实例列(SoA 布局) |
Group |
:172 |
组 |
DrawStateKey |
:399 |
绘制状态键 |
⚠️ DrawStateKey 是 private static final class(:399),与 DrawState(public final)是两个不同概念 —— DrawStateKey 是 map 键用的值对象,DrawState 是状态描述。名字相近但职责不同,极易混淆。
两个 need 位标志(:669-670)
private static final int NEED_MATRIX = 1;
private static final int NEED_CUBE = 2;
使用处(:660-666):
if ((need & NEED_CUBE) != 0) {
if (deferred != null) deferred.bindInstancedVariant(Instancing.CUBE);
drawCubeVariant(list);
}
if (need != 0 && deferred != null) deferred.rebindCurrentPass();
⚠️ 两处检查不对称:第二处用 need != 0(任一位),第一处用位掩码。若 need 只有 NEED_MATRIX,第一处跳过、第二处仍 rebind。这是有意的(任何绘制后都要 rebind pass),但容易读成疏漏。
Instancing.CUBE 是 GLSM 枚举的一个值 —— Instancing 枚举的全部取值本条目不可判定(GLSM 子项目)。已知至少有 CUBE 一个,且 TesrInstancingPipeline.hasInstancedVariant(Instancing kind) 按 kind 分派。
⚠️ bindInstancedVariant 之后必须 rebindCurrentPass()(:666)—— 这是 TesrInstancingPipeline 三个方法的实际使用顺序。若 rebindCurrentPass 抛异常或被跳过,Iris 的 pass 状态会停留在 cube 变体上。
live() 的双条件(:~672-674)
private boolean live(Group g) {
return g.frameMark == frameMark && (g.templateColumns.hasInstances() || g.cubeColumns.hasInstances());
}
本帧被标记过 且 至少一列有实例。
⚠️ g.frameMark == frameMark 是「本帧活跃」判据。若某组在某帧未被访问,frameMark 不更新 → live() 假 → 该组被清扫。这意味着「一帧内未被渲染的组」等同失效,即使它仍在视野内(只是本帧恰好没画)。
3 个静态可变对象
| 字段 | 行 |
|---|---|
Z_TESR_REBUILD |
:40 —— Tracy zone tesrRebuild,COLOR_CLIENT |
IDENTITY |
:849 —— private static final Matrix4f |
⚠️ IDENTITY 是共享的可变 Matrix4f。若被某处 set() 或乘性修改,全局所有「单位矩阵」基准被污染。需靠源码不可见的约定保证不被改。
3 个 fastutil 结构 + 1 个 LongSupplier
| 字段 | 类型 | 行 |
|---|---|---|
source |
AngelicaBufferSource |
:42 |
clock |
LongSupplier |
:43 |
nowMs |
long |
:44 |
baseMV |
Matrix4f |
:45 |
fastutil import(5 个):FloatArrayList、IntArrayList、LongArrayList、ObjectArrayList、Object2ObjectOpenHashMap、Reference2ObjectOpenHashMap(:12-17)。Reference2ObjectOpenHashMap 是弱引用键 —— 组可被回收。
LongSupplier clock(:43)是注入的时间源(不是直接 System.currentTimeMillis),便于测试。nowMs(:44)是缓存的当前时间,每帧更新一次。
⚠️ nowMs 是实例字段。若 RetainedTesrGroups 有多个实例(每个渲染层一个?),各自的 TTL 判定基于各自的 nowMs,只要 clock 相同就一致。但若某个实例的 nowMs 未更新,TTL 永不过期。
⚠️ 文件 1083 行中约 1000 行本条目未逐行阅读(只读了 :1-45、:660-680、以及 grep 出的常量/类声明)。Group(:172,约 180 行)、InstanceColumns(:94,约 80 行)、TexRun(:75,约 20 行)的字段与算法本条目不可判定。 本条目只覆盖结构性事实。
TesrAnchorMath:锚点量化与实例哈希
78 行,3 个 public 静态方法,是 RetainedTesrGroups 的数学基础。
| 常量 | 值 | 行 |
|---|---|---|
ANCHOR_GRID |
16 | :7 |
REANCHOR_DISTANCE |
512.0 |
:8 |
HASH_QUANT |
4096f |
:9(私有) |
anchorCoord(:13-15)
public static long anchorCoord(double cam) { return (long) Math.floor(cam / ANCHOR_GRID) * ANCHOR_GRID; }
向下取整到 16 的倍数,返回 long。三个轴各调一次。
⚠️ Math.floor(cam / 16.0) * 16.0 在 cam 极大时会丢精度。cam 是 double,除以 16 是精确的(2 的幂),Math.floor 精确,乘 16 也精确(2 的幂)。所以这个实现实际是精确的 —— 但依赖 ANCHOR_GRID 恰为 2 的幂。改成 10 或 15 就会引入误差。
⚠️ 负坐标:-0.5 / 16 = -0.03125,floor → -1,× 16 → -16。正确(负方向也向下取整到网格)。
shouldReanchor(:17-22)
final double dx = camX - anchorX; ...
return dx * dx + dy * dy + dz * dz > REANCHOR_DISTANCE * REANCHOR_DISTANCE;
纯平方距离比较,阈值 512.0² = 262144。用距离平方而非距离,避免 sqrt。
⚠️ 阈值 512 与 ANCHOR_GRID = 16 相差 32 倍 —— 意味着相机要走出 32 个网格才会重锚。这是个刻意选择的宽阈值(重锚会清空所有相对化坐标,代价高)。
⚠️ 严格 >(不是 >=)—— 恰好 512 不重锚。
toAnchorRelative(:24-28)
m.m30(m.m30() + (float) (camX - anchorX));
m.m31(m.m31() + (float) (camY - anchorY));
m.m32(m.m32() + (float) (camZ - anchorZ));
⚠️ **直接改 m 的第 3 行(平移)三个分量,就地修改传入的 Matrix4f。无返回值,无 Matrix4f 复用(每次 new)。调用方必须知道矩阵被修改了。
⚠️ (float) 强转在 double → float 时丢精度。锚点差值最大 512(因为超出会重锚),float 有 24 位有效数字,512 范围内精度约 3e-5 —— 对 1.7.10 的方块坐标完全够用。
⚠️ 只改 m30/m31/m32,不动 m03/m13/m23(行向量约定下的另一组平移)。这是列向量约定(m30 是第 3 行第 0 列 = x 平移) —— 与 GlintClock 里 m01() 的用法同属 JOML 约定依赖。
instanceHash(:30-~50)
long h = templateIdentity * 0x9E3779B97F4A7C15L; // :31
h = mix(h, quantize(mA.m00())); // :32
h = mix(h, quantize(mA.m01())); // :33
... // 9 个矩阵元素
⚠️ 0x9E3779B97F4A7C15L 是 64 位黄金比例常数(2⁶⁴/φ 的整数部分) —— 即 SplitMix64 / Fibonacci hashing 的乘子。templateIdentity 先乘它做初始扩散,再用 mix 逐个混入量化后的矩阵元素。
⚠️ 只用了 3×3 的 9 个元素(m00..m22)—— 不含平移(m03/m13/m23 或 m30/m31/m32)。这是有意的:平移不同但旋转/缩放相同的实例应归入同一组(用相对坐标渲染)。但 instanceHash 与 toAnchorRelative 的「平移在哪一列」约定必须一致,否则哈希会漏掉本该区分的差异。
⚠️ quantize(x) 用 HASH_QUANT = 4096f(:9)做量化 —— 即 Math.round(x * 4096) 之类。量化步长是 1/4096 ≈ 0.000244。两个矩阵元素相差小于这个步长会被判为同一实例。对 1.7.10 的方块级模型足够(最小有意义差异远大于 1/4096),但对 TESR 的连续缩放/旋转动画可能不够。
⚠️ mix 与 quantize 的实现本条目未读(在 :41 之后)。quantize 对负数的处理、mix 的最终化步骤本条目不可判定。
EntityMaterials:14 个共享材质
121 行,全是 static final 常量,是本条目里最「平」的文件。全部 package-private(无 public)。
| # | 常量 | 行 | 构造 |
|---|---|---|---|
| 1 | CUTOUT |
:11 |
.cutout(0.1f).stream() |
| 2 | CUTOUT_HALF |
:12 |
.cutout(0.5f).stream() |
| 3 | SOLID |
:13 |
.stream() |
| 4 | TRANSLUCENT |
:14 |
.translucent().cutout(1f/255f).noDepthWrite().stream() |
| 5 | TRANSLUCENT_DEPTH_WRITE |
:15 |
.translucent().cutout(1f/255f).stream() |
| 6 | ADDITIVE |
:16 |
.additive().stream() |
| 7 | ADDITIVE_NO_DEPTH_WRITE |
:17 |
.additive().noDepthWrite().stream() |
| 8 | ADDITIVE_CUTOUT |
:18 |
.additive().cutout(0.1f).stream() |
| 9 | ADDITIVE_CUTOUT_NO_DEPTH_WRITE |
:19 |
.additive().cutout(0.1f).noDepthWrite().stream() |
| 10 | ADDITIVE_ALPHA |
:20 |
.additiveAlpha().stream() |
| 11 | ADDITIVE_ALPHA_NO_DEPTH_WRITE |
:21 |
.additiveAlpha().noDepthWrite().stream() |
| 12 | ADDITIVE_ALPHA_CUTOUT |
:22 |
.additiveAlpha().cutout(0.1f).stream() |
| 13 | ADDITIVE_ALPHA_CUTOUT_NO_DEPTH_WRITE |
:23 |
.additiveAlpha().cutout(0.1f).noDepthWrite().stream() |
| 14 | SHADOW |
:24 |
.translucent().cutout(0.1f).noDepthWrite().unlit().stream() |
TesrMaterial 在 com.gtnewhorizons.angelica.api.tesr(api/ 包)—— 见 API 层。
两个必须注意的细节
⚠️ 1f / 255f 只出现在半透明的两个常量里(:14、:15),不透明的全部用 0.1f。即「alpha cutoff」在两个透明度族里取值不同:半透明用 1/255(几乎不裁剪),不透明用 0.1。这个差异是刻意的(半透明要保留几乎所有片元,不透明要剔除 10% alpha 以下的边),但源码无注释说明。
⚠️ SHADOW 是唯一带 .unlit() 的常量(:24)—— 阴影不参与光照计算,且 translucent + cutout(0.1f) + noDepthWrite 的组合与 EntityShadowBatcher 配套。
⚠️ 14 个常量构成 3 个正交维度的组合:transparency(solid / translucent / additive / additiveAlpha)× cutoff(无 / 0.1 / 0.5 / 1-255)× depthWrite(是 / 否)。但组合并不完备 —— 例如没有 translucent + cutout(0.1f)(只有 1/255),没有 solid + noDepthWrite。缺哪些组合是有意的还是遗漏,本仓库无法判定。
⚠️ 全部 14 个是包私有,外部 mod 无法复用(只能走 api/ 的 builder 自己造)。
ShadowQuadMath:11 字段记录格式
98 行,public final,全部是常量 + 静态方法。这是「数据布局的定义方」。
| 常量 | 值 | 行 |
|---|---|---|
RECORD_SIZE |
11 | :17 |
11 个字段索引(:19 一行内全部定义):
| 索引 | 名字 | 语义 |
|---|---|---|
| 0 | MIN_X |
|
| 1 | Y |
⚠️ 没有 MIN_Y / MAX_Y —— 只有单个 Y |
| 2 | MIN_Z |
|
| 3 | MAX_X |
|
| 4 | MAX_Z |
|
| 5 | U_MIN_X |
|
| 6 | V_MIN_Z |
|
| 7 | U_MAX_X |
|
| 8 | V_MAX_Z |
|
| 9 | COLOR |
|
| 10 | LIGHT |
⚠️ 索引 0-4 是几何,5-8 是 UV,9-10 是颜色与光照 —— 三段布局。
⚠️ Y 只有 1 个(索引 1),而 X 和 Z 各有 MIN/MAX 两个 —— 阴影 quad 在 Y 方向是退化的(贴在地面上,高度固定)。这与 1.7.10 的实体阴影(textures/misc/shadow.png 的水平面片)一致。没有 MAX_Y 是正确的,不是遗漏。
⚠️ 10 个索引常量在一行内用逗号分隔声明(:19)—— 这是 Java 允许的多变量声明,但不符合格式规范,IDE 格式化会拆行。同时这也意味着一个笔误会静默改变索引(因为编译器不检查)。
UV 命名顺序:U_MIN_X(5)、V_MIN_Z(6)、U_MAX_X(7)、V_MAX_Z(8)—— X 的 U 夹在 Z 的 V 之间,是「按角点分组」而不是「按分量分组」。这个交错顺序不可重排。
EntityShadowBatcher
172 行,public final。
| 常量 | 值 | 行 |
|---|---|---|
SHADOW_TEXTURE |
new ResourceLocation("textures/misc/shadow.png") |
:29 |
PACKED_UP |
NormI8.pack(0f, 1f, 0f, 0f) |
:30 |
INITIAL_QUADS |
256 | :32 |
INITIAL_MVS |
8 | :33 |
⚠️ SHADOW_TEXTURE 是 1.7.10 原版资源路径(textures/misc/shadow.png),没有 minecraft: 前缀 —— 1.7.10 的 ResourceLocation 单参数构造把缺省域设为 minecraft。这与 BeaconBeamMeshes.BEACON_TEXTURE、VanillaModelMeshes.SIGN_TEXTURE 的写法一致(全仓统一风格)。
⚠️ PACKED_UP 与 VertexTransform.PACKED_UP 是两份独立定义的同值常量(VertexTransform.java:27 vs EntityShadowBatcher.java:30),都是 NormI8.pack(0f, 1f, 0f, 0f)。同包内两份同值常量,无关联。
⚠️ INITIAL_QUADS = 256 与 INITIAL_MVS = 8 相差 32 倍 —— 每个 modelview 矩阵要展开成多个 quad(ShadowQuadMath.RECORD_SIZE = 11 个字段的记录,一个 box 有 4 个侧面 → 4 个 quad)。8 个 MVS × 4 quad = 32,接近 256/8。 具体比例关系本条目未读方法实现,不臆断。
用 fastutil 的 IntArrayList / LongArrayList / FloatArrayList(RetainedTesrGroups 也用同 3 个)。
VanillaModelMeshes
136 行,public final,为原版方块实体提供预烘焙网格。
| 常量 | 值 | 行 |
|---|---|---|
MODEL_SCALE |
0.0625f |
:22 |
LID_CLOSED_EPS |
1.0e-4f |
:23 |
WHITE |
TesrMaterial.builder().color(1f,1f,1f,1f).build() |
:24 |
SIGN_TEXTURE |
new ResourceLocation("textures/entity/sign.png") |
:25 |
⚠️ MODEL_SCALE = 0.0625f = 1/16 —— 与 EntityFX 粒子半边长 0.1F(见 粒子条目)是不同量级。1/16 是「一个方块 = 16 个模型单位」的 Minecraft 惯例。注意 0.0625F 与 0.0624375F(粒子 UV 魔数)字面形近但值不同(差 2⁻¹⁶)—— 极易混淆。
⚠️ 0.0625f 写的是字面量不是 1.0f/16.0f —— 与 GlintClock 的 0.33333334f 同类写法(保持 float 精确值)。
两张弱引用键表(:34-35)
private static final Reference2ObjectOpenHashMap<ResourceLocation, ChestKeys> SINGLE_CHEST_KEYS = ...; // :34
private static final Reference2ObjectOpenHashMap<ResourceLocation, ChestKeys> DOUBLE_CHEST_KEYS = ...; // :35
⚠️ ResourceLocation 作弱引用键 —— 好处是资源包卸载时键可回收;坏处是 ResourceLocation 被回收后弱键会被自动清空,但 map 里对应的 ChestKeys 也在同一 entry 里(Reference2Object 的 value 是强引用,key 被回收后 value 仍在,直到 entry 被清理)。这可能导致 ChestKeys 比预期活得久。
3 类键(:36-41)
| 键 | 行 | 类型 |
|---|---|---|
KEY_SIGN_STANDING |
:36 |
new Object() —— 单例 Object 键 |
KEY_SIGN_WALL |
:37 |
同上 |
SKULL_KEYS |
:41 |
Map<SkullKey, Object>,普通 HashMap |
⚠️ KEY_SIGN_STANDING / KEY_SIGN_WALL 是 new Object() 的裸实例键(Object 没有 equals/hashCode → 身份相等)。只能作为 map 键使用,不能反查语义 —— 调试时打印出来是 java.lang.Object@1a2b3c。没有名字常量。
⚠️ SKULL_KEYS 用普通 HashMap(:41)而箱子用 Reference2ObjectOpenHashMap(:34-35) —— 同一个类里两种 map 实现。若 SkullKey 的生命周期与 ResourceLocation 不同,这是有意的;否则是不一致。本条目未读 SkullKey 的定义(在 api/ 或本文件内,api/ 见 API 层)。
Builder(:104,private static final class 实现 TesrMeshBuilder)与静态 BUILDER(:43)是构建器的单例。
BeaconBeamMeshes
108 行,public final,为信标光柱提供内外两层。
| 常量 | 值 | 行 |
|---|---|---|
BEACON_TEXTURE |
new ResourceLocation("textures/entity/beacon_beam.png") |
:16 |
INNER |
TesrMaterial.builder()... |
:18 |
OUTER |
TesrMaterial.builder()... |
:21 |
INNER_KEY |
new Object() |
:26 |
OUTER_KEY |
new Object() |
:27 |
INNER_RADIUS |
0.2 |
:29 |
BEAM_HEIGHT |
256.0 |
:30 |
INNER_V_TOP |
BEAM_HEIGHT * (0.5 / INNER_RADIUS) = 640.0 |
:31 |
INNER_BUILDER |
sink -> {...} |
:33 |
OUTER_BUILDER |
sink -> {...} |
:49 |
⚠️ INNER_V_TOP 可验算:256.0 * (0.5 / 0.2) = 256.0 * 2.5 = 640.0。源码写成表达式而非字面量 640.0(:31),是为了让「V 坐标 = 高度 / 内半径 × 0.5」的推导可见。改动 INNER_RADIUS 会自动改变 V 坐标 —— 这正是它被写成表达式的原因。
⚠️ BEAM_HEIGHT = 256.0 是原版 1.7.10 的信标光柱高度(WorldProvider 的 beacon 光柱)。INNER_RADIUS = 0.2 是内层半径。两者都是原版魔数。
⚠️ INNER_KEY / OUTER_KEY 又是 new Object() 裸实例键(:26-27),与 VanillaModelMeshes.KEY_SIGN_* 同一手法。全包 4 处这样的键(信标 2、牌子 2)。
⚠️ INNER_BUILDER / OUTER_BUILDER 是 static final 的 lambda(:33、:49)—— 持有 sink 参数做网格构建。它们是 static 的,所以构建出的网格若被缓存,是全进程共享的。
已知问题 / 风险
RetainedTesrGroups1083 行中约 1000 行本条目未逐行阅读(只覆盖了 5 个常量、4 个内部类、2 个 need 位标志、live()与Instancing.CUBE的使用)。Group/InstanceColumns/TexRun的字段与算法本条目不可判定。这是本条目最大的已知缺口。RetainedTesrGroups类整体包私有但实现内嵌上游的钩子接口(:32AngelicaBufferSource.LayerDrawHook),无main里的显式new—— 生命周期由上游的AngelicaBufferSource持有。GROUP_TTL_MS/TEMPLATE_TTL_MS/LRU_TIMEOUT_MS三处共用一个常量,改一处改三处。PROMOTE_AFTER_REBUILDS = 3与DEMOTE_AFTER_STABLE_FRAMES = 4是不对称的滞回设计(易被误读为笔误)。MERGE_SCAN = 4限制合并扫描范围,组数多时可合并的组会被漏掉。live()的frameMark == frameMark判据使「本帧未渲染的组」等同失效。IDENTITY是共享可变的static final Matrix4f(RetainedTesrGroups.java:849),被就地修改会污染全局基准。nowMs是实例字段,某实例未更新则其 TTL 永不过期。need的两处检查不对称((need & NEED_CUBE) != 0vsneed != 0),易读成疏漏。TesrAnchorMath.anchorCoord的精确性依赖ANCHOR_GRID = 16恰为 2 的幂(:7、:14),改成 10/15 会引入浮点误差。instanceHash的quantize步长 1/4096(HASH_QUANT = 4096f),对连续动画可能不足;且只哈希 3×3 不含平移,依赖与toAnchorRelative的列约定一致。ShadowQuadMath的 10 个索引常量挤在一行(:19),编译器不检查,笔误会静默改变索引。ShadowQuadMath.RECORD_SIZE = 11与 UV 的交错命名(U_MIN_X/V_MIN_Z/U_MAX_X/V_MAX_Z)不可重排。PACKED_UP在同包内两份独立定义(EntityShadowBatcher.java:30与VertexTransform.java:27)。EntityMaterials的 14 个材质组合不完备(无translucent + cutout(0.1f)、无solid + noDepthWrite),缺哪些是有意还是遗漏本仓库无法判定;且半透明族用1/255、不透明族用0.1的 cutoff 差异无注释。VanillaModelMeshes.MODEL_SCALE = 0.0625f与粒子 UV 魔数0.0624375F字面形近值不同(差 2⁻¹⁶),极易混淆。VanillaModelMeshes同一文件内混用Reference2ObjectOpenHashMap(箱子)与普通HashMap(头颅),一致性未说明。- 4 处
new Object()裸实例键(VanillaModelMeshes2 个、BeaconBeamMeshes2 个),无名字常量,调试不可读。 InstanceColumns/Group/TexRun的字段与算法未读(同上 1)。
相关条目
- TESR 实例化管线 -
RetainedTesrGroups的驱动方;Instancing.CUBE的使用 - TESR 网格捕获 -
TemplateBuffer的生产者 - API 层 -
TesrMaterial/TesrMeshBuilder/SkullKey的定义 - 渲染崩溃恢复 -
TesrBatchRenderer.clearRetained()清的就是本条目的组 - Iris(内嵌) -
NormI8/CapturedRenderingState的来源 - Subprojects(内嵌子项目) - GLSM 的
InstancedAttribs/Instancing/CubeParams/CombinedGlint - Compat Runtime -
compat.mojang.RenderLayer的归属 - AngelicaCommand - 调试指令的入口