TESR 实例化管线(rendering/tesr 核心)
基本信息
| 属性 | 值 |
|---|---|
| 包 | com.gtnewhorizons.angelica.rendering.tesr |
| 本条目覆盖 | 12 个文件(目录共 29 个) |
| 总行数 | 603 + 355 + 204 + 168 + 67 + 44 + 39 + 30 + 27 + 18 + 12 + 7 = 1574 |
| 主题 | 批处理资格判定、绘制状态描述、模板实例化、状态变更兜底、生命周期与混合作用域 |
rendering/tesr/ 共 29 个文件,是本条目与 TESR 网格捕获、TESR 保留组 三篇共同覆盖的对象。
| # | 文件 | 行数 | 可见性 |
|---|---|---|---|
| 1 | TesrBatchRenderer.java |
603 | public final(INSTANCE 单例) |
| 2 | InstancedTemplateRenderer.java |
355 | public final |
| 3 | DrawState.java |
204 | public final |
| 4 | BatchEligibility.java |
168 | public final |
| 5 | InstanceRing.java |
67 | public final |
| 6 | TesrProviderDispatch.java |
44 | public final |
| 7 | BatchStateFallback.java |
39 | 包私有 final class |
| 8 | TesrBlendScope.java |
30 | public final |
| 9 | PassRebindGate.java |
27 | public final |
| 10 | TesrLifecycle.java |
18 | public final |
| 11 | TesrInstancingPipeline.java |
12 | public interface |
| 12 | TesrAttribution.java |
7 | public final |
TesrInstancingPipeline:3 方法接口
12 行,只 3 个方法:
| 方法 | 行 |
|---|---|
rebindCurrentPass() |
:7 |
hasInstancedVariant(Instancing kind) |
:9 |
bindInstancedVariant(Instancing kind) |
:11 |
Instancing 是 GLSM 的枚举(com.gtnewhorizons.angelica.glsm.ffp.Instancing),其定义在 GLSM 子项目,本条目不可判定其有哪些值。
实现方:PassRebindGate.rebindIfDirty()(:20-23)持有 WorldRenderingPipeline 并调 pipeline.rebindCurrentPass() —— 内嵌 Iris 的 WorldRenderingPipeline 实现了这个接口(net.coderbot.iris.pipeline)。所以这是 Angelica 接口 ← Iris 实现 的方向,与其它条目里「Angelica 调 Iris」相反。
⚠️ bindInstancedVariant 的实现方在本包内不可见(grep 不到实现类)。若 Iris 的 pipeline 未实现,调用会 ClassCastException 或 AbstractMethodError。本仓库无法判定谁提供实现。
TesrAttribution:7 行的全局槽
public final class TesrAttribution { // :3
private TesrAttribution() {} // :4
public static Class<?> currentRenderable; // :6
}
public static 可写字段,无 volatile、无封装、无注释。 用途是「当前正在渲染的 renderer 类」,供 BatchEligibility 判断状态变更是否发生在 renderer 内部。
三个复位点:
| 位置 | 动作 |
|---|---|
RenderRecovery.resetAfterCrash()(RenderRecovery.java:39) |
TesrAttribution.currentRenderable = null —— 直接写字段,不是调方法 |
BatchEligibility |
作用域内保存/恢复 |
| 正常渲染路径 | 由 mixin 设置 |
⚠️ 这是全仓「用 static 字段当跨类传参槽」最典型的一例,与 WitherArmorState.pendingInflate 同类。三个复位点分散在三个文件,任一路径漏复位都会让后续判定错乱。
⚠️ 字段是 Class<?> 强引用,会持有 renderer 类的 Class 对象 —— 阻止类卸载。renderer 类通常随 mod 常驻,影响可忽略。
TesrBlendScope:GLSM 状态的 push/pop 栈
30 行,把「进入混合作用域」的 GLSM 深度压进一个可扩容的 int 数组。
| 成员 | 行 | 说明 |
|---|---|---|
depths |
:12 |
int[4] 初值 |
depth |
:13 |
当前深度 |
public static void enter() { // :15-20
if (depth == depths.length) depths = Arrays.copyOf(depths, depth * 2);
depths[depth++] = GLStateManager.pushState(StateSet.BLEND);
}
public static void exit() { // :22-25
if (depth == 0) return;
GLStateManager.popStateTo(depths[--depth]);
}
public static void reset() { depth = 0; } // :27-29
⚠️ 只 push StateSet.BLEND 一个状态集。若作用域内改了矩阵、纹理、深度等,这些不在 push 范围内。
⚠️ exit() 在 depth == 0 时静默 return(:23)—— 不匹配的 enter/exit 不报错。这掩盖了配对错误。
⚠️ reset() 只置 depth = 0,不清 depths 内容(:28)。崩溃复位后 depths 里是过期的 GLSM 深度值。若复位后有人误调 exit(),depth == 0 会挡住 —— 这一层保护是有效的。
⚠️ 扩容用 Arrays.copyOf(堆分配)(:17),与 InstanceRing / MeshBuffer 用直接内存不同。enter() 在渲染热路径,深度超过 4 时会分配新数组。深度 4 对应 4 层嵌套混合作用域,实际几乎不会到。
PassRebindGate:按「世代」跳过重复 rebind
27 行,用两个世代计数器判断 Iris pass 是否需要重绑。
private static int lastProgramGen = -1; // :9
private static int lastFbGen = -1; // :10
rebindIfDirty()(:14-26):
| 步 | 行 | 动作 |
|---|---|---|
| 1 | :15 |
!Iris.enabled → 直接返回 |
| 2 | :16-19 |
program 代数 与 draw FBO 代数都没变 → 返回 |
| 3 | :20-23 |
取 pipeline,非 null 时 rebindCurrentPass() |
| 4 | :24-25 |
记录两个新世代 |
GLSM 侧的两个计数器:GLStateManager.getProgramGeneration() 与 GLStateManager.getDrawFramebufferGeneration()。这两个「世代」在 GLSM 中的递增规则本条目不可判定(GLSM 子项目),只能确认它们是 int 且在 program / FBO 变更时递增。
⚠️ 两个世代初值都是 -1(:9-10),GLStateManager 的初值是 0,所以首次调用必然走 rebind。正确。
⚠️ 第 3 步的 pipeline == null 时不更新世代(:24-25 在 if (pipeline != null) 之外)—— 每次调用都会重查。正确但略浪费。
⚠️ lastProgramGen / lastFbGen 无 volatile。若 rebind 在渲染线程、program 变更在其它线程,世代读取可能过期。实际都在渲染线程。
调用方:PlayerReflectionCapture.flush()(PlayerReflectionCapture.java:214)—— 即护甲/玩家反射条目里提到的那个「三重前置检查」之一。
BatchEligibility:三态资格判定
168 行,判定「当前能否批处理」。核心是一个 3 值 byte 枚举。
| 常量 | 值 | 行 | 语义 |
|---|---|---|---|
UNKNOWN |
0 |
:11 |
未判定 |
SAFE |
1 |
:12 |
可批处理 |
DENIED |
2 |
:13 |
不可批处理 |
⚠️ byte 值 1/2 与 DrawState.CULL_BACK = 1 / CULL_FRONT = 2 / CULL_BOTH = 3 数值巧合但语义无关。两套常量都在本包,不要混用。
入口与出口
| 方法 | 行 | 说明 |
|---|---|---|
begin(byte state, long drawCount) |
:84 |
返回 boolean |
end(byte state, long drawCount) |
:97 |
返回新的 byte |
beginIsolated(byte state, long drawCount) |
:68 |
隔离作用域 |
endIsolated(byte state, long drawCount) |
:76 |
返回 byte |
batchingAllowed() |
:111 |
|
insideRenderer() |
(BatchStateFallback.java:19 用到) |
是否在 renderer 内部 |
⚠️ begin 返回 boolean 而 end 返回 byte —— 不对称。调用方必须把 begin 的 boolean 转回 byte 存起来,再传给 end。这个不对称是接口设计的坑。
SavedScope:5 字段快照(:30-34)
private static final class SavedScope {
int depth, bracketDepth, parts; // 3 个 int
boolean allowed, uncapturedState; // 2 个 boolean
}
save(long drawCount)(:36)/ restore(long drawCount)(:51)成对。isolatedScopes 是 ArrayList<SavedScope>(:27),栈式。
⚠️ save / restore 都接收 drawCount 参数 —— 说明它们还会断言「作用域内 draw call 数量是否符合预期」,但本条目未读这两个方法的完整实现。beginExpectedDraws(:147)/ endExpectedDraws(:153)是一对显式的预期 draw 计数接口。
计数器回调
| 方法 | 行 | 何时调 |
|---|---|---|
onPartQueued() |
:136 |
有 ModelPart 入队 |
onPartFallback(drawsBefore, drawsNow) |
:140 |
回落到非批处理路径,参数是前后 draw 数 |
onUncapturedState() |
(BatchStateFallback.java:27 调) |
检测到未捕获的状态变更 |
onBatchFlushed(long) |
(BatchStateFallback.java:36 调) |
flush 完成,参数是本次的 draw 数增量 |
onStateChange(Object renderer, byte state) |
:159 |
状态变更,带 renderer 引用 |
⚠️ onBatchFlushed(GLStateManager.drawCalls - before)(BatchStateFallback.java:36)—— flush 过程中若有第三方插入额外 draw call,计数会被污染,导致 onPartFallback 的启发式判断失准。
BatchStateFallback:未捕获状态变更的兜底 flush
39 行,包私有,是全包唯一包私有的类。类注释(:8):
Uncaptured state changes draw the queued batch before the mutation takes effect.
安装机制(:10-12)
private static final Runnable FLUSH = BatchStateFallback::flush;
static void install() { BatchStateGuard.flush = FLUSH; }
⚠️ 它通过写 GLSM 的 BatchStateGuard.flush 静态字段来接管回调(com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard)。这是一个全局单槽回调 —— 任何其它代码写 BatchStateGuard.flush 都会覆盖本安装。GLSM 侧的字段可见性与初始化顺序本条目不可判定(GLSM 子项目)。
⚠️ install() 是唯一的安装入口,调用方在本包外不可见。若 GLSM 初始化晚于 Angelica 安装,会被 GLSM 自己的默认值覆盖。本仓库无法判定安装顺序。
flush 的三道提前返回(:17、:19)
if (!models.isActive() && !tesrs.hasPendingGeometry()) return; // :17
if (!models.hasQueuedGeometry() && !tesrs.hasQueuedGeometry()
&& !BatchEligibility.insideRenderer()) return; // :19
第 2 行的注释(:18)说明了它的必要性:
Pass setup may establish defaults after beginPass, before any renderer or queued draw.
即「pass 建立默认值可能发生在 beginPass 之后、任何 renderer 或入队绘制之前」—— 此时的 flush 是多余的。
唯一的诊断输出(:20-24)
if (models.entityPassActive()) {
final boolean inside = BatchEligibility.insideRenderer();
GLStateManager.warnOnce("batch-fallback-entities:" + inside,
"Uncaptured state change {} a renderer flushed the entity batch early",
inside ? "inside" : "between", new Throwable());
}
warnOnce 的 key 是 "batch-fallback-entities:" + inside —— 只有 2 个可能的 key(true / false)。即每种情况一生只警告一次,带完整 new Throwable() 栈。
⚠️ 只诊断 models.entityPassActive() 这一种情况。若 flush 发生在非实体 pass(阴影、光照),静默无日志。这是最大的可诊断性缺口。
状态保存/恢复(:25-37)
final int mode = GLStateManager.getMatrixMode().getMode(); // :25
final int unit = GLStateManager.getActiveTextureUnit(); // :26
...
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW); // :30
models.flushForStateChange();
tesrs.flushForStateChange();
} finally {
GLStateManager.glMatrixMode(mode); // :34
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit); // :35
BatchEligibility.onBatchFlushed(GLStateManager.drawCalls - before); // :36
}
只保存/恢复 2 个状态:矩阵模式与活动纹理单元。⚠️ flush 内部若改了其它 GL 状态(program、VAO、FBO、混合),不会被恢复 —— 依赖 flushForStateChange 自己处理。
⚠️ onBatchFlushed 在 finally 里调,用的是 drawCalls - before(:28)。若 flushForStateChange 抛异常,onBatchFlushed 仍会执行(finally 语义),但计数不完整。
DrawState:绘制状态的描述与 interning
204 行,public final,是绘制状态的值对象 + 驻留表。
两组枚举常量
剔除模式(4 值)(:14-17):
| 常量 | 值 | 语义 |
|---|---|---|
DISABLED |
0 |
不剔除 |
CULL_BACK |
1 |
剔背面 |
CULL_FRONT |
2 |
剔正面 |
CULL_BOTH |
3 |
双面剔除 |
混合模式(5 值)(:19-23):
| 常量 | 值 | 语义 |
|---|---|---|
OPAQUE |
0 |
|
TRANSLUCENT |
1 |
|
ADDITIVE |
2 |
|
ADDITIVE_ALPHA |
3 |
|
GLINT |
4 |
⚠️ 剔除是 4 值(0…3,两位),混合是 5 值(0…4)。packCull(boolean enabled, int faceMode)(:93)把两者打包,剔除占 2 位、混合占 3 位(共 5 位)。:56 的 int h = cull; 是哈希计算的起点。
驻留表(:25)
private static final ObjectOpenCustomHashSet<DrawState> CACHE = new ObjectOpenCustomHashSet<>(HashStrategy.INSTANCE);
fastutil 的 ObjectOpenCustomHashSet + 自定义 HashStrategy.INSTANCE。这是 Angelica 自己实现的 HashStrategy(不在本条目覆盖的文件里,属 api/ 或其它包 —— 见 API 层)。
of(cull, blend, depthEqual, depthWrite, colorWrite, alphaCutoff, lit, offsetFactor, offsetUnits)(:68)是 9 参数工厂。⚠️ 9 个参数里 4 个是 boolean —— 工厂调用点极易发生参数错位(相邻 boolean 传反不报错)。源码用长参数列表单行承载(:68),可读性差。
forMaterial(TesrMaterial material, int cull, boolean lit, float offsetFactor, float offsetUnits)(:72)从 TesrMaterial 派生,blendFor(TesrMaterial.Transparency)(:83)做 5 值映射。
liveCull()(:102)与 liveLit(TesrMaterial)(:106)读当前实时 GL 状态而非驻留值。
apply()(:110-~173,本文件最大方法)把状态写回 GLSM。equals(:175)/ hashCode(:180)/ toString(:185)三者齐备。
⚠️ 驻留表 CACHE 是静态且无上限。of() 每产生一个新状态组合就 intern 一个。9 个参数的组合空间很大,理论上是无界的内存增长。TesrLifecycle.reset() 调 DrawState.clearInterning()(:16)—— 复位入口存在。
TesrBatchRenderer:单例总控
603 行,public static final TesrBatchRenderer INSTANCE(:51)。全仓最重的渲染协调器之一。
三个 Pass 槽(:53-56)
| 常量 | 值 | 行 |
|---|---|---|
PASS_MAIN_0 |
0 |
:53 |
PASS_MAIN_1 |
1 |
:54 |
PASS_SHADOW |
2 |
:55 |
PASS_COUNT |
3 |
:56(私有) |
⚠️ PASS_MAIN_0 / PASS_MAIN_1 是两个主 pass(多 DrawBuffer / 渲染到纹理的两级主渲染),不是「主 pass 的第 0/1 项」。1.7.10 的主渲染只有 1 个 FBO,所以这两个槽在无 Iris 时只有一个会被用。
5 个 Tracy Zone(:60-64)
| Zone 名 | 常量 | 行 |
|---|---|---|
tesrOpaque |
Z_TESR_OPAQUE |
:60 |
tesrBatch |
Z_TESR_BATCH |
:61 |
tesrInstanced |
Z_TESR_INSTANCED |
:62 |
tesrText |
Z_TESR_TEXT |
:63 |
tesrDeferred |
Z_TESR_DEFERRED |
:64 |
全部 Tracy.COLOR_CLIENT。这是本包中 Tracy 标注最密集的文件 —— 说明 5 条路径是独立可观测的。
⚠️ Z_TESR_TEXT(文字)是一个独立路径 —— 说明 TESR 的 TileEntitySpecialRenderer 里可能有文本渲染(1.7.10 的 TileEntitySign 等)。
SWEEP_INTERVAL_MS(:58)
private static final long SWEEP_INTERVAL_MS = 5_000L —— 5 秒做一次清扫(清理过期模板 / 组)。无配置项。
LayerKey(:195)
static final class LayerKey —— 内部类,包私有。是渲染层的键。
对外方法(本条目已确认的)
| 方法 | 来源 | 语义 |
|---|---|---|
clearRetained() |
TesrLifecycle.java:11 |
清保留组 |
hasPendingGeometry() |
BatchStateFallback.java:17、:19 |
有待画几何 |
hasQueuedGeometry() |
BatchStateFallback.java:19 |
有已入队几何 |
flushForStateChange() |
BatchStateFallback.java:32 |
状态变更前 flush |
entityPassActive() |
BatchStateFallback.java:20 |
实体 pass 是否活跃 |
⚠️ clearRetained / hasPendingGeometry / hasQueuedGeometry / flushForStateChange / entityPassActive 全部是包私有(BatchStateFallback 在同包才能调)。外部 mod 无法直接操作 TESR 批处理。
InstancedTemplateRenderer:模板 + 实例环
355 行,模板网格的 GPU 实例化绘制。
| 常量 | 值 | 行 |
|---|---|---|
TEMPLATE_TTL_MS |
AngelicaTesrMeshCache.LRU_TIMEOUT_MS |
:35 |
TEMPLATE_STRIDE |
VERTEX_SIZE * 4 |
:37 |
TEMPLATE_FLAGS |
COLOR_BIT | TEXTURE_BIT | NORMAL_BIT |
:38 |
⚠️ TEMPLATE_TTL_MS 与 RetainedTesrGroups.GROUP_TTL_MS 都直接等于 AngelicaTesrMeshCache.LRU_TIMEOUT_MS(RetainedTesrGroups.java:37)—— 三处共用一个 LRU 超时常量,改一处即改三处。这是有意的单一真源(不是重复),但也意味着改 TTL 会同时影响 3 个子系统。
⚠️ TEMPLATE_STRIDE = VERTEX_SIZE * 4 —— VERTEX_SIZE 未在本文件 import 列表中出现(应来自 GLSM 或静态导入),其值本条目不可判定。
TemplateMesh(:42)与 Bucket(:51)是两个私有内部类。Bucket 与 TemplateBuffer.bucketEpoch / bucketIndex(TemplateBuffer.java:9-10)对应 —— 模板按 bucket 分组,bucket 有世代。
InstanceRing:实例数据环
67 行,public final,是固定容量的实例数据环形缓冲。
| 方法 | 行 | 语义 |
|---|---|---|
upload(ByteBuffer data, int stride) |
:17 |
返回 long(上传的字节数?地址?) |
keptUploadsEpoch() |
:38 |
返回 int |
bufferId() |
:42 |
返回 int |
postDraw() |
:46 |
绘制后 |
delete() |
:53 |
⚠️ upload 返回 long,本条目未读其实现,无法判定返回值语义(可能是上传字节数,也可能是地址)。
postDraw() 由 ParticleInstancer.endLayer() 调用(ParticleInstancer.java:240 附近的 ring().postDraw())—— 粒子与 TESR 共用同一套实例环机制(GLSM 的 Instancing)。
⚠️ delete() 的调用方在本包外不可见。若从不调用,GL buffer 泄漏。
TesrProviderDispatch:对外 provider 分发
44 行,接入 com.gtnewhorizons.angelica.api.tesr.TesrMeshProvider —— 这是 api/ 包,见 API 层。
resolveBlockEntityId(:16-25)
5 段 null 检查,逐层降级返回 0:
| 检查 | 行 | 返回 |
|---|---|---|
te == null |
:17 |
0 |
te.getBlockType() == null |
:18 |
0 |
matches == null |
:21 |
0 |
metaMap == null |
:23 |
0 |
| 正常 | :24 |
Math.max(0, metaMap.get(metadata)) |
⚠️ 最后一步 Math.max(0, ...) —— 若 metaMap 里有负 id(Iris 的 BlockMaterialMapping 可能用 -1 表示「无匹配」),被夹到 0。0 与「无匹配」在 Iris 侧可能不是同一个语义(FallingBlockRendering.blockMaterialId 用的就是 -1 表示无匹配,见 Tessellator 条目)。同一概念在两处的哨兵值不同(0 vs -1)。
tryRender(:27-43)—— 教科书级 try/finally
if (!(renderer instanceof TesrMeshProvider provider)) return false; // :28
final Object key = provider.angelica$meshKey(te); // :29
if (key == null) return false; // :30
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity(); // :32
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(te == null ? null : te.getBlockType(),
te == null ? 0 : te.getBlockMetadata()); // :33
GLStateManager.glPushMatrix(); // :34
try {
provider.angelica$transform(te, x, y, z);
AngelicaTesrMeshCache.INSTANCE.renderCached(key, provider.angelica$meshDirty(te), provider, te);
} finally {
GLStateManager.glPopMatrix(); // :39
CapturedRenderingState.INSTANCE.popCurrentBlockEntity(); // :40
}
return true;
⚠️ te == null 的三重检查冗余 —— 第 30 行 key == null 时若 te 必为 null(meshKey(te) 的实现决定的),第 33 行的 te == null ? ... 就多余。反之若 te 可为 null 且 key 非 null,第 33 行是必需的。源码保留了防御,但没有说明 te 是否真的可为 null。
⚠️ 两处 glPushMatrix / glPopMatrix 正确配对,CapturedRenderingState 的 push/pop 也配对。这是全仓异常处理最规范的方法之一,可作为 DeferredEntityOverlay.recycle()(缺 finally)与 RenderRecovery.resetAfterCrash()(13 步无保护)的对照。
⚠️ return true 在 finally 之后(:42)。若 renderCached 抛异常,return true 不执行,异常向上传播 —— 正确(不吞异常)。
TesrLifecycle:崩溃复位
18 行,5 步复位(无 try/finally):
public static void reset() { // :10-17
TesrBatchRenderer.INSTANCE.clearRetained(); // :11
ModelPartBatcher.INSTANCE.clear(); // :12
ParticleInstancer.clear(); // :13
RenderLayer.clearInterningAndHooks(); // :15
DrawState.clearInterning(); // :16
}
被 RenderRecovery.resetAfterCrash() 的第 9 步调用(RenderRecovery.java:37)。TesrBlendScope.reset() 是第 10 步(:38),TesrAttribution.currentRenderable = null 是第 11 步(:39)。
⚠️ TesrLifecycle.reset() 本身 5 步无异常保护,与 RenderRecovery 的 13 步同样问题。第 11 步 ParticleInstancer.clear()(:13)会 ParticleQuadMesh.delete() 与 ParticleDescriptorRegistry.clearCache()(见 粒子条目)—— 跨子系统的连锁清理。
⚠️ 5 步的顺序不可换:先清缓存(1-3)再清驻留表(4-5)。若 DrawState.clearInterning() 先执行而 ModelPartBatcher.clear() 仍持有 DrawState 引用,会出现「已清空的表被再次访问」。
已知问题 / 风险
TesrAttribution.currentRenderable是裸public static可写字段(:6),三个复位点分散在RenderRecovery/BatchEligibility/ 正常路径,任一漏复位即判定错乱。BatchStateFallback.install()通过写 GLSM 的BatchStateGuard.flush全局单槽回调(:12),安装顺序无保障,且会被任何其它写入覆盖。BatchStateFallback.flush的诊断只覆盖entityPassActive()一种情况(:20),非实体 pass 的提前 flush 完全静默。BatchStateFallback.flush只保存/恢复矩阵模式与纹理单元(:25-26),flush 期间的其它 GL 状态变更不被恢复。BatchEligibility.begin返回boolean而end返回byte(:84、:97),接口不对称,调用方必须手工转换。DrawState.of是 9 参数工厂,其中 4 个相邻boolean(:68),参数错位不报错。DrawState的CACHE驻留表静态无上限(:25),9 参数组合空间大,理论内存增长无界。TesrLifecycle.reset()与RenderRecovery.resetAfterCrash()均无异常聚合(TesrLifecycle.java:10-17),任一步抛异常后续全部跳过。TesrBlendScope.exit()在depth == 0时静默返回(:23),掩盖 enter/exit 配对错误。TesrBlendScope只 pushStateSet.BLEND(:19),作用域内其它状态不受保护。resolveBlockEntityId的哨兵值0与FallingBlockRendering.blockMaterialId的-1不一致(TesrProviderDispatch.java:24),同一「无匹配」概念两处不同。TEMPLATE_TTL_MS/GROUP_TTL_MS/LRU_TIMEOUT_MS三处共用一个常量(改一处改三处)。BatchEligibility的UNKNOWN/SAFE/DENIED = 0/1/2与DrawState.CULL_* = 0/1/2/3数值巧合(同包内两套无关常量)。PassRebindGate的两个世代计数器无 volatile(:9-10),pipeline == null时每次重查。InstanceRing.upload返回long,语义未在公开 API 文档中说明;InstanceRing.delete()的调用方在包外不可见。TesrInstancingPipeline.bindInstancedVariant的实现方在本仓库不可见 —— 接口由内嵌 Iris 实现,但 Iris 侧是否真的实现本仓库无法判定。InstanceRing被粒子与 TESR 共用(ParticleInstancer.endLayer调ring().postDraw()),一条链路的异常可能影响另一条。
相关条目
- TESR 网格捕获 - 模板数据的来源;
TemplateBuffer/TemplateCapture的定义方 - TESR 保留组 -
clearRetained()清的内容 - API 层 -
TesrMeshProvider接口的定义 - 渲染崩溃恢复 -
TesrLifecycle.reset()的调用方(复位第 9 步) - 粒子实例化 -
ParticleInstancer.clear()的清理对象;共用InstanceRing - 护甲 / 闪光 / 玩家反射 -
PassRebindGate的调用方 - Subprojects(内嵌子项目) - GLSM 的
BatchStateGuard/Instancing/StateSet - Iris(内嵌) -
WorldRenderingPipeline实现TesrInstancingPipeline - Compat Runtime -
compat.mojang.RenderLayer的归属