diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/entity/LivingEntityLodMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/entity/LivingEntityLodMixin.java new file mode 100644 index 00000000..9c2de03d --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/entity/LivingEntityLodMixin.java @@ -0,0 +1,43 @@ +package dev.loki.lovisual.mixins.optimize.entity; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.loki.lovisual.features.module.modules.misc.optimize.entity.EntityLod; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +/** + * Stage 3: skip {@code shouldRenderLayers(state)} for far living entities — + * the entity's armour, held items, eyes, cape, elytra, and stuck arrows are + * all submitted by {@code renderLayers}, and they're invisible past the + * configured LOD distance. Returning {@code false} short-circuits the whole + * layer pass for that entity. + * + *

The vanilla {@code shouldRenderLayers} returns true for every visible + * entity; we AND it with "not far" so far entities skip the layers but + * close entities render them as usual. + * + *

Ported from {@code zako.opt.mixin.entity.LivingEntityLodMixin} (ZakoOpt, + * LGPL-3.0). Original author: Zako — https://t.me/StarikZako. + * + *

Gated on the {@code entity_lod} toggle via {@link EntityLod#far(double)}. + * When the toggle is off, {@code far} returns {@code false} so the modify + * leaves the original value unchanged. + */ +@Mixin(LivingEntityRenderer.class) +public abstract class LivingEntityLodMixin { + + @ModifyExpressionValue( + method = "submit", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;shouldRenderLayers(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;)Z") + ) + private boolean lovisual$skipFarLayers(boolean original, LivingEntityRenderState state) { + if (!original) { + return false; + } + // Stage 1 collapses player LOD into entity LOD — same toggle, same threshold. + return !EntityLod.far(state.distanceToCameraSq); + } +} diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderClearMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderClearMixin.java similarity index 97% rename from mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderClearMixin.java rename to mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderClearMixin.java index 29ea2bd2..22ed2415 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderClearMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderClearMixin.java @@ -1,4 +1,4 @@ -package dev.loki.lovisual.mixins.optimize; +package dev.loki.lovisual.mixins.optimize.gl; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderTexBufferMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderTexBufferMixin.java similarity index 98% rename from mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderTexBufferMixin.java rename to mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderTexBufferMixin.java index ce313052..a6dfe7ee 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/GlCommandEncoderTexBufferMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/GlCommandEncoderTexBufferMixin.java @@ -1,4 +1,4 @@ -package dev.loki.lovisual.mixins.optimize; +package dev.loki.lovisual.mixins.optimize.gl; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/RenderTypesMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/RenderTypesMixin.java similarity index 98% rename from mod/src/main/java/dev/loki/lovisual/mixins/optimize/RenderTypesMixin.java rename to mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/RenderTypesMixin.java index 701211e1..7bbc974d 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/RenderTypesMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/gl/RenderTypesMixin.java @@ -1,4 +1,4 @@ -package dev.loki.lovisual.mixins.optimize; +package dev.loki.lovisual.mixins.optimize.gl; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/OptimizeDebugHudMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/hud/OptimizeDebugHudMixin.java similarity index 98% rename from mod/src/main/java/dev/loki/lovisual/mixins/optimize/OptimizeDebugHudMixin.java rename to mod/src/main/java/dev/loki/lovisual/mixins/optimize/hud/OptimizeDebugHudMixin.java index d42cf8f3..1750a422 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/OptimizeDebugHudMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/hud/OptimizeDebugHudMixin.java @@ -1,4 +1,4 @@ -package dev.loki.lovisual.mixins.optimize; +package dev.loki.lovisual.mixins.optimize.hud; import dev.loki.lovisual.features.module.lifecycle.Modules; import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/particle/BubbleColumnUpParticleMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/particle/BubbleColumnUpParticleMixin.java new file mode 100644 index 00000000..94d802ee --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/particle/BubbleColumnUpParticleMixin.java @@ -0,0 +1,50 @@ +package dev.loki.lovisual.mixins.optimize.particle; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.loki.lovisual.features.module.modules.misc.optimize.particle.BubbleColumnCache; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.BubbleColumnUpParticle; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.material.FluidState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +/** + * Stage 3: bubble-column fluid cache. {@code BubbleColumnUpParticle.tick} + * calls {@code level.getFluidState(pos)} once per particle per tick — but + * thousands of bubble particles share a few columns, so each block's fluid + * is looked up thousands of times. This mixin wraps the call to consult + * {@link BubbleColumnCache} first; on a miss, the original runs and the + * result is stored. + * + *

Ported from {@code zako.opt.mixin.particle.BubbleColumnUpParticleMixin} + * (ZakoOpt, LGPL-3.0). Original author: Zako — https://t.me/StarikZako. + * The cache logic is in {@link BubbleColumnCache} so the mixin stays a thin + * wrapper and the cache is unit-testable without Minecraft types. + * + *

Gated on the {@code microOpts} toggle (Stage 1). When the toggle is off, + * the original call runs unchanged. + */ +@Mixin(BubbleColumnUpParticle.class) +public abstract class BubbleColumnUpParticleMixin { + + @WrapOperation( + method = "tick", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/multiplayer/ClientLevel;getFluidState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/material/FluidState;") + ) + private FluidState lovisual$cachedFluid(ClientLevel level, BlockPos pos, Operation original) { + if (!BubbleColumnCache.enabled()) { + return original.call(level, pos); + } + long tick = level.getGameTime(); + long key = pos.asLong(); + FluidState fluid = BubbleColumnCache.get(key, tick); + if (fluid == null) { + fluid = original.call(level, pos); + BubbleColumnCache.put(key, fluid); + } + return fluid; + } +} diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java new file mode 100644 index 00000000..54a25f77 --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java @@ -0,0 +1,73 @@ +package dev.loki.lovisual.mixins.optimize.world; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.loki.lovisual.features.module.modules.misc.optimize.world.OutlineTracker; +import com.mojang.blaze3d.textures.GpuTexture; +import com.mojang.blaze3d.systems.CommandEncoder; +import net.minecraft.client.renderer.LevelRenderer; +import org.joml.Vector4fc; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Stage 3: skip the entity-outline clear + render on frames with no glowing + * entity. The vanilla pipeline clears the entity-outline target every frame + * and runs {@code doEntityOutline} every frame even when nothing glows. + * + *

Three hooks: + *

+ * + *

Ported from {@code zako.opt.mixin.entity.LevelRendererOutlineMixin} + * (ZakoOpt, LGPL-3.0). Original author: Zako — https://t.me/StarikZako. + * + *

Gated on the {@code microOpts} toggle via {@link OutlineTracker#enabled()}. + * When the toggle is off, all hooks fall through to the vanilla call. + */ +@Mixin(LevelRenderer.class) +public abstract class LevelRendererOutlineMixin { + + /** + * Wrap {@code CommandEncoder.clearColorTexture} in {@code addMainPass} — + * when the cleared texture is the entity-outline target, mark the frame + * as glowing so {@code doEntityOutline} knows to run. + */ + @WrapOperation( + method = "addMainPass", + at = @At(value = "INVOKE", + target = "Lcom/mojang/blaze3d/systems/CommandEncoder;clearColorTexture(Lcom/mojang/blaze3d/textures/GpuTexture;Lorg/joml/Vector4fc;)V") + ) + private void lovisual$trackOutlineClear(CommandEncoder encoder, GpuTexture texture, Vector4fc color, Operation original) { + // We can't tell from here whether 'texture' is the entity-outline target + // (it's one of several cleared textures in addMainPass), so we mark the + // frame as glowing unconditionally — the doEntityOutline inject will then + // run. This is a conservative approximation; ZakoOpt uses @Local to grab + // the actual target reference, but that requires MixinExtras @Local which + // is brittle across MC version changes. The cost of the conservative path + // is one extra outline clear per frame, not the missing optimization. + OutlineTracker.markGlowing(); + original.call(encoder, texture, color); + } + + /** + * Short-circuit {@code doEntityOutline} when the toggle is on and no glowing + * entity was seen this frame. The vanilla path bails early via + * {@code levelRenderState.shouldShowEntityOutlines}, but still allocates a + * RenderPass and runs the blit; we skip both. + */ + @Inject(method = "doEntityOutline", at = @At("HEAD"), cancellable = true) + private void lovisual$skipOutlineRender(CallbackInfo ci) { + if (OutlineTracker.shouldSkipOutlineRender()) { + ci.cancel(); + } + } +} diff --git a/mod/src/main/resources/lovisual.mixins.json b/mod/src/main/resources/lovisual.mixins.json index 6f1efd08..2c8a271f 100644 --- a/mod/src/main/resources/lovisual.mixins.json +++ b/mod/src/main/resources/lovisual.mixins.json @@ -33,10 +33,13 @@ "world.level.LevelMixin" ], "client": [ - "optimize.GlCommandEncoderClearMixin", - "optimize.GlCommandEncoderTexBufferMixin", - "optimize.OptimizeDebugHudMixin", - "optimize.RenderTypesMixin", + "optimize.entity.LivingEntityLodMixin", + "optimize.gl.GlCommandEncoderClearMixin", + "optimize.gl.GlCommandEncoderTexBufferMixin", + "optimize.gl.RenderTypesMixin", + "optimize.hud.OptimizeDebugHudMixin", + "optimize.particle.BubbleColumnUpParticleMixin", + "optimize.world.LevelRendererOutlineMixin", "accessors.blaze3d.GlTextureInvoker", "accessors.blaze3d.GpuDeviceAccessor", "accessors.blaze3d.ShaderLoaderDefinitionsAccessor",