Resolve redirect conflict and add more optifine hacks

- Fix entitiesForRendering redirect to be compatible with carpet.
 - Use more reflection for dealing with optifine
 - Fixes issue where flywheel would still be on immediately after enabling optifine shaders
This commit is contained in:
Jozufozu 2022-02-10 21:26:56 -08:00
parent e1af5b2533
commit a4d3f17fb3
8 changed files with 125 additions and 129 deletions

View file

@ -41,8 +41,6 @@ public class Backend {
} }
public static void refresh() { public static void refresh() {
OptifineHandler.refresh();
engine = chooseEngine(); engine = chooseEngine();
} }

View file

@ -1,25 +1,43 @@
package com.jozufozu.flywheel.backend; package com.jozufozu.flywheel.backend;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import net.minecraft.client.Minecraft; import javax.annotation.Nullable;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.culling.Frustum;
public final class OptifineHandler { public final class OptifineHandler {
public static final String OPTIFINE_ROOT_PACKAGE = "net.optifine"; public static final String OPTIFINE_ROOT_PACKAGE = "net.optifine";
public static final String SHADER_PACKAGE = "net.optifine.shaders"; public static final String SHADER_PACKAGE = "net.optifine.shaders";
private static boolean isOptifineInstalled; private static boolean isOptifineInstalled;
private static boolean isUsingShaders; private static BooleanSupplier shadersEnabledSupplier;
private static BooleanSupplier shadowPassSupplier; private static BooleanSupplier shadowPassSupplier;
private static FrustumConstructor shadowFrustumConstructor;
private OptifineHandler() { private OptifineHandler() {
} }
private static FrustumConstructor createShadowFrustumConstructor() {
try {
Class<?> ofShaders = Class.forName("net.optifine.shaders.ShadersRender");
Method method = ofShaders.getDeclaredMethod("makeShadowFrustum", Camera.class, Float.TYPE);
method.setAccessible(true);
return (cam, pt) -> {
try {
return (Frustum) method.invoke(null, cam, pt);
} catch (Exception ignored) {
return null;
}
};
} catch (Exception ignored) {
return ($, $$) -> null;
}
}
private static BooleanSupplier createShadowPassSupplier() { private static BooleanSupplier createShadowPassSupplier() {
try { try {
Class<?> ofShaders = Class.forName("net.optifine.shaders.Shaders"); Class<?> ofShaders = Class.forName("net.optifine.shaders.Shaders");
@ -37,28 +55,21 @@ public final class OptifineHandler {
} }
} }
private static boolean areShadersDisabledInOptifineConfigFile() { private static BooleanSupplier createShadersEnabledSupplier() {
File dir = Minecraft.getInstance().gameDirectory; try {
Class<?> ofShaders = Class.forName("net.optifine.shaders.Shaders");
File shaderOptions = new File(dir, "optionsshaders.txt"); Field field = ofShaders.getDeclaredField("shaderPackLoaded");
field.setAccessible(true);
boolean shadersOff = true; return () -> {
try (BufferedReader reader = new BufferedReader(new FileReader(shaderOptions))) { try {
return field.getBoolean(null);
shadersOff = reader.lines() } catch (IllegalAccessException ignored) {
.anyMatch(it -> { return false;
String line = it.replaceAll("\\s", ""); }
if (line.startsWith("shaderPack=")) { };
String setting = line.substring("shaderPack=".length()); } catch (Exception ignored) {
return () -> false;
return setting.equals("OFF") || setting.equals("(internal)");
}
return false;
});
} catch (IOException e) {
Backend.LOGGER.info("No shader config found.");
} }
return shadersOff;
} }
public static void init() { public static void init() {
@ -67,21 +78,13 @@ public final class OptifineHandler {
if (isOptifineInstalled) { if (isOptifineInstalled) {
Backend.LOGGER.info("Optifine detected."); Backend.LOGGER.info("Optifine detected.");
refresh();
} else { } else {
Backend.LOGGER.info("Optifine not detected."); Backend.LOGGER.info("Optifine not detected.");
} }
shadersEnabledSupplier = createShadersEnabledSupplier();
shadowPassSupplier = createShadowPassSupplier(); shadowPassSupplier = createShadowPassSupplier();
} shadowFrustumConstructor = createShadowFrustumConstructor();
public static void refresh() {
if (!isOptifineInstalled) return;
boolean shadersOff = areShadersDisabledInOptifineConfigFile();
isUsingShaders = !shadersOff;
} }
public static boolean isOptifineInstalled() { public static boolean isOptifineInstalled() {
@ -89,10 +92,26 @@ public final class OptifineHandler {
} }
public static boolean isUsingShaders() { public static boolean isUsingShaders() {
return isUsingShaders; return shadersEnabledSupplier.getAsBoolean();
} }
public static boolean isShadowPass() { public static boolean isShadowPass() {
return shadowPassSupplier.getAsBoolean(); return shadowPassSupplier.getAsBoolean();
} }
@Nullable
public static Frustum createShadowFrustum(Camera camera, float partialTicks) {
var frustum = shadowFrustumConstructor.create(camera, partialTicks);
if (frustum != null) {
var position = camera.getPosition();
frustum.prepare(position.x, position.y, position.z);
}
return frustum;
}
@FunctionalInterface
public interface FrustumConstructor {
@Nullable
Frustum create(Camera camera, float partialTicks);
}
} }

View file

@ -12,6 +12,7 @@ import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent; import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.jozufozu.flywheel.util.ClientLevelExtension;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
@ -130,7 +131,8 @@ public class InstanceWorld {
public void loadEntities(ClientLevel world) { public void loadEntities(ClientLevel world) {
// Block entities are loaded while chunks are baked. // Block entities are loaded while chunks are baked.
// Entities are loaded with the world, so when chunks are reloaded they need to be re-added. // Entities are loaded with the world, so when chunks are reloaded they need to be re-added.
world.entitiesForRendering() ClientLevelExtension.cast(world)
.flywheel$getAllLoadedEntities()
.forEach(entityInstanceManager::add); .forEach(entityInstanceManager::add);
} }

View file

@ -1,51 +0,0 @@
package com.jozufozu.flywheel.mixin;
import java.util.ArrayList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Group;
import org.spongepowered.asm.mixin.injection.Redirect;
import com.google.common.collect.Lists;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.world.entity.Entity;
@Mixin(LevelRenderer.class)
public class CancelEntityRenderMixin {
// TODO: Don't use redirect
@Group(name = "entityFilter", min = 1, max = 1)
@Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;entitiesForRendering()Ljava/lang/Iterable;"))
private Iterable<Entity> filterEntities(ClientLevel world) {
Iterable<Entity> entities = world.entitiesForRendering();
if (Backend.isOn()) {
ArrayList<Entity> filtered = Lists.newArrayList(entities);
filtered.removeIf(InstancedRenderRegistry::shouldSkipRender);
return filtered;
}
return entities;
}
// @Group(name = "entityFilter")
// @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/ClassInstanceMultiMap;iterator()Ljava/util/Iterator;"))
// private Iterator<Entity> filterEntitiesOF(ClassInstanceMultiMap<Entity> classInheritanceMultiMap) {
// if (Backend.getInstance()
// .canUseInstancing()) {
//
// ArrayList<Entity> filtered = Lists.newArrayList(classInheritanceMultiMap);
//
// InstancedRenderRegistry r = InstancedRenderRegistry.getInstance();
// filtered.removeIf(r::shouldSkipRender);
//
// return filtered.iterator();
// }
// return classInheritanceMultiMap.iterator();
// }
}

View file

@ -0,0 +1,42 @@
package com.jozufozu.flywheel.mixin;
import java.util.ArrayList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.google.common.collect.Lists;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.util.ClientLevelExtension;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.entity.LevelEntityGetter;
@Mixin(ClientLevel.class)
public abstract class ClientLevelMixin implements ClientLevelExtension {
@Shadow
protected abstract LevelEntityGetter<Entity> getEntities();
@Override
public Iterable<Entity> flywheel$getAllLoadedEntities() {
return getEntities().getAll();
}
@Inject(method = "entitiesForRendering", at = @At("RETURN"), cancellable = true)
private void filterEntities(CallbackInfoReturnable<Iterable<Entity>> cir) {
if (Backend.isOn()) {
Iterable<Entity> entities = cir.getReturnValue();
ArrayList<Entity> filtered = Lists.newArrayList(entities);
filtered.removeIf(InstancedRenderRegistry::shouldSkipRender);
cir.setReturnValue(filtered);
}
}
}

View file

@ -1,35 +0,0 @@
package com.jozufozu.flywheel.mixin;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.OptifineHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.VideoSettingsScreen;
@Mixin(Minecraft.class)
public class ShaderCloseMixin {
@Shadow
@Nullable
public Screen screen;
@Inject(at = @At("HEAD"), method = "setScreen")
private void whenScreenChanges(Screen screen, CallbackInfo info) {
if (OptifineHandler.isOptifineInstalled() && screen instanceof VideoSettingsScreen) {
Screen old = this.screen;
if (old != null && old.getClass()
.getName()
.startsWith(OptifineHandler.SHADER_PACKAGE)) {
OptifineHandler.refresh();
}
}
}
}

View file

@ -0,0 +1,22 @@
package com.jozufozu.flywheel.util;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
public interface ClientLevelExtension {
/**
* Get an iterator over all entities in this level.
*
* <p>
* Normally, this would be accomplished by {@link ClientLevel#entitiesForRendering()}, but the output of that
* method is filtered of entities that are rendered by flywheel. This interface provides a workaround.
* </p>
* @return An iterator over all entities in the level, including entities that are rendered by flywheel.
*/
Iterable<Entity> flywheel$getAllLoadedEntities();
static ClientLevelExtension cast(ClientLevel level) {
return (ClientLevelExtension) level;
}
}

View file

@ -10,7 +10,7 @@
"BufferBuilderMixin", "BufferBuilderMixin",
"BufferUploaderMixin", "BufferUploaderMixin",
"CameraMixin", "CameraMixin",
"CancelEntityRenderMixin", "ClientLevelMixin",
"ChunkRebuildHooksMixin", "ChunkRebuildHooksMixin",
"EntityTypeMixin", "EntityTypeMixin",
"FixFabulousDepthMixin", "FixFabulousDepthMixin",
@ -23,7 +23,6 @@
"PausedPartialTickAccessor", "PausedPartialTickAccessor",
"RenderTexturesMixin", "RenderTexturesMixin",
"RenderTypeMixin", "RenderTypeMixin",
"ShaderCloseMixin",
"atlas.AtlasDataMixin", "atlas.AtlasDataMixin",
"atlas.SheetDataAccessor", "atlas.SheetDataAccessor",
"light.LightUpdateMixin", "light.LightUpdateMixin",