Skip to content

fix(compat): restore BetterPortals Refitted end portal rendering - #110

Merged
DHJComical merged 13 commits into
mainfrom
fix/betterportals-end-portal
Sep 2, 2026
Merged

fix(compat): restore BetterPortals Refitted end portal rendering#110
DHJComical merged 13 commits into
mainfrom
fix/betterportals-end-portal

Conversation

@DHJComical

Copy link
Copy Markdown
Owner

概述

修复 BetterPortals Refitted(0.4.1)末地传送门的视觉兼容性。修复前:无光影下没有"看穿看到末地"的效果(洞口只见星野贴图)且视觉位置低约一格;开启光影后传送门进入视角即严重卡顿(约 5 fps)、主世界地形整体消失;风暴修复后星野叠加层仍几乎不可见且随视角转动闪烁。传送功能本身一直正常。

根因与改动(四层根因叠加,详见 docs/compat/betterportals.md)

  1. glsm texgen 顶点着色器触发 NVIDIA 驱动 uniform 消除d4af535):逐分量覆写模式让驱动 DCE 掉 u_TexGenEyePlaneS,星野被拉成竖直条纹。改为单表达式 vec4 构造,语义等价。
  2. 星野叠加渲染被整体劫持96e318d):BPR 用无 world 的合成 dummy TE 调原版 TESR 画星野叠加层,依赖 shouldRenderFace 里的 blend 钩子;新增 EndPortalRenderPolicy 按调用来源分流,无光影下放行 legacy 路径由 glsm FFP 模拟执行。
  3. 客户端数组 draw 的 FFP flags 推导e61d697)+ compat shader uniform 存活断言(a266e75)。
  4. 光影管线重载风暴7ccf0ce):BPR 顺序双 pass 每帧往返切换维度,preparePipeline 每次切换都销毁重建管线并杀 celeritas worker → 地形永远来不及重建。改为按维度缓存切换;地形 program 缓存按管线实例分键;合成写入动态读取的当前 framebuffer,两个世界各自获得完整光影效果。
  5. 光影下星野叠加层被旁路a26efe2):实体阶段 Iris gbuffers program 已绑定,glsm 让位导致 texgen FFP 仿真永远不执行。光影下合成 TE 改走为光影设计的替代渲染器,并完整复刻 BPR 的 CONSTANT_ALPHA 淡出钩子(检测因子对 + 从真实 GL 状态读回常量 alpha,折入首层顶点 alpha)。
  6. 闲置维度管线回收162ed0e):停止渲染 30 秒以上的维度管线自动销毁(render target 组不再永久驻留),地形 program 缓存惰性同步清理。

如何验证

  • ./gradlew build --no-daemon 全绿(含 check:JUnit 自动化测试 + remap jar 结构/桥契约测试)。
  • 实机回归(实例 1.12.2-Cleanroom,NVIDIA RTX 5070 Laptop / 610.74):
    • 无光影:看穿(洞内可见末地景象)+ 星野旋涡 + 淡出 + 位置正常;
    • 光影(BSL):看穿与主世界各自带光影效果,帧数/地形正常;星野叠加层可见、转动视角不闪烁、淡出正常;
    • 主世界/末地/暮色森林三维度往返正常;破坏传送门闲置 30 秒后日志出现 Destroying idle pipeline for dimension,重新看门时管线一次性重建、无每帧风暴。

验证过的光影包与模组

  • 光影包:BSL(实机)
  • 模组:BetterPortals Refitted 0.4.1(+ forgelin-continuous)、Twilight Forest 3.11.1021

遗留

  • 不装 BetterPortals 的原版末地传送门/折跃门回归未实测(docs/compat/betterportals.md 已记待补)。

文档

  • docs/compat/betterportals.md:兼容状态更新为已验证,记录全部四层根因与验证记录。
  • docs/compatibility-matrix.md:BetterPortals Refitted 行更新为已验证。

Repackage org.joml under org.embeddedt.embeddium.impl.shadow.joml inside
the distributed jar, matching the upstream Celeritas shadow layout, while
the bundled joml jar stays nested for third-party mods that link against
the original namespace. Dev runs inject the shaded jar via crl.dev.extrapath
only, keeping it invisible to the application classloader to avoid splitting
mixin classes.
ExtraUtilities2's XUBlockStatic anonymous getQuads implementation mutates a
plain HashMap from ChunkBuilderMeshingTask worker threads, crashing with
ConcurrentModificationException once Celeritas meshing runs concurrently
with portal view passes. Redirect computeIfAbsent through a synchronized
wrapper; the accessor keeps refmap remapping by not disabling remap.
Strict preprocessor separation rejects shaders whose conditional directives
interleave with GLSL tokens, which made BetterPortals' portal shader fail to
initialize. Fall back to an evaluated-conditional path that keeps only the
taken branch, hoist leading comments into the preprocessor section to avoid
a glsl-lib injectFunction index fault, and parse integer literals before
identifiers so version guards evaluate correctly.
Per-component writes to a pre-initialized texGenCoord let NVIDIA drivers
dead-code-eliminate the uniform feeding a lone component (observed:
u_TexGenEyePlaneS dropped from GL_ACTIVE_UNIFORMS while T/R survive),
collapsing end-portal EYE_LINEAR texgen into stripes. Emit the equivalent
single-expression vec4 constructor instead; regression test parses the
generated GLSL and asserts the constructor feeds all eye planes.
BetterPortals draws its starfield overlay through a synthetic world-less
dummy TileEntityEndPortal and relies on the vanilla render path's blend
hooks (CONSTANT_ALPHA fade via shouldRenderFace) and projective texgen.
The replacement-renderer mixin cancelled that call unconditionally,
breaking the overlay. EndPortalRenderPolicy now routes only real world
block entities to the core-profile renderer and leaves synthetic calls
on the legacy path executed through the glsm FFP simulation.
The cached currentVertexFlags only reflect the last buffer-state setup,
so a client-array draw could select an FFP variant reading attributes it
does not provide (e.g. a black default vertex color for a POSITION-only
draw). Raw draw entry points now use preDrawFFP(), which derives flags
from the VAO's actually enabled attributes when client-side arrays are
in play and keeps the cached flags otherwise.
The BPR render_portal regression slipped through because the case only
asserted eliminated symbols. Also assert the uniforms Forge looks up by
name (sampler, screenSize, opacity, fogColor, fogDensity) stay present.
…uential passes

BetterPortals renders the remote dimension and the main world as two
sequential renderWorldPass calls in one frame (swapping Minecraft.world
and the target framebuffer between them). preparePipeline treated each
dimension flip as a world unload: destroyPipeline() plus a full reload
twice per frame, killing the celeritas workers every time so terrain
never got rebuilt (5 fps, main world terrain gone once a portal entered
the view).

Keep one pipeline per dimension cached and switch between them; terrain
chunk programs embed per-pipeline state, so IrisCeleritasChunkProgram
Overrides now keys its program cache by pipeline instance. The Iris
injections on renderWorldPass keep a pure-depth RenderWorldRecursionGuard
as defense against true nested re-entry only; sequential passes are served
by the cache. MinecraftFramebufferHelper resolves the current framebuffer
dynamically so Iris composites land in the portal FBO that BetterPortals
swapped in, giving both worlds their own shader effects.
…er under shaders

With a shader pack active the starfield overlay of BetterPortals (a
world-less dummy TileEntityEndPortal drawn through the vanilla TESR)
could never execute its legacy path: an Iris gbuffers program owns the
entity-phase draw and glsm defers to the bound program, so the texgen
fixed-function emulation is bypassed and the Iris program misreads the
POSITION_COLOR vertex stream. The overlay ended up nearly invisible and
flickered while rotating the view.

World-less render calls now go through the shader-compatible replacement
renderer when a shader pack is in use (no-shader behavior unchanged).
The BetterPortals fade hook still fires from its shouldRenderFace
override during face evaluation: it is recognized by the tracked
CONSTANT_ALPHA blend factor pair, the constant alpha is read back from
the real GL state (the hook writes it through a raw glBlendColor that
bypasses glsm tracking), and the opacity is folded into the first sky
layer's vertex alpha, the exact SRC_ALPHA equivalent of the legacy
CONSTANT_ALPHA blend. Additive layers keep full strength and
precomposition is skipped for fading overlays since a single composited
quad cannot fade the sky layer alone. The pre-hook blend state is
restored after the call so the hook cannot leak into later rendering.
The per-dimension pipeline cache kept every visited dimension's pipeline
(including its full-resolution render targets) alive until the next
shaderpack reload or world unload, so portal mods and dimension travel
accumulated GPU memory without bound.

Pipelines now carry a last-used timestamp; preparePipeline runs a
throttled scan (once per second) and destroys any non-active dimension
pipeline that has been idle for more than 30 seconds, using the same
teardown sequence as destroyPipeline(). The terrain chunk program cache
lazily drops programs whose pipeline is no longer cached, so their GL
objects do not leak either. The currently rendered dimensions refresh
their timestamp every pass and are never evicted; recreating a pipeline
after eviction is the same one-time cost as the first creation.
@DHJComical DHJComical changed the title Fix BetterPortals Refitted end portal rendering (no-shader and shader paths) fix(compat): restore BetterPortals Refitted end portal rendering Sep 2, 2026
@DHJComical
DHJComical merged commit 0ed51a2 into main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant