Skip to content

fix(glsm): add double-angle glRotatef redirect target used by NTM-CE - #65

Merged
DHJComical merged 29 commits into
mainfrom
fix/issue64-ntm-glsm
Sep 2, 2026
Merged

fix(glsm): add double-angle glRotatef redirect target used by NTM-CE#65
DHJComical merged 29 commits into
mainfrom
fix/issue64-ntm-glsm

Conversation

@DHJComical

@DHJComical DHJComical commented Aug 17, 2026

Copy link
Copy Markdown
Owner

问题 / Problem

Fixes #64 — placing HBM's Nuclear Tech (Community Edition) machines crashes the client:

java.lang.NoSuchMethodError: 'void com.gtnewhorizons.angelica.glsm.GLStateManager.glRotatef(double, float, float, float)'
	at com.hbm.render.tileentity.RenderIndustrialTurbine$1.renderCommon(RenderIndustrialTurbine.java:80)

根因 / Root Cause

Confirmed by decompiling NTM-CE-1.12.2-2.5.0.5.jar:

  • NTM-CE's RenderIndustrialTurbine$1.renderCommon invokes net.minecraft.client.renderer.GlStateManager.rotate:(DFFF)V — the double-angle rotate overload (rotate(double, float, float, float)) used by its item renderers.
  • The GLSM redirector rewrites vanilla GlStateManager calls to GLStateManager by method name while preserving the caller's descriptor (rotate / func_179114_b / func_187444_aglRotatef), so the (DFFF)V rotate call became GLStateManager.glRotatef(DFFF)V.
  • GLStateManager already had double-parameter glScalef(double,double,double) and glTranslatef(double,double,double) compat overloads, but no glRotatef(double,float,float,float) — the link failed with NoSuchMethodError.

修复 / Fix

Add GLStateManager.glRotatef(double angle, float x, float y, float z) next to the existing double-parameter compat overloads; it delegates to the float form. No behavior change for the float path.

验证 / Verification

  • GLSMRedirectorTest: a synthetic GlStateManager.rotate(DFFF)V call is rewritten to GLStateManager.glRotatef with the (DFFF)V descriptor preserved.
  • GLStateManagerRedirectContractTest: asserts both (FFFF)V and (DFFF)V glRotatef forms exist on the compiled class (same ASM-based pattern as the issue [Bug] HammerLib fails to initialize shader programs #40 glGetActiveUniform contract test, since GLStateManager's static initializer needs a live GL context).
  • Full suite: 394 tests, 0 failures (baseline 391 + 3 new).

Note on the "depth buffer breaks / HUD flashing" symptoms reported right before the crash: those were the aftermath of the aborted NTM item render (state pushed and never restored because the call threw mid-render). With the method present this path completes normally; if rendering-state corruption still shows up standalone, it would be a separate state-tracking issue worth a fresh report.

Closes #64

后续修复 / Follow-up Fixes

The original double-angle glRotatef link fix exposed additional rendering-state and compatibility issues in the same HBM-CE/iTRP scenario. The final branch includes the following follow-up work that was not covered by the original description.

Compatibility and loading

  • 58d4246: restore the legacy DefaultChunkRenderer.fillCommandBuffer compatibility seam required by HBM-CE's Celeritas mixin targets.
  • 74a920d: add the HBM-CE and CTM development dependencies used for compatibility validation.
  • fcdde1e: restore the legacy OcclusionNode API and its OcclusionCuller compatibility targets required by HBM-CE. This prevents critical mixin failures from poisoning the renderer before the world loads.

Rendering correctness

  • 034fee4: snapshot section light data for off-thread chunk builds.
  • 1393dd5: guard tile-entity batches against leaked HBM depth, blend, and texture state before batch drawing.
  • 1a07f32: preserve the camera getfield layout required by the Celeritas renderer compatibility path.
  • ef5adc8: derive fixed-function shader vertex flags from the attributes actually enabled on the bound VAO, preventing raw HBM VAO draws from selecting shader variants with missing attributes.
  • 92d3664: route HBM RenderUtil attribute scopes through GLSM, synchronize the machine world lightmap, and add conditional HBM compatibility mixins and regression coverage.
  • b7e344f: restore shader texture state across HBM raw GL rendering.
  • ed070e2: collect block entities at the correct point for shadow rendering so HBM machines participate in the shadow pass.
  • 007f377: fix the general foreign-render path. Iris and external renderers share GLSM state, so returning from a foreign draw now re-synchronizes the active framebuffer, viewport, program, and deferred blend/depth/color state. This is a general renderer fix; HBM is the primary reproducer, not a special-case workaround.

Shadow pipeline synchronization

The branch also contains the Celeritas shadow-occlusion synchronization used by the updated terrain path: fast frustum visibility handling, receiver-driven shadow occlusion wiring, and activation of the shadow occlusion culler (099a74f, d8dde88, 106b7e3, f375bab). The associated upstream-sync planning and completion notes are recorded in 8cb2251 and 92c55ff.

The temporary iTRP/HBM diagnostic collector used during investigation was removed before the final commit. The functional HBM compatibility layer remains because it adapts HBM's private attribute-stack semantics and lightmap requirements.

Final Verification / 最终验证

  • IDEA MCP full rebuild: passed.
  • gradlew.bat -g D:/gradle build --no-daemon: BUILD SUCCESSFUL.
  • check, compat-bridge Jar verification, and remap Jar verification: passed.
  • git diff --check and UTF-8 no-BOM checks: passed.
  • Dev-client manual regression with multiple HBM machines and iTRP: rendering returned to normal; the previously observed solid-color/partial-white models, hand-held item contamination, missing/incorrect machine shadow interaction, depth loss, and distance/chunk-dependent behavior no longer reproduced.
  • runClient was not run during the final cleanup/build pass; the manual regression was completed earlier with the debug instrumentation before cleanup.

The final cleanup and verification commit is 007f377 (fix(glsm): resync shader state after foreign draws).

Additional branch maintenance / 其他分支维护

  • 8dea113: fall back to bufferData when mapBufferRange is unavailable or fails.
  • 16eaec9, 90d7aa1, b542092, 73042f7: add, revert, and document the deferred JMH benchmark harness after the Windows Mesa EGL limitation was confirmed; ignore the local Mesa DLL directory used for the investigation.
  • 25d151e: increase CI build memory for the expanded build and remap workload.
  • Merge commits 05f581c, 489b882, and 39a1b08 bring the then-current main branch and completed Celeritas upstream synchronization into this PR.

Creative inventory item render-state isolation / 创意物品栏物品渲染状态隔离

  • 9f425eb: wrap RenderItem's built-in renderer path with a GLSM glPushAttrib(GL_ALL_ATTRIB_BITS) boundary and foreign-draw synchronization.
  • HBM's WrappedTEISRModel invokes a built-in item renderer that can modify lighting, culling, blending, texture, color, shade, and lightmap state. The previous GUI-level restoration was too coarse: after scrolling past the Death Detonator, later slots could render solid white with alpha lost.
  • The boundary is applied per built-in item renderer and restores the tracked OpenGL state before Iris resumes synchronization. Regular baked-model items bypass the boundary.
  • Manual verification passed with the HBM nuclear creative-inventory page: scrolling past the affected entries no longer contaminates subsequent item slots or transparent textures.

HBM's Nuclear Tech (Community Edition) calls net.minecraft.client.renderer.GlStateManager.rotate(double, float, float, float) from its tile entity item renderers. The GLSM redirector rewrites vanilla GlStateManager calls to GLStateManager by method name while preserving the caller's descriptor, so the (DFFF)V rotate call became GLStateManager.glRotatef(DFFF)V — which did not exist, crashing with NoSuchMethodError when rendering the Industrial Turbine item (issue #64).

Add the missing glRotatef(double, float, float, float) overload next to the existing double-parameter glScalef/glTranslatef compat overloads; it delegates to the float form. Also extend the redirector tests: GLSMRedirectorTest now asserts a synthetic GlStateManager.rotate(DFFF) call is rewritten to GLStateManager.glRotatef with the descriptor preserved, and GLStateManagerRedirectContractTest asserts both the (FFFF)V and (DFFF)V glRotatef forms exist on the compiled class (394 tests, 0 failures).
HBM-CE's MixinDefaultChunkRenderer redirects reads inside fillCommandBuffer, a
method that existed in upstream Celeritas but was dropped in Actinium's
rewritten renderer. The mixin then aborts with a Critical injection error,
poisoning DefaultChunkRenderer and breaking world loading (issue #47): a
NoClassDefFoundError on VintageRenderSectionManager\ surfaces in
Minecraft.loadWorld and the client player never spawns, cascading into the
player-null NPE storm.

Restore a fillCommandBuffer(CameraTransform) compatibility seam (never invoked
by the render path) so the redirects find their getfield targets and apply
cleanly, preserving the unsafe camera access they also apply to
setModelMatrixUniforms. MixinOcclusionCuller still targets the old Celeritas
OcclusionCuller API that Actinium does not provide, so dev runs enable
-Dmixin.env.ignoreConstraints to downgrade that Critical failure to a skip
instead of poisoning the class.
Declare curse.maven coordinates for HBM's Nuclear Tech Mod Community Edition
(1312314:8330665) and its CTM dependency (267602:2915363) so the dev client
reproduces the HBM celeritas-mixin environment for issue #64 / #47 work.
HBM-CE's MixinOcclusionCuller wraps the pre-lattice upstream culling API:
isWithinFrustum(Viewport, OcclusionNode) call sites in isSectionVisible and
tryVisitNode, plus isWithinRenderDistance(CameraTransform, OcclusionNode,
float) in isSectionVisible. Actinium's lattice-based rewrite dropped the
object-node API, so the critical injections failed, poisoned OcclusionCuller,
and aborted the world-join task with NoClassDefFoundError before mc.player
was created — surfacing as the issue #47 player-null NPE storm.

Reintroduce a minimal OcclusionNode data holder and the four legacy members
as dead injection targets, mirroring upstream b07278d0. Verified in the dev
client: all HBM celeritas mixins now apply cleanly and the world loads.
Chunk-builder workers read the live light NibbleArrays while the light engine keeps mutating them on the main thread, so a torn read bakes transient darkness into the mesh (water and fluidlogged quads turning black after a relight burst). Copy the block/sky light arrays eagerly while the section is being cloned on the main thread.
HBM-CE machine TESRs leak depth/blend/texture state during the render loop. The FastTESR batch flush relies on ambient GL state, so with machines in view the leaked state turned HBM water reeds into opaque black crosses that also punched holes in the water surface behind them. Wrap the block-entity region in a GLSM attrib push/pop and restore the clean entry state right before drawBatch, in both the main path and the mod-injected setTileEntities path.
…rom upstream

Sync upstream commits 9b4256d0, daaefb72, 7c7c8228, d96b07f3 and the
FFC/snapshot portion of 8fdbdb49 (0a3624bc..d96b07f3 + 8fdbdb49).

- OcclusionCuller: explicit allowFrustumClamping parameter (7-arg
  findVisible), branchless integer angle refinement, aperture-based
  frustum clamping via FastFrustumClamping, multi-root search disables
  clamping. Public/package-visible signatures (isWithinFrustum,
  isWithinRenderDistance, tryVisitNode, Visitor) preserved.
- FastFrustumClamping: new SWAR-based packed aperture culler (upstream).
- RegionCullCache: reset-based classification with UNCOMPUTED and
  PARTIAL_DISTANCE_IN/PARTIAL_FRUSTUM_IN sub-classifications plus cached().
- SectionLattice: visibility snapshot reduced from long[] to int[]
  visibleFrames with SnapshotBuffers; findVisible gains the clamping flag.
- RenderListManager/RenderSectionManager: thread the clamping flag through
  (enabled for the main pass, disabled for the orthographic shadow pass).

Not included: b8c1079a shadow-occlusion refactor (deferred to the next
phase), HBM-CE OcclusionNode seam (lives on fix/issue64-ntm-glsm, not in
this branch).
Sync the algorithm/data portion of upstream b8c1079a (receiver-driven
shadow occlusion culling).

- ShadowOcclusionCuller: new receiver-driven shadow search rooted at the
  main pass's visible sections, traversing toward the light (upstream).
- ShadowSearchFrustum: new optional capability interface for shadow
  frustums that support the receiver-driven search (upstream).
- SectionLattice: constructor gains hasShadowPass; adds shadowVisitState,
  shadowCuller, visibleCells/visibleCount; splits snapshot buffers into
  main/shadow pairs; findVisible records visible cells when a shadow pass
  exists; adds findShadowVisible.
- OcclusionCuller: findVisible gains visitState and recordVisible
  parameters; static members made package-visible for ShadowOcclusionCuller;
  records visible cells during the search.
- Viewport: adds getFrustum().

RenderListManager/SectionGraph wiring is deferred to the next phase; the
tree is intentionally not fully buildable until then.
Finish the common-side sync of upstream b8c1079a by adopting SectionGraph
and the frame-ordered shadow search in the renderer layer.

- SectionGraph: new shared lattice + single search thread + deferred
  lattice updates (upstream).
- RenderListManager: constructor takes (SectionGraph, shadow,
  AsyncOcclusionMode, SectionTicker); attach/detach/updateSectionMetadata
  and the search thread move into SectionGraph; adds startShadowGraphUpdate
  and the shadow flag; startGraphUpdate rejects the shadow manager.
- RenderSectionManager: both list managers share one SectionGraph; update()
  and updateForShadowPass() split by frame order (didShadowPassRunThisFrame);
  attach/detach/metadata fan out through the graph; the local
  getCurrentRenderListManager() seam is kept for the render/rebuild paths,
  while terrain search uses the terrain manager and shadow search uses
  updateForShadowPass.
- SimpleWorldRenderer: setupTerrain/setupShadowTerrain/prepareFrame split,
  with the local shadow render-distance switch retained in
  setupShadowTerrain; chunk events apply while the graph is quiescent.
- VintageRenderSectionManager/ActiniumWorldRenderer: drop the now-redundant
  finishAllGraphUpdates() in the shadow render path (setupShadowTerrain
  already joins).

Build and all 423 tests pass.
Wire the common-side shadow search (b8c1079a) into the Iris shader module.

- WorldRendererCompat: add setupShadowTerrain and getLastViewport so the
  shadow pass can run the frame-ordered shadow search with the player
  viewport captured by the preceding terrain pass.
- ShadowRenderer: call setupShadowTerrain(playerViewport, shadowViewport)
  instead of setupTerrain in the celeritas integration block; pass
  resolved near/far planes and the interval size to the advanced shadow
  frustum (legacy perspective packs skip depth planes); update culling
  info strings.
- AdvancedShadowCullingFrustum: implement ShadowSearchFrustum and add
  depth planes (toward/away) using SHADOW_CAMERA_OFFSET.
- SafeZoneCullingFrustum: override supportsOcclusionSearch() to false
  (safe-zone semantics are incompatible with the receiver-driven search).
- ShadowMatrices: extract SHADOW_CAMERA_OFFSET constant.
- ActiniumWorldRenderer: implement setupShadowTerrain/getLastViewport.

Shadow occlusion now runs end-to-end: the shadow search consumes the
terrain pass's visible-cell root set and expands toward the light. Build
and all 423 tests pass; runtime shadow verification still pending.
Sync the upstream f3c5642e benchmark harness and adapt it to Actinium's
LWJGL 3.4.1 (the version Cleanroom and the mod's dependency script use).

- celeritas-common/src/jmh: voxel/occlusion/multidraw benchmark sources
  from upstream, including the GL-backed OcclusionCullerBench and
  MultiDrawBench with their @benchmark methods. SectionLattice/findVisible
  calls updated for the post-b8 3-arg constructor and 7-arg findVisible.
- build.gradle: jmh source set, jmhImplementation (jmh-core + annotation
  processor) and LWJGL 3.4.1 (lwjgl/lwjgl-opengl/lwjgl-egl) with the same
  native-classifier logic as GTNHLib; lwjgl-egl ships no natives classifier,
  matching upstream.
- gradle.properties: jmh_version.

HeadlessGl opens a system EGL context (mesa), so the GL benchmarks need
EGL available at runtime (not bundled); pure-CPU benchmarks compile and
run regardless. Build and all 423 tests pass.
StreamingUploader's MAP_BUFFER_RANGE path fed the glMapBufferRange result
straight into MemoryUtilities.memAddress0 without a null check. When the
driver fails the mapping (returns NULL), Unsafe.getLong(null, 0x10) kills
the JVM with EXCEPTION_ACCESS_VIOLATION reading address 0x10 (hs_err on
the splash render thread during startup; MAP_BUFFER_RANGE is the default
streaming upload strategy).

Restore upstream Angelica's guarded form: RenderBackend gains
mapBufferRangeAddress (returns 0L on failed mapping) and the uploader
falls back to bufferData with a one-time WARN. The fallback is per-call,
so transient mapping failures recover automatically on the next upload.
# Conflicts:
#	gradle/scripts/dependencies.gradle
#	src/test/java/com/gtnewhorizons/angelica/glsm/redirect/GLSMRedirectorTest.java
#	src/test/java/com/gtnewhorizons/angelica/glsm/redirect/GLStateManagerRedirectContractTest.java
…update

HBM-CE MixinRenderSectionManager (hbm.mod.mixin.json) registers @reDIrect
injections on the update(Viewport, int, boolean) method body that rewrite
the CameraTransform x/y/z getfields to its unsafe accessors. The b8c1079a
shadow occlusion sync delegated that camera bookkeeping to the new private
updateCameraPosition helper, leaving the redirector with zero scanned
targets; the mixin application aborts with InjectionError, poisons the
whole RenderSectionManager class and crashes mod construction with
NoClassDefFoundError (observed as an Actinium onConstruct crash).

Inline the camera reads back into update (updateCameraPosition stays for
the shadow pass, which HBM-CE does not redirect) and lock the binding
contract with HbmCameraRedirectContractTest, which asserts the three
GETFIELD instructions against the compiled class bytes (same pattern as
GLStateManagerRedirectContractTest).
HBM-CE uploads OBJ models into VAOs while its glEnableClientState calls
(redirected to GLSM) leak bits such as COLOR_BIT into ShaderManager's
globally tracked currentVertexFlags without a matching VAO attribute.
Raw GL draws (glDrawElements and friends, used by HBM's VAO path and by
ShaderManager.preDraw()) then pick a shader variant that declares an
attribute the VAO does not provide; the shader reads the default
(0,0,0,1) and the model renders black. Because the leak is global and
never restored, every machine rendered after the first model upload is
affected and the pollution also breaks GUI item icons that share the FFP
pipeline, independent of the fast-lit item / display-list / multidraw
options.

Derive the vertex-flag mask from VertexAttribState's per-VAO attribute
enablement instead: ShaderManager.preDraw() and onBindVertexArray() for
VAOs unknown to the vanilla VertexFormat setup path now use
VertexAttribState.deriveVertexFlags(), which always matches the real GL
state. Locked down by VertexAttribStateDeriveVertexFlagsTest.
Route HBM render-state scopes through the GLSM state stack and synchronize world lightmap coordinates before tile-entity rendering. Derive fixed-function vertex flags from the bound VAO so raw HBM models do not inherit stale attributes. Add conditional mixins, regression tests, and compatibility documentation.

Test: ./gradlew build --no-daemon
Raise the Gradle heap and direct-memory limits to 8G and cap worker concurrency at two so GitHub Actions builds do not exhaust runner memory during compilation and remapping.

Test: ./gradlew compileJava --no-daemon
Collect tile entities after the shadow render lists are updated instead of during the later terrain setup.
@DHJComical DHJComical linked an issue Sep 2, 2026 that may be closed by this pull request
Conflict resolution highlights:
- VertexAttribState: unify the parallel per-VAO FFP vertex-flag derivations
  (deriveVertexFlags from this branch, currentClientArrayVertexFlags from main's
  BPR fix) into the single currentClientArrayVertexFlags, keeping this branch's
  null guard; ShaderManager call sites and the branch's test updated to the
  unified name.
- VertexAttribStateTest: use a direct buffer for the client pointer, as
  VertexAttribState.set now captures the native address.
- docs/compatibility-matrix.md, dependencies.gradle, mixin condition/test
  lists: union of both sides.
@DHJComical
DHJComical merged commit 2f54db7 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.

[Bug] Crashes with HBM [Bug] NoSuchMethodError: GLStateManager.glRotatef with HBM's Nuclear Tech (Community Edition) on Java 25

1 participant