Add Optifine compatibility to rendering by disabling their separate chunk loading queue

Add compatibility to shaders that utilize the Shadow Map by manually setting displayListEntitiesDirty to false
Call Shaders.beginRender in an Event Handler to avoid calling all of EntityRenderer#renderWorld
This commit is contained in:
CrushedPixel
2016-11-09 04:28:56 +01:00
committed by johni0702
parent 3a28c5ba6f
commit e5eb982b98
10 changed files with 270 additions and 47 deletions

View File

@@ -0,0 +1,23 @@
package com.replaymod.compat;
import com.replaymod.compat.shaders.ShaderBeginRender;
import com.replaymod.core.ReplayMod;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.EventBus;
@Mod(modid = ReplayModCompat.MOD_ID, useMetadata = true)
public class ReplayModCompat {
public static final String MOD_ID = "replaymod-compat";
@Mod.Instance(ReplayMod.MOD_ID)
private static ReplayMod core;
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
EventBus bus = FMLCommonHandler.instance().bus();
bus.register(new ShaderBeginRender());
}
}

View File

@@ -0,0 +1,39 @@
package com.replaymod.compat.shaders;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import java.lang.reflect.InvocationTargetException;
public class ShaderBeginRender {
private final Minecraft mc = Minecraft.getMinecraft();
/**
* Invokes Shaders#beginRender when rendering a video,
* as this would usually get called by EntityRenderer#renderWorld,
* which we're not calling during rendering.
*/
@SubscribeEvent
public void onRenderTickStart(TickEvent.RenderTickEvent event) {
if (event.phase != TickEvent.Phase.START) return;
if (ShaderReflection.shaders_beginRender == null) return;
if (ShaderReflection.config_isShaders == null) return;
try {
// check if video is being rendered
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null)
return;
// check if Shaders are enabled
if (!(boolean) (ShaderReflection.config_isShaders.invoke(null))) return;
ShaderReflection.shaders_beginRender.invoke(null, mc, mc.timer.elapsedPartialTicks, 0);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,102 @@
package com.replaymod.compat.shaders;
import net.minecraft.client.Minecraft;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ShaderReflection {
// Shaders.frameTimeCounter
public static Field shaders_frameTimeCounter;
// Shaders.isShadowPass
public static Field shaders_isShadowPass;
// Shaders.beginRender()
public static Method shaders_beginRender;
// RenderGlobal.chunksToUpdateForced (Optifine only)
public static Field renderGlobal_chunksToUpdateForced;
// Config.isShaders() (Optifine only)
public static Method config_isShaders;
static {
initFrameTimeCounter();
initIsShadowPass();
initBeginRender();
initChunksToUpdateForced();
initConfigIsShaders();
}
private static void initFrameTimeCounter() {
try {
shaders_frameTimeCounter = Class.forName("shadersmod.client.Shaders")
.getDeclaredField("frameTimeCounter");
shaders_frameTimeCounter.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchFieldException e) {
// the field wasn't found. Has it been renamed?
e.printStackTrace();
}
}
private static void initIsShadowPass() {
try {
shaders_isShadowPass = Class.forName("shadersmod.client.Shaders")
.getDeclaredField("isShadowPass");
shaders_isShadowPass.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchFieldException e) {
// the field wasn't found. Has it been renamed?
e.printStackTrace();
}
}
private static void initBeginRender() {
try {
shaders_beginRender = Class.forName("shadersmod.client.Shaders")
.getDeclaredMethod("beginRender", Minecraft.class, float.class, long.class);
shaders_frameTimeCounter.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchMethodException e) {
// the method wasn't found. Has it been renamed?
e.printStackTrace();
}
}
private static void initChunksToUpdateForced() {
try {
renderGlobal_chunksToUpdateForced = Class.forName("net.minecraft.client.renderer.RenderGlobal")
.getDeclaredField("chunksToUpdateForced");
renderGlobal_chunksToUpdateForced.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchFieldException e) {
// the field wasn't found. Has it been renamed?
e.printStackTrace();
}
}
private static void initConfigIsShaders() {
try {
config_isShaders = Class.forName("Config")
.getDeclaredMethod("isShaders");
config_isShaders.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchMethodException e) {
// the method wasn't found. Has it been renamed?
e.printStackTrace();
}
}
}

View File

@@ -1,52 +1,28 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.compat.shaders.ShaderReflection;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
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 java.lang.reflect.Field;
@Mixin(EntityRenderer.class)
public abstract class MixinShaderEntityRenderer {
@Shadow
public Minecraft mc;
@Inject(method = "renderWorldPass", at = @At("HEAD"))
private void replayModCompat_updateShaderFrameTimeCounter(CallbackInfo ignore) {
if (ReplayModReplay.instance.getReplayHandler() == null) return;
if (ShaderReflection.shaders_frameTimeCounter == null) return;
private static Field replayModCompat_frameTimeCounterField = null;
static {
replayModCompat_initFrameTimeCounterField();
}
private static void replayModCompat_initFrameTimeCounterField() {
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
float timestamp = replayHandler.getReplaySender().currentTimeStamp() / 1000f % 3600f;
try {
replayModCompat_frameTimeCounterField = Class.forName("shadersmod.client.Shaders")
.getDeclaredField("frameTimeCounter");
replayModCompat_frameTimeCounterField.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchFieldException e) {
// the field wasn't found. Has it been renamed?
ShaderReflection.shaders_frameTimeCounter.set(null, timestamp);
} catch (Exception e) {
e.printStackTrace();
}
}
@Inject(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;enableDepth()V"))
private void replayModCompat_updateShaderFrameTimeCounter(CallbackInfo ignore) {
if (replayModCompat_frameTimeCounterField != null) {
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
float timestamp = replayHandler.getReplaySender().currentTimeStamp() / 1000f % 3600f;
try {
replayModCompat_frameTimeCounterField.set(null, timestamp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,28 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.chunk.RenderChunk;
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.CallbackInfoReturnable;
@Mixin(RenderChunk.class)
public abstract class MixinShaderRenderChunk {
private final Minecraft mc = Minecraft.getMinecraft();
/**
* Changes the RenderChunk#isPlayerUpdate method that Optifine adds
* to always return true while rendering so no chunks are being added
* to a separate rendering queue
*/
@Inject(method = "isPlayerUpdate", at = @At("HEAD"), cancellable = true)
private void replayModCompat_disableIsPlayerUpdate(CallbackInfoReturnable<Boolean> ci) {
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return;
ci.setReturnValue(true);
}
}

View File

@@ -0,0 +1,40 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.compat.shaders.ShaderReflection;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.entity.Entity;
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;
@Mixin(RenderGlobal.class)
public abstract class MixinShaderRenderGlobal {
private final Minecraft mc = Minecraft.getMinecraft();
@Shadow
public boolean displayListEntitiesDirty;
@Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true)
public void replayModCompat_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera,
int frameCount, boolean playerSpectator, CallbackInfo ci) {
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return;
if (ShaderReflection.shaders_isShadowPass == null) return;
// when called by the shadow pass, displayListEntitiesDirty can't be set to false, as no chunk updates
// are being processed. As it's being set to true by ChunkLoadingRenderGlobal#updateChunks, we have to
// set it to false manually to exit the loop imposed by MixinRenderGlobal#replayModRender_setupTerrain.
try {
if ((boolean) ShaderReflection.shaders_isShadowPass.get(null) == true) {
displayListEntitiesDirty = false;
}
} catch (IllegalAccessException ignore) {}
}
}

View File

@@ -6,7 +6,6 @@ import com.replaymod.render.capturer.WorldRenderer;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import java.io.IOException;
@@ -36,18 +35,13 @@ public class EntityRendererHandler implements WorldRenderer {
public void renderWorld(float partialTicks, long finishTimeNano) {
FMLCommonHandler.instance().onRenderTickStart(partialTicks);
// the Shaders Mod does an initializing call in the EntityRenderer#renderWorld method
if (FMLClientHandler.instance().hasOptifine()) {
mc.entityRenderer.renderWorld(partialTicks, finishTimeNano);
} else {
mc.entityRenderer.updateLightmap(partialTicks);
mc.entityRenderer.updateLightmap(partialTicks);
GlStateManager.enableDepth();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc(516, 0.5F);
GlStateManager.enableDepth();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc(516, 0.5F);
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
}
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
FMLCommonHandler.instance().onRenderTickEnd(partialTicks);
}

View File

@@ -39,6 +39,8 @@ public abstract class MixinRenderGlobal {
setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator);
} while (displayListEntitiesDirty);
displayListEntitiesDirty = true;
replayModRender_passThroughSetupTerrain = false;
ci.cancel();
}