Repository navigation
[1.21.4] Share structure previews 共用结构渲染与只读界面预览 - #284
Conversation
- 迁移隔离结构沙盒、相机拾取与原生界面浮窗 - 提供自定义界面注册和附加渲染接口,解除对手册的依赖 - 适配当前游戏版本的渲染与透明度补丁 - 补充通用翻译、接口说明及只读作用域回归检查
🌿 Roseau API Breaking Change Report
|
Gugle2308
left a comment
There was a problem hiding this comment.
结论:PR #284 已合并(squash 23df6a0c,dev/1.21.4 tip,合并树与 head 7dffa045 完全一致,2026-11-14T18:43:21Z),我按合并后复核流程重做了一遍静态审查:1.21.4 的 4 个 mixin 目标与覆写前提全部成立,但与仍在开放的 1.21.1 姊妹 PR #283 相比,透明度实现走了不同机制,另有若干渲染状态未还原与零调用方代码需要后续处理。
一、合并态与范围
| 判据 | 结果 |
|---|---|
| state / merged / merged_at | closed / true / 2026-11-14T18:43:21Z |
| merge_commit_sha | 23df6a0c98529710785d3cb2d92bf712c4fafb01 = dev/1.21.4 tip |
| 合并树 vs head | 空 diff ⇒ 进主干的就是 7dffa045 |
| 规模 | 32 文件 +3516/−0(含 module.rendering/** 新模块内容、lang 两份、previewTest) |
| head CI | build / build ✅ |
| 既有评论 | 仅 Roseau API 报告(/issues/284/comments 计数 1) |
二、跨版本分歧(本次最值得跟进的一项)
同一天合并的 #277 与本 PR、以及仍开放的 #283 是同一套功能的三个版本,但透明度钩子的作用面不同:
| 版本线 | 钩子 | 影响范围 |
|---|---|---|
| 1.21.1(#283,仍 open) | @Mixin(GuiGraphics) + @ModifyVariable(method="setColor", ordinal=3) |
只改 GUI 本地颜色,作用面窄 |
| 1.21.4(本 PR) | @Mixin(RenderSystem) + @ModifyVariable(method="setShaderColor", ordinal=3) |
改全局着色器颜色,预览作用域内所有 setShaderColor 调用的 alpha 都被乘 |
已核对 1.21.4 事实:RenderSystem.setShaderColor(float,float,float,float)(mappings 410:415,该类只有这一个重载)⇒ 名字选择器无歧义;目标为静态方法、handler 为 static ✅;argsOnly=true, ordinal=3 指向第 4 个 float(alpha)✅。
问题不在「能不能注入」,而在两条线视觉行为会不一致:1.21.4 的写法会让预览作用域内任何 RenderSystem.setShaderColor(含被预览屏幕自己绘制物品/提示框时的调用、以及同作用域内其他模组代码)的 alpha 一起被乘,1.21.1 则只影响 GuiGraphics 自己的颜色状态。建议在 #283 合并前明确取舍:要么把 1.21.4 收窄成 GuiGraphics 路线,要么把 1.21.1 也改成 RenderSystem 路线并补一条注释说明这是有意扩大。否则后面 1.21.8 / 1.21.11 / 26.x 各自选一个,会出现「同一结构预览在不同 MC 版本淡入淡出强度不同」的难查问题。
三、渲染状态与可选依赖(合并后建议)
StructurePreviewRenderer.render(level, cameraRig)里的RenderSystem.clear(GlConst.GL_DEPTH_BUFFER_BIT):这是便捷重载,在 GUI pass 内清整屏深度且未开 scissor,会把此前绘制内容的深度抹掉;GUI 里后续依赖深度的物品/文字绘制可能错位。建议要么去掉这次 clear,要么限定在预览区域内(配合enableScissor)。- 雾效不还原:
render(...)设置RenderSystem.setShaderFog(FogParameters.NO_FOG)后,finally只做popMatrix + restoreProjectionMatrix + Lighting.setupFor3DItems(),没有恢复雾参数;1.21.4 起雾是FogParameters对象,残留NO_FOG会带到调用方后续绘制。1.21.1 那份用的是 fog color/start/end/shape 四件套,同样未还原——两条线可以一起修。 PreviewLightmapAtlas.bind()把预览图集绑到纹理单元 2 且不还原原版LightTexture.LIGHTMAP_LOCATION,作为库会给同帧后续绘制留脏状态。StructureGuiRenderStateMixin(setupRenderStateTAIL)在预览作用域内对每个 shard 无条件enableBlend()+defaultBlendFunc():1.21.4 mappings 里RenderStateShard.setupRenderState()全层级只有一处声明(38:39),所以确实覆盖所有 shard——包括显式NO_TRANSPARENCY(内部会disableBlend)和LIGHTMAP/OVERLAY等不该混合的 shard,而且打开后不关闭。建议改为在StructureGuiPreview.render里包住screen.render(...)手动开关,或把条件收窄到 texture/transparency 组合。SodiumSpriteBridge:compileOnly 'maven.modrinth:sodium:mc1.21.4-0.6.13-neoforge'+ModList.get().isLoaded("sodium")只挡「没装」,挡不住「装了别的 Sodium 版本 / 替代实现」⇒SpriteUtil.INSTANCE.markSpriteActive可能在预览渲染路径抛NoSuchMethodError。建议try/catch (Throwable)(只是动画纹理提示,失败无害)或做版本区间校验。
四、零调用方代码与 API 稳健性(不阻塞)
StructureBlockEntityDetails.describe(144 行)、StructureDetailsPager(74 行)、StructureGuiSession.tint(本 PR 内仅被PreviewRegressionTest调用)没有生产调用方,却带进 107×2 条nbt.field.*翻译。要么在机器/手册界面接线,要么先删掉并标 TODO。SectionOffsetVertexConsumer只对 3 参addVertex做分段平移,putBulkData(...)(两个重载)与 11 参addVertex、addVertex(Matrix4f,…)都直通 ⇒ 任何走 bulk 的调用会把几何画到分段原点。SandboxRenderLevel.getFilledBlocks()返回复用的MutableBlockPos,public 方法配可变返回值,调用方collect(toList())会拿到 N 个同一实例(内部getBounds()用forEach、picker用immutable()都没问题,但这是 API 陷阱)。StructureGuiProviders.register冲突时直接抛IllegalStateException(客户端初始化期)⇒ 两个模组登记同一方块 id 会变成启动崩溃;建议 log + first-wins。SandboxRenderLevel.recipeAccess()/fuelValues()用Objects.requireNonNull(Minecraft.getInstance().level)委托,客户端断开/资源重载期间若仍有预览存在会 NPE;getSeaLevel()硬编码 63 也建议注明。StructureBlockEntityDetails.appendArray每次getAsByteArray()/getAsIntArray()/getAsLongArray(),lambda 内又取一次 ⇒ 逐元素重复分配数组(MAX_LINES有上界,仍可一行提出)。StructureSandboxFactory里EntityType.loadEntityRecursive(..., EntitySpawnReason.LOAD, ...)与 1.21.1 的写法不同是对的;entity.getSelfAndPassengers().forEach(level::addEntity)会先removeEntity(id, DISCARDED),结构内两个实体 id 相同(如手工编辑过的 NBT)时后者会顶掉前者,可接受但值得注释。
五、验证边界
未执行 Gradle 构建、runClient 与游戏内实测(PR 自述「客户端画面尚未实测」);previewTest 只覆盖 provider 注册/重复保护、renderOnly 嵌套与异常恢复、easeOut 单调性,不覆盖渲染。本文结论来自静态读码 + 1.21.4 官方 client_mappings/混淆 client.jar 核对(RenderSystem.setShaderColor(FFFF)V 唯一重载、RenderStateShard.setupRenderState() 单一声明、Screen.renderTransparentBackground(GuiGraphics)、RecipeBookComponent.isVisibleAccordingToBookData()、ChunkAccess#setBlockState 抽象而 LevelChunk 实现 ⇒ SandboxLevelChunk 覆写成立)+ 合并树/head 对账 + head CI。建议实机补测:真实容器屏在 100%/150% GUI scale 下的浮窗与裁剪、F3 隐藏 GUI、开光影与未装/装 Sodium 两条路径、同一帧先渲染预览再渲染原版 GUI 的深度是否正确,以及与 #283 对比淡入淡出观感。
将结构渲染、隔离沙盒、相机拾取和只读 GUI 浮窗移入砧库,让手册与机器界面共用实现,避免机器功能依赖手册模组。
验证:
:anvillib-rendering-neoforge-1.21.4:build(含previewTest)与git diff --check通过;已对照当前版本源码核查 Mixin 目标。客户端画面尚未实测。