diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java new file mode 100644 index 00000000..0587764f --- /dev/null +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -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()); + } + +} diff --git a/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java b/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java new file mode 100644 index 00000000..8404801b --- /dev/null +++ b/src/main/java/com/replaymod/compat/shaders/ShaderBeginRender.java @@ -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(); + } + } + +} diff --git a/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java b/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java new file mode 100644 index 00000000..55d785b2 --- /dev/null +++ b/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java @@ -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(); + } + } + +} diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java index 875b9703..358ff118 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderEntityRenderer.java @@ -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(); - } - } - } } diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java new file mode 100644 index 00000000..3b4e9bc2 --- /dev/null +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderChunk.java @@ -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 ci) { + if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return; + ci.setReturnValue(true); + } + + +} diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java new file mode 100644 index 00000000..be69cf00 --- /dev/null +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShaderRenderGlobal.java @@ -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) {} + + } + +} diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index c38ba636..f27ada50 100644 --- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -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); } diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java index 34ee45e0..7f5cae0a 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java @@ -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(); } diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index bf81b322..f6ac199a 100755 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -116,5 +116,22 @@ "parent": "replaymod", "screenshots": [], "dependencies": [] - } + }, + { + "modid": "replaymod-compat", + "name": "Replay Mod - Compatibility", + "description": "Compatibility Module of the ReplayMod - Adds compatibility with other mods", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://replaymod.com", + "updateUrl": "https://replaymod.com/download", + "authorList": [ + "CrushedPixel", + "johni0702" + ], + "logoFile": "replaymod_logo.png", + "parent": "replaymod", + "screenshots": [], + "dependencies": [] + } ] diff --git a/src/main/resources/mixins.compat.shaders.replaymod.json b/src/main/resources/mixins.compat.shaders.replaymod.json index 656c092b..eea17922 100644 --- a/src/main/resources/mixins.compat.shaders.replaymod.json +++ b/src/main/resources/mixins.compat.shaders.replaymod.json @@ -1,10 +1,12 @@ { - "required": true, + "required": false, "package": "com.replaymod.compat.shaders.mixin", "mixins": [], "server": [], "client": [ - "MixinShaderEntityRenderer" + "MixinShaderEntityRenderer", + "MixinShaderRenderChunk", + "MixinShaderRenderGlobal" ], "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file