Skip to content

Commit

Permalink
Switch to using Mixins to implement all calls to ClientHooks. getRend…
Browse files Browse the repository at this point in the history
…erFluidState
  • Loading branch information
Cadiboo committed Apr 8, 2024
1 parent a182fc3 commit bb71aae
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 229 deletions.
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ dependencies {
// implementation fg.deobf("curse.maven:Embeddium-908741:4984832")
// implementation fg.deobf("curse.maven:Oculus-581495:4767500")

modImplementation 'maven.modrinth:sodium:mc1.20.1-0.5.7'
// CompileOnly so our Mixins can target Sodium's classes
def sodium = modCompileOnly 'maven.modrinth:sodium:mc1.20.1-0.5.7'
modImplementation sodium
modImplementation 'maven.modrinth:iris:1.6.17+1.20.1'
// Fabric API appears to be required by Sodium
modImplementation "net.fabricmc.fabric-api:fabric-api:0.92.0+1.20.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ public static void preIterationSodium(
// @OnlyIn(Dist.CLIENT)
@Environment(EnvType.CLIENT)
public static FluidState getRenderFluidState(BlockPos pos, BlockState state) {
SelfCheck.getRenderFluidState = true;
if (NoCubesConfig.Server.extendFluidsRange > 0)
return ClientUtil.getExtendedFluidState(pos);
return state.getFluidState();
Expand Down
211 changes: 4 additions & 207 deletions src/main/java/io/github/cadiboo/nocubes/hooks/MixinAsm.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public final class MixinAsm {
/**
* Hooks multiple parts of the chunk rendering method to allow us to do our own custom rendering
* - Injects our {@link io.github.cadiboo.nocubes.hooks.ClientHooks#preIteration} hook
* - Injects our {@link io.github.cadiboo.nocubes.hooks.ClientHooks#getRenderFluidState} hook
*/
public static void transformChunkRenderer(ClassNode classNode, RemapperChain mapper) {
var instructions = findMethodNode(classNode, MappedMember.from(
Expand Down Expand Up @@ -108,12 +107,10 @@ public static void transformChunkRenderer(ClassNode classNode, RemapperChain map
print("Done injecting the preIteration hook");
}

redirectBlockStateGetFluidStateSoExtendedFluidsWork(
mapper,
instructions,
// blockPos local variable index
isOptiFinePresent ? (ofg8 ? 19 : 17) : 16
);
// https://github.com/SpongePowered/Mixin/blob/41a68854f6e63e8ec6d38e7d7612230d7f73a9bc/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java#L546
// https://github.com/IrisShaders/Iris/blob/eb54b796e63b1adafbd5a36703cb04061d17a3ea/src/main/java/net/irisshaders/iris/mixin/vertices/block_rendering/MixinChunkRebuildTask.java#L57
// https://github.com/FabricMC/fabric/blob/1.20.1/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java
// https://duckduckgo.com/?q=fabric-gametest-api-v1&t=osx&ia=web
}

static boolean detectOptiFine(InsnList instructions) {
Expand All @@ -131,145 +128,9 @@ static boolean detectOptiFine(InsnList instructions) {
print("Did not detect OptiFine");
return false;
}

/**
* Redirects 'state.getFluidState()' to our own code, so we can have extended fluids render properly
* Specifically: changes 'state.getFluidState()' to 'Hooks.getRenderFluidState(pos, state)'
*/
static void redirectBlockStateGetFluidStateSoExtendedFluidsWork(RemapperChain mapper, InsnList instructions, int blockPosLocalVarIndex) {
var getFluidStateMethod = MappedMember.from(
mapper,
"getFluidState",
"net/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase",
"m_60819_",
"()Lnet/minecraft/world/level/material/FluidState;",
"net/minecraft/class_4970$class_4971",
"method_26227",
"()Lnet/minecraft/class_3610;"
);
String a = "net/minecraft/world/level/block/state/BlockState";
String b = "net/minecraft/class_2680";
var getFluidStateCallOwner = MappedMember.mapType(mapper, a, b);
var getFluidStateCall = findFirstMethodCall(
instructions,
Opcodes.INVOKEVIRTUAL,
getFluidStateCallOwner,
getFluidStateMethod.name,
getFluidStateMethod.desc,
0 // startIndex
);

var previousLabel = findFirstLabelBefore(instructions, getFluidStateCall);

// Change
// LABEL
// <Somehow put BlockState onto the stack>
// INVOKE BlockState.getFluidState
// to
// LABEL
// LOAD blockPos
// <Somehow put BlockState onto the stack>
// INVOKE Hooks.getFluidState
instructions.insert(previousLabel, new VarInsnNode(Opcodes.ALOAD, blockPosLocalVarIndex));
instructions.insert(getFluidStateCall, callNoCubesClientHook(
mapper,
"getRenderFluidState",
"(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/world/level/material/FluidState;",
"(Lnet/minecraft/class_2338;Lnet/minecraft/class_2680;)Lnet/minecraft/class_3610;"
));
instructions.remove(getFluidStateCall);

print("Done injecting the fluid state getter redirect");
}
// endregion

// region Fluid rendering

/**
* Changes fluid rendering to support extended fluid rendering
* - Injects our {@link io.github.cadiboo.nocubes.hooks.ClientHooks#getRenderFluidState} hook
*/
public static void transformFluidRenderer(ClassNode classNode, RemapperChain mapper) {
var instructions = findMethodNode(classNode, MappedMember.from(
mapper,
"tesselate",
"net/minecraft/client/renderer/block/LiquidBlockRenderer",
"m_234369_",
"(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/core/BlockPos;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/material/FluidState;)V",
"net/minecraft/class_775",
"method_3347",
"(Lnet/minecraft/class_1920;Lnet/minecraft/class_2338;Lnet/minecraft/class_4588;Lnet/minecraft/class_2680;Lnet/minecraft/class_3610;)V"
)).instructions;
// Redirect every 'blockState.getFluidState()' call preceded by a 'world.getBlockState(pos)' to our 'getRenderFluidState' hook
// This could be converted to a Mixin but
// - Each offset block pos would need to be recreated (currently using DUP_X1 to avoid this) making it less efficient that this ASM
// - Targeting each different 'blockState.getFluidState()' call might be hard
// Warning - clever/complex code:
// - Uses DUP_X1 to copy the 'pos' parameter from the 'world.getBlockState(pos)' call onto the stack (below the 'world' param to not interfere with the call)
// - Uses DUP to copy the 'state' returned from the 'world.getBlockState(pos)' call onto the stack
// - Removes the existing 'blockState.getFluidState()' call
// - Calls our 'getRenderFluidState' with the 'pos' and 'state', removing them from the stack
// Repeats this for all 6 invocations at the start of the method
var lastIndex = 0;
for (var direction = 0; direction < 6; ++direction) {
var getBlockStateMethod = MappedMember.from(
mapper,
"getBlockState",
"net/minecraft/world/level/BlockGetter",
"m_8055_",
"(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;",
"net/minecraft/class_1922",
"method_8320",
"(Lnet/minecraft/class_2338;)Lnet/minecraft/class_2680;"
);
var getBlockStateCallOwner = MappedMember.mapType(mapper, "net/minecraft/world/level/BlockAndTintGetter", "net/minecraft/class_1920");
var getBlockStateCall = findFirstMethodCall(
instructions,
Opcodes.INVOKEINTERFACE,
getBlockStateCallOwner,
getBlockStateMethod.name,
getBlockStateMethod.desc,
lastIndex + 1
);
// DUP the blockPos parameter and put it lower on the stack than world
instructions.insertBefore(getBlockStateCall, new InsnNode(Opcodes.DUP_X1));
// DUP the returned blockState
instructions.insert(getBlockStateCall, new InsnNode(Opcodes.DUP));
lastIndex = instructions.indexOf(getBlockStateCall);
var getFluidStateMethod = MappedMember.from(
mapper,
"getFluidState",
"net/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase",
"m_60819_",
"()Lnet/minecraft/world/level/material/FluidState;",
"net/minecraft/class_4970$class_4971",
"method_26227",
"()Lnet/minecraft/class_3610;"
);
var getFluidStateCallOwner = MappedMember.mapType(mapper, "net/minecraft/world/level/block/state/BlockState", "net/minecraft/class_2680");
var getFluidStateCall = findFirstMethodCall(
instructions,
Opcodes.INVOKEVIRTUAL,
getFluidStateCallOwner,
getFluidStateMethod.name,
getFluidStateMethod.desc,
lastIndex + 1
);
var previousLabel = findFirstLabelBefore(instructions, getFluidStateCall);
removeBetweenIndicesInclusive(instructions, instructions.indexOf(previousLabel) + 1, instructions.indexOf(getFluidStateCall));
instructions.insert(previousLabel, callNoCubesClientHook(
mapper,
"getRenderFluidState",
"(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/world/level/material/FluidState;",
"(Lnet/minecraft/class_2338;Lnet/minecraft/class_2680;)Lnet/minecraft/class_3610;"
));
// We didn't remove the ASTORE instruction with our 'removeBetweenIndicesInclusive' so the result of our hook call automatically gets stored
}
}
// endregion

// region Sodium compatibility

/**
* Same as {@link MixinAsm#transformChunkRenderer} but for Sodium.
*/
Expand Down Expand Up @@ -322,70 +183,6 @@ public static void transformSodiumChunkRenderer(ClassNode classNode, RemapperCha
"(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lnet/minecraft/class_852;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;IIIIIILnet/minecraft/class_2338$class_2339;Lnet/minecraft/class_2338$class_2339;Ljava/lang/Object;)V"
)
));

redirectBlockStateGetFluidStateSoExtendedFluidsWork(mapper, instructions, blockPosLocalVarIndex);
}

/**
* Same as {@link MixinAsm#transformFluidRenderer} but for Sodium.
*/
public static void transformSodiumFluidRenderer(ClassNode classNode, RemapperChain mapper) {
var instructions = findMethodNode(
classNode,
"fluidHeight",
MappedMember.mapDescriptor(
mapper,
"(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;)F",
"(Lnet/minecraft/class_1920;Lnet/minecraft/class_3611;Lnet/minecraft/class_2338;Lnet/minecraft/class_2350;)F"
)
).instructions;

redirectBlockStateGetFluidStateSoExtendedFluidsWork(
mapper,
instructions,
// blockPosLocalVarIndex
3
);

// Not implemented yet - see comments in transformSodiumWorldRenderer
}

/**
* Same as {@link LevelRendererMixin#noCubes$setBlocksDirty} but for Sodium.
*/
public static void transformSodiumWorldRenderer(ClassNode classNode, RemapperChain mapper) {
// This is low priority and tricky, I'm going to deal with it later
// When implementing, set up a test world
// - Need an empty Mixin to inject ASM into the vanilla LevelRenderer (overwritten by Sodium)
// - Need an empty Mixin to inject ASM into the Sodium WorldRenderer
// It seems as though, once overwritten by Sodium, LevelRenderer is no-longer extending the dirty block area
// Should NoCubes make it be extended again?

// var methodNode = findMethodNode(
// classNode,
// "scheduleRebuildForBlockArea",
// "(IIIIIIZ)V"
// );
// var instructions = methodNode.instructions;
//
// LabelNode firstLabel = null;
// for (var instruction : instructions) {
// if (instruction instanceof LabelNode labelNode) {
// firstLabel = labelNode;
// break;
// }
// }
// assertInstructionFound(firstLabel, "firstLabel", instructions);
//
// var instructionsToInsert = new InsnList();
//
}

/**
* Same as {@link LevelRendererMixin#noCubes$setBlocksDirty} but for Sodium.
*/
public static void transformSodiumLevelRenderer(ClassNode classNode, RemapperChain mapper) {
// Not implemented yet - see comments in transformSodiumWorldRenderer
}
// endregion

Expand Down
2 changes: 0 additions & 2 deletions src/main/java/io/github/cadiboo/nocubes/hooks/SelfCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ public final class SelfCheck {

static boolean preIteration;
static boolean preIterationSodium;
static boolean getRenderFluidState;

public static String[] info() {
return new String[]{
"preIteration hook called: " + preIteration,
"preIterationSodium hook called: " + preIterationSodium,
"getRenderFluidState hook called: " + getRenderFluidState,
};
}

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/io/github/cadiboo/nocubes/mixin/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.cadiboo.nocubes.mixin;

public interface Constants {
/**
* To make extended fluids work, we need to redirect all getFluidState calls.
* <pre>
* var blockState = world.getBlockState(pos);
* var fluidState = blockState.getFluidState();
* </pre>
* <pre>
* var blockState = world.getBlockState(pos);
* var fluidState = ClientHooks.getRenderFluidState(pos, blockState);
* </pre>
*/
String EXTENDED_FLUIDS_BLOCK_POS_REF_NAME = "extendedFluidsBlockPosRef";
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ void transformClass(String mixinClassName, ClassNode classNode, RemapperChain ma
if (!sodiumInstalled) // Iris breaks with a mixin LVT error, no point in transforming a class that won't get used
MixinAsm.transformChunkRenderer(classNode1, mapper1);
});
case "io.github.cadiboo.nocubes.mixin.client.LiquidBlockRendererMixin" -> transformOnce(mixinClassName, classNode, mapper, MixinAsm::transformFluidRenderer);
case "io.github.cadiboo.nocubes.mixin.client.sodium.ChunkBuilderMeshingTaskMixin" -> transformOnce(mixinClassName, classNode, mapper, MixinAsm::transformSodiumChunkRenderer);
case "io.github.cadiboo.nocubes.mixin.client.sodium.FluidRendererMixin" -> transformOnce(mixinClassName, classNode, mapper, MixinAsm::transformSodiumFluidRenderer);
case "io.github.cadiboo.nocubes.mixin.client.sodium.WorldRendererMixin" -> transformOnce(mixinClassName, classNode, mapper, MixinAsm::transformSodiumWorldRenderer);
case "io.github.cadiboo.nocubes.mixin.client.sodium.LevelRendererMixin" -> transformOnce(mixinClassName, classNode, mapper, MixinAsm::transformSodiumLevelRenderer);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,67 @@
package io.github.cadiboo.nocubes.mixin.client;

import com.llamalad7.mixinextras.injector.ModifyReceiver;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import io.github.cadiboo.nocubes.hooks.ClientHooks;
import io.github.cadiboo.nocubes.hooks.trait.INoCubesChunkSectionRenderBuilder;
import io.github.cadiboo.nocubes.mixin.Constants;
import net.minecraft.client.renderer.block.LiquidBlockRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

/**
* There is more to this mixin, see the documentation in {@link io.github.cadiboo.nocubes.hooks.MixinAsm}
* and {@link io.github.cadiboo.nocubes.hooks.MixinAsm#transformFluidRenderer}.
* Changes fluid rendering to support extended fluid rendering
* Redirects all instances of {@link BlockState#getFluidState()} to use our {@link io.github.cadiboo.nocubes.hooks.ClientHooks#getRenderFluidState}
*/
@Mixin(LiquidBlockRenderer.class)
public class LiquidBlockRendererMixin {
/**
* See documentation on {@link Constants#EXTENDED_FLUIDS_BLOCK_POS_REF_NAME}
*/
@ModifyReceiver(
method = {
"tesselate",
"getHeight(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/BlockPos;)F",
"getHeight(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/material/FluidState;)F"
},
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/BlockAndTintGetter;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"
)
)
private BlockAndTintGetter storeBlockPos(
BlockAndTintGetter blockAndTintGetter,
BlockPos pos, @Share(Constants.EXTENDED_FLUIDS_BLOCK_POS_REF_NAME) LocalRef<BlockPos> posRef
) {
posRef.set(pos);
return blockAndTintGetter;
}

/**
* See documentation on {@link Constants#EXTENDED_FLUIDS_BLOCK_POS_REF_NAME}
*/
@Redirect(
method = {
"tesselate",
"getHeight(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/BlockPos;)F",
"getHeight(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/material/FluidState;)F"
},
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/state/BlockState;getFluidState()Lnet/minecraft/world/level/material/FluidState;"
)
)
private FluidState getRenderFluidState(
BlockState instance,
@Share(Constants.EXTENDED_FLUIDS_BLOCK_POS_REF_NAME) LocalRef<BlockPos> pos
) {
return ClientHooks.getRenderFluidState(pos.get(), instance);
}
}
Loading

0 comments on commit bb71aae

Please sign in to comment.