From 25582b6a518685d807ce116fc029f1e1a7acfb83 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Tue, 14 Jun 2016 17:01:32 +0200 Subject: [PATCH] Separate rendering into its own module --- .../java/com/replaymod/core/ReplayMod.java | 5 +- .../com/replaymod/render/RenderSettings.java | 86 +++ .../com/replaymod/render/ReplayModRender.java | 32 + .../replaymod/render}/VideoWriter.java | 41 +- .../render/capturer/CaptureData.java | 4 + .../capturer/CubicOpenGlFrameCapturer.java | 4 +- .../capturer/CubicPboOpenGlFrameCapturer.java | 6 +- .../MultiFramePboOpenGlFrameCapturer.java | 10 +- .../render}/capturer/OpenGlFrameCapturer.java | 10 +- .../render}/capturer/RenderInfo.java | 6 +- .../capturer/SimpleOpenGlFrameCapturer.java | 4 +- .../SimplePboOpenGlFrameCapturer.java | 8 +- .../StereoscopicOpenGlFrameCapturer.java | 6 +- .../StereoscopicPboOpenGlFrameCapturer.java | 6 +- .../render}/capturer/WorldRenderer.java | 2 +- .../render}/frame/CubicOpenGlFrame.java | 4 +- .../replaymod/render}/frame/OpenGlFrame.java | 4 +- .../replaymod/render}/frame/RGBFrame.java | 6 +- .../frame/StereoscopicOpenGlFrame.java | 4 +- .../render/gui/GuiRenderSettings.java | 414 +++++++++++ .../render/gui/GuiVideoRenderer.java | 284 ++++++++ .../hooks}/ChunkLoadingRenderGlobal.java | 62 +- .../render/hooks}/EntityRendererHandler.java | 24 +- .../render/hooks/RenderReplayTimer.java | 26 + .../render}/metadata/MetadataInjector.java | 2 +- .../render}/mixin/MixinEffectRenderer.java | 10 +- .../render/mixin/MixinEntityRenderer.java | 241 +++++++ .../replaymod/render}/mixin/MixinRender.java | 10 +- .../render/mixin/MixinRenderGlobal.java | 61 ++ .../render/mixin/MixinRenderManager.java | 32 + .../mixin/MixinRendererLivingEntity.java | 12 +- .../processor/AbstractFrameProcessor.java | 6 +- .../processor/CubicToRGBProcessor.java | 10 +- .../EquirectangularToRGBProcessor.java | 8 +- .../processor/OpenGlToRGBProcessor.java | 6 +- .../processor/StereoscopicToRGBProcessor.java | 10 +- .../com/replaymod/render/rendering/Frame.java | 5 + .../render}/rendering/FrameCapturer.java | 2 +- .../render}/rendering/FrameConsumer.java | 2 +- .../render}/rendering/FrameProcessor.java | 2 +- .../replaymod/render}/rendering/Pipeline.java | 2 +- .../replaymod/render/rendering/Pipelines.java | 76 ++ .../render/rendering/VideoRenderer.java | 308 ++++++++ .../render}/utils/ByteBufferPool.java | 6 +- .../render/utils}/PixelBufferObject.java | 24 +- .../com/replaymod/render/utils/Utils.java | 26 + .../simplepathing/gui/GuiPathing.java | 59 +- .../coremod/ForceChunkLoadingCT.java | 255 ------- .../replaymod/coremod/LoadingPlugin.java | 2 +- .../replaymod/gui/GuiRenderSettings.java | 675 ------------------ .../replaymod/gui/GuiVideoRenderer.java | 305 -------- .../replaymod/mixin/MixinEntityRenderer.java | 231 +----- .../replaymod/mixin/MixinRenderManager.java | 18 - .../replaymod/renderer/SpectatorRenderer.java | 4 +- .../replaymod/settings/EncodingPreset.java | 54 -- .../replaymod/settings/RenderOptions.java | 267 ------- .../replaymod/timer/ReplayTimer.java | 43 -- .../replaymod/utils/BoundingUtils.java | 3 +- .../replaymod/utils/DurationUtils.java | 15 - .../replaymod/utils/OpenGLUtils.java | 13 - .../replaymod/video/ScreenCapture.java | 73 -- .../replaymod/video/VideoRenderer.java | 397 ---------- .../replaymod/video/capturer/CaptureData.java | 4 - .../replaymod/video/rendering/Frame.java | 5 - .../replaymod/video/rendering/Pipelines.java | 80 --- src/main/resources/mcmod.info | 17 + .../resources/mixins.render.replaymod.json | 15 + src/main/resources/mixins.replaymod.json | 3 - 68 files changed, 1822 insertions(+), 2635 deletions(-) create mode 100644 src/main/java/com/replaymod/render/RenderSettings.java create mode 100644 src/main/java/com/replaymod/render/ReplayModRender.java rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/VideoWriter.java (68%) create mode 100644 src/main/java/com/replaymod/render/capturer/CaptureData.java rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/CubicOpenGlFrameCapturer.java (91%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/CubicPboOpenGlFrameCapturer.java (81%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/MultiFramePboOpenGlFrameCapturer.java (91%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/OpenGlFrameCapturer.java (92%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/RenderInfo.java (54%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/SimpleOpenGlFrameCapturer.java (79%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/SimplePboOpenGlFrameCapturer.java (92%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/StereoscopicOpenGlFrameCapturer.java (82%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/StereoscopicPboOpenGlFrameCapturer.java (79%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/capturer/WorldRenderer.java (81%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/frame/CubicOpenGlFrame.java (93%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/frame/OpenGlFrame.java (77%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/frame/RGBFrame.java (83%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/frame/StereoscopicOpenGlFrame.java (85%) create mode 100644 src/main/java/com/replaymod/render/gui/GuiRenderSettings.java create mode 100644 src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java rename src/main/java/{eu/crushedpixel/replaymod/renderer => com/replaymod/render/hooks}/ChunkLoadingRenderGlobal.java (61%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render/hooks}/EntityRendererHandler.java (72%) create mode 100644 src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/metadata/MetadataInjector.java (98%) rename src/main/java/{eu/crushedpixel/replaymod => com/replaymod/render}/mixin/MixinEffectRenderer.java (90%) create mode 100644 src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java rename src/main/java/{eu/crushedpixel/replaymod => com/replaymod/render}/mixin/MixinRender.java (63%) create mode 100644 src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java create mode 100644 src/main/java/com/replaymod/render/mixin/MixinRenderManager.java rename src/main/java/{eu/crushedpixel/replaymod => com/replaymod/render}/mixin/MixinRendererLivingEntity.java (75%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/processor/AbstractFrameProcessor.java (55%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/processor/CubicToRGBProcessor.java (83%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/processor/EquirectangularToRGBProcessor.java (95%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/processor/OpenGlToRGBProcessor.java (87%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/processor/StereoscopicToRGBProcessor.java (76%) create mode 100644 src/main/java/com/replaymod/render/rendering/Frame.java rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/rendering/FrameCapturer.java (73%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/rendering/FrameConsumer.java (71%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/rendering/FrameProcessor.java (73%) rename src/main/java/{eu/crushedpixel/replaymod/video => com/replaymod/render}/rendering/Pipeline.java (98%) create mode 100644 src/main/java/com/replaymod/render/rendering/Pipelines.java create mode 100644 src/main/java/com/replaymod/render/rendering/VideoRenderer.java rename src/main/java/{eu/crushedpixel/replaymod => com/replaymod/render}/utils/ByteBufferPool.java (89%) rename src/main/java/{eu/crushedpixel/replaymod/opengl => com/replaymod/render/utils}/PixelBufferObject.java (92%) create mode 100644 src/main/java/com/replaymod/render/utils/Utils.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/coremod/ForceChunkLoadingCT.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoRenderer.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/settings/EncodingPreset.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java delete mode 100755 src/main/java/eu/crushedpixel/replaymod/timer/ReplayTimer.java delete mode 100755 src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/video/capturer/CaptureData.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/video/rendering/Frame.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java create mode 100644 src/main/resources/mixins.render.replaymod.json diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 6eebeda3..72f73d20 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -14,13 +14,10 @@ import eu.crushedpixel.replaymod.registry.UploadedFileHandler; import eu.crushedpixel.replaymod.renderer.CustomObjectRenderer; import eu.crushedpixel.replaymod.renderer.PathPreviewRenderer; import eu.crushedpixel.replaymod.renderer.SpectatorRenderer; -import eu.crushedpixel.replaymod.settings.EncodingPreset; -import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.settings.ReplaySettings; import eu.crushedpixel.replaymod.sound.SoundHandler; import eu.crushedpixel.replaymod.utils.OpenGLUtils; import eu.crushedpixel.replaymod.utils.TooltipRenderer; -import eu.crushedpixel.replaymod.video.rendering.Pipelines; import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.client.settings.GameSettings; @@ -204,6 +201,7 @@ public class ReplayMod { String height = System.getProperty("replaymod.render.height"); String exportCommand = System.getProperty("replaymod.render.exportcommand"); String exportCommandArgs = System.getProperty("replaymod.render.exportcommandargs"); + /* final RenderOptions options = new RenderOptions(); if (bitrate != null) { options.setBitrate(bitrate); @@ -265,6 +263,7 @@ public class ReplayMod { } } options.setMode(pipelinePreset); + */ @SuppressWarnings("unchecked") Queue tasks = mc.scheduledTasks; diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java new file mode 100644 index 00000000..f87339be --- /dev/null +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -0,0 +1,86 @@ +package com.replaymod.render; + +import lombok.Data; +import net.minecraft.client.resources.I18n; +import org.lwjgl.util.ReadableColor; + +import java.io.File; + +@Data +public class RenderSettings { + public enum RenderMethod { + DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR; + + @Override + public String toString() { + return I18n.format("replaymod.gui.rendersettings.renderer." + name().toLowerCase()); + } + + public String getDescription() { + return I18n.format("replaymod.gui.rendersettings.renderer." + name().toLowerCase() + ".description"); + } + } + + public enum EncodingPreset { + MP4_CUSTOM("-an -c:v libx264 -b:v %BITRATE% -pix_fmt yuv420p \"%FILENAME%\"", "mp4"), + + MP4_HIGH("-an -c:v libx264 -preset ultrafast -qp 1 -pix_fmt yuv420p \"%FILENAME%\"", "mp4"), + + MP4_DEFAULT("-an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \"%FILENAME%\"", "mp4"), + + MP4_POTATO("-an -c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \"%FILENAME%\"", "mp4"), + + WEBM_CUSTOM("-an -c:v libvpx -b:v %BITRATE% \"%FILENAME%\"", "webm"), + + MKV_LOSSLESS("-an -c:v libx264 -preset ultrafast -qp 0 \"%FILENAME%\"", "mkv"), + + PNG("\"%FILENAME%-%06d.png\"", "png"); + + private final String preset; + private final String fileExtension; + + EncodingPreset(String preset, String fileExtension) { + this.preset = preset; + this.fileExtension = fileExtension; + } + + public String getValue() { + return "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - " + preset; + } + + public String getFileExtension() { + return fileExtension; + } + + public boolean hasBitrateSetting() { + return preset.contains("%BITRATE%"); + } + + public boolean isYuv420() { return preset.contains("-pix_fmt yuv420p"); } + + @Override + public String toString() { + return I18n.format("replaymod.gui.rendersettings.presets." + name().replace('_', '.').toLowerCase()); + } + } + + private final RenderMethod renderMethod; + private final EncodingPreset encodingPreset; + private final int videoWidth; + private final int videoHeight; + private final int framesPerSecond; + private final int bitRate; + private final File outputFile; + + private final boolean renderNameTags; + private final boolean stabilizeYaw; + private final boolean stabilizePitch; + private final boolean stabilizeRoll; + private final ReadableColor chromaKeyingColor; + private final boolean inject360Metadata; + + private final String exportCommand; + private final String exportArguments; + + private final boolean highPerformance; +} diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java new file mode 100644 index 00000000..bc72af0b --- /dev/null +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -0,0 +1,32 @@ +package com.replaymod.render; + +import com.replaymod.core.ReplayMod; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import org.apache.logging.log4j.Logger; + +@Mod(modid = ReplayModRender.MOD_ID, useMetadata = true) +public class ReplayModRender { + public static final String MOD_ID = "replaymod-render"; + + @Mod.Instance(MOD_ID) + public static ReplayModRender instance; + + @Mod.Instance(ReplayMod.MOD_ID) + private static ReplayMod core; + + private Logger logger; + + private Configuration configuration; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) { + logger = event.getModLog(); + configuration = new Configuration(event.getSuggestedConfigurationFile()); + } + + public Configuration getConfiguration() { + return configuration; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java b/src/main/java/com/replaymod/render/VideoWriter.java similarity index 68% rename from src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java rename to src/main/java/com/replaymod/render/VideoWriter.java index 2d1ae51f..4eb2da13 100755 --- a/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java +++ b/src/main/java/com/replaymod/render/VideoWriter.java @@ -1,11 +1,10 @@ -package eu.crushedpixel.replaymod.video; +package com.replaymod.render; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.rendering.FrameConsumer; +import com.replaymod.render.utils.ByteBufferPool; import eu.crushedpixel.replaymod.utils.StreamPipe; import eu.crushedpixel.replaymod.utils.StringUtils; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; -import eu.crushedpixel.replaymod.video.rendering.FrameConsumer; import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; @@ -26,30 +25,30 @@ import static org.apache.commons.lang3.Validate.isTrue; public class VideoWriter implements FrameConsumer { - private final RenderOptions options; + private final RenderSettings settings; private final Process process; private final OutputStream outputStream; private final WritableByteChannel channel; private final String commandArgs; private volatile boolean aborted; - public VideoWriter(final RenderOptions options) throws IOException { - this.options = options.copy(); + public VideoWriter(final RenderSettings settings) throws IOException { + this.settings = settings; - File outputFolder = options.getOutputFile().getParentFile(); - String fileName = options.getOutputFile().getName(); + File outputFolder = settings.getOutputFile().getParentFile(); + String fileName = settings.getOutputFile().getName(); - commandArgs = options.getExportCommandArgs() - .replace("%WIDTH%", String.valueOf(options.getWidth())) - .replace("%HEIGHT%", String.valueOf(options.getHeight())) - .replace("%FPS%", String.valueOf(options.getFps())) + commandArgs = settings.getExportArguments() + .replace("%WIDTH%", String.valueOf(settings.getVideoWidth())) + .replace("%HEIGHT%", String.valueOf(settings.getVideoHeight())) + .replace("%FPS%", String.valueOf(settings.getFramesPerSecond())) .replace("%FILENAME%", fileName) - .replace("%BITRATE%", options.getBitrate()); + .replace("%BITRATE%", String.valueOf(settings.getBitRate())); - List command = new ArrayList(); - command.add(options.getExportCommand()); + List command = new ArrayList<>(); + command.add(settings.getExportCommand().isEmpty() ? "ffmpeg" : settings.getExportCommand()); command.addAll(StringUtils.translateCommandline(commandArgs)); - System.out.println("Starting " + options.getExportCommand() + " with args: " + commandArgs); + System.out.println("Starting " + settings.getExportCommand() + " with args: " + commandArgs); process = new ProcessBuilder(command).directory(outputFolder).start(); OutputStream exportLogOut = new FileOutputStream("export.log"); new StreamPipe(process.getInputStream(), exportLogOut).start(); @@ -96,7 +95,7 @@ public class VideoWriter implements FrameConsumer { } CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame"); CrashReportCategory exportDetails = report.makeCategory("Export details"); - exportDetails.addCrashSection("Export command", options.getExportCommand()); + exportDetails.addCrashSection("Export command", settings.getExportCommand()); exportDetails.addCrashSection("Export args", commandArgs); Minecraft.getMinecraft().crashed(report); } @@ -107,8 +106,8 @@ public class VideoWriter implements FrameConsumer { } private void checkSize(int width, int height) { - isTrue(width == options.getWidth(), "Width has to be %d but was %d", options.getWidth(), width); - isTrue(height == options.getHeight(), "Height has to be %d but was %d", options.getHeight(), height); + isTrue(width == settings.getVideoWidth(), "Width has to be %d but was %d", settings.getVideoWidth(), width); + isTrue(height == settings.getVideoHeight(), "Height has to be %d but was %d", settings.getVideoHeight(), height); } public void abort() { diff --git a/src/main/java/com/replaymod/render/capturer/CaptureData.java b/src/main/java/com/replaymod/render/capturer/CaptureData.java new file mode 100644 index 00000000..988a102b --- /dev/null +++ b/src/main/java/com/replaymod/render/capturer/CaptureData.java @@ -0,0 +1,4 @@ +package com.replaymod.render.capturer; + +public interface CaptureData { +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java similarity index 91% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java index fce13bbc..dc01c374 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame; +import com.replaymod.render.frame.CubicOpenGlFrame; public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer { public enum Data implements CaptureData { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicPboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/CubicPboOpenGlFrameCapturer.java similarity index 81% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicPboOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/CubicPboOpenGlFrameCapturer.java index 8fb1a03a..a7cf9910 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CubicPboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/CubicPboOpenGlFrameCapturer.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; +import com.replaymod.render.frame.CubicOpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; public class CubicPboOpenGlFrameCapturer extends MultiFramePboOpenGlFrameCapturer { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/MultiFramePboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java similarity index 91% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/MultiFramePboOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java index 683a21f3..55eccf01 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/MultiFramePboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java @@ -1,9 +1,9 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.opengl.PixelBufferObject; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.rendering.Frame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.rendering.Frame; +import com.replaymod.render.utils.ByteBufferPool; +import com.replaymod.render.utils.PixelBufferObject; import net.minecraft.client.renderer.OpenGlHelper; import org.lwjgl.opengl.GL11; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java similarity index 92% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java index ccc82346..4513f2ae 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java @@ -1,9 +1,9 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.rendering.Frame; -import eu.crushedpixel.replaymod.video.rendering.FrameCapturer; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.rendering.Frame; +import com.replaymod.render.rendering.FrameCapturer; +import com.replaymod.render.utils.ByteBufferPool; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.shader.Framebuffer; import org.lwjgl.opengl.GL11; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/RenderInfo.java b/src/main/java/com/replaymod/render/capturer/RenderInfo.java similarity index 54% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/RenderInfo.java rename to src/main/java/com/replaymod/render/capturer/RenderInfo.java index 0b20de27..896e2419 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/RenderInfo.java +++ b/src/main/java/com/replaymod/render/capturer/RenderInfo.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.settings.RenderOptions; +import com.replaymod.render.RenderSettings; import org.lwjgl.util.ReadableDimension; public interface RenderInfo { @@ -10,5 +10,5 @@ public interface RenderInfo { float updateForNextFrame(); - RenderOptions getRenderOptions(); + RenderSettings getRenderSettings(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/SimpleOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/SimpleOpenGlFrameCapturer.java similarity index 79% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/SimpleOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/SimpleOpenGlFrameCapturer.java index 29f6bd0c..7c677280 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/SimpleOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/SimpleOpenGlFrameCapturer.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/SimplePboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java similarity index 92% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/SimplePboOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java index 39806adc..13492562 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/SimplePboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java @@ -1,8 +1,8 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.opengl.PixelBufferObject; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.utils.ByteBufferPool; +import com.replaymod.render.utils.PixelBufferObject; import net.minecraft.client.renderer.OpenGlHelper; import org.lwjgl.opengl.GL11; import org.lwjgl.util.ReadableDimension; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/StereoscopicOpenGlFrameCapturer.java similarity index 82% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/StereoscopicOpenGlFrameCapturer.java index c1df12d8..846a9274 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/StereoscopicOpenGlFrameCapturer.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.frame.StereoscopicOpenGlFrame; public class StereoscopicOpenGlFrameCapturer extends OpenGlFrameCapturer { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicPboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/StereoscopicPboOpenGlFrameCapturer.java similarity index 79% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicPboOpenGlFrameCapturer.java rename to src/main/java/com/replaymod/render/capturer/StereoscopicPboOpenGlFrameCapturer.java index f4a5d632..95b36ef9 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/StereoscopicPboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/StereoscopicPboOpenGlFrameCapturer.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.frame.StereoscopicOpenGlFrame; public class StereoscopicPboOpenGlFrameCapturer extends MultiFramePboOpenGlFrameCapturer { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/WorldRenderer.java b/src/main/java/com/replaymod/render/capturer/WorldRenderer.java similarity index 81% rename from src/main/java/eu/crushedpixel/replaymod/video/capturer/WorldRenderer.java rename to src/main/java/com/replaymod/render/capturer/WorldRenderer.java index 6eb74cf9..f1f9d020 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/WorldRenderer.java +++ b/src/main/java/com/replaymod/render/capturer/WorldRenderer.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.capturer; +package com.replaymod.render.capturer; import org.lwjgl.util.ReadableDimension; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicOpenGlFrame.java b/src/main/java/com/replaymod/render/frame/CubicOpenGlFrame.java similarity index 93% rename from src/main/java/eu/crushedpixel/replaymod/video/frame/CubicOpenGlFrame.java rename to src/main/java/com/replaymod/render/frame/CubicOpenGlFrame.java index 2414b88f..217e4849 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicOpenGlFrame.java +++ b/src/main/java/com/replaymod/render/frame/CubicOpenGlFrame.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.frame; +package com.replaymod.render.frame; -import eu.crushedpixel.replaymod.video.rendering.Frame; +import com.replaymod.render.rendering.Frame; import lombok.Getter; import org.apache.commons.lang3.Validate; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/OpenGlFrame.java b/src/main/java/com/replaymod/render/frame/OpenGlFrame.java similarity index 77% rename from src/main/java/eu/crushedpixel/replaymod/video/frame/OpenGlFrame.java rename to src/main/java/com/replaymod/render/frame/OpenGlFrame.java index df6fb1a9..1d595aab 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/OpenGlFrame.java +++ b/src/main/java/com/replaymod/render/frame/OpenGlFrame.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.frame; +package com.replaymod.render.frame; -import eu.crushedpixel.replaymod.video.rendering.Frame; +import com.replaymod.render.rendering.Frame; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.lwjgl.util.ReadableDimension; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/RGBFrame.java b/src/main/java/com/replaymod/render/frame/RGBFrame.java similarity index 83% rename from src/main/java/eu/crushedpixel/replaymod/video/frame/RGBFrame.java rename to src/main/java/com/replaymod/render/frame/RGBFrame.java index 094866e8..8a1f5a8c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/RGBFrame.java +++ b/src/main/java/com/replaymod/render/frame/RGBFrame.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.frame; +package com.replaymod.render.frame; -import eu.crushedpixel.replaymod.video.rendering.Frame; +import com.replaymod.render.rendering.Frame; import lombok.Getter; import org.apache.commons.lang3.Validate; import org.lwjgl.util.ReadableDimension; @@ -20,7 +20,7 @@ public class RGBFrame implements Frame { public RGBFrame(int frameId, ReadableDimension size, ByteBuffer byteBuffer) { Validate.isTrue(size.getWidth() * size.getHeight() * 3 == byteBuffer.remaining(), "Buffer size is %d (cap: %d) but should be %d", - byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * 4); + byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * 3); this.frameId = frameId; this.size = size; this.byteBuffer = byteBuffer; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicOpenGlFrame.java b/src/main/java/com/replaymod/render/frame/StereoscopicOpenGlFrame.java similarity index 85% rename from src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicOpenGlFrame.java rename to src/main/java/com/replaymod/render/frame/StereoscopicOpenGlFrame.java index 0fbbbefe..39eb0432 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicOpenGlFrame.java +++ b/src/main/java/com/replaymod/render/frame/StereoscopicOpenGlFrame.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.video.frame; +package com.replaymod.render.frame; -import eu.crushedpixel.replaymod.video.rendering.Frame; +import com.replaymod.render.rendering.Frame; import lombok.Getter; import org.apache.commons.lang3.Validate; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java new file mode 100644 index 00000000..43880b90 --- /dev/null +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -0,0 +1,414 @@ +package com.replaymod.render.gui; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; +import com.replaymod.pathing.path.Timeline; +import com.replaymod.render.RenderSettings; +import com.replaymod.render.ReplayModRender; +import com.replaymod.render.rendering.VideoRenderer; +import com.replaymod.replay.ReplayHandler; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.GuiVerticalList; +import de.johni0702.minecraft.gui.element.*; +import de.johni0702.minecraft.gui.element.advanced.GuiColorPicker; +import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu; +import de.johni0702.minecraft.gui.function.Closeable; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.GridLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup; +import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.utils.Consumer; +import de.johni0702.minecraft.gui.utils.Utils; +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +public class GuiRenderSettings extends GuiScreen implements Closeable { + public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT); + public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true); + + public final GuiDropdownMenu renderMethodDropdown = + new GuiDropdownMenu().onSelection(new Consumer() { + @Override + public void consume(Integer old) { + updateInputs(); + } + }).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.RenderMethod.values()); + + { + for (Map.Entry entry : + renderMethodDropdown.getDropdownEntries().entrySet()) { + entry.getValue().setTooltip(new GuiTooltip().setText(entry.getKey().getDescription())); + } + } + + public final GuiDropdownMenu encodingPresetDropdown = + new GuiDropdownMenu().onSelection(new Consumer() { + @Override + public void consume(Integer old) { + RenderSettings.EncodingPreset newPreset = encodingPresetDropdown.getSelectedValue(); + // Update export arguments to match new Preset + exportArguments.setText(newPreset.getValue()); + // If the user hasn't changed the output file by themselves, + if (!outputFileManuallySet) { + // generate a new output file name with updated file extension + outputFile = generateOutputFile(newPreset); + outputFileButton.setLabel(outputFile.getName()); + } + updateInputs(); + } + }).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.EncodingPreset.values()); + + public final GuiNumberField videoWidth = new GuiNumberField().setSize(50, 20).setMinValue(1); + public final GuiNumberField videoHeight = new GuiNumberField().setSize(50, 20).setMinValue(1); + public final GuiSlider frameRateSlider = new GuiSlider().onValueChanged(new Runnable() { + @Override + public void run() { + frameRateSlider.setText(I18n.format("replaymod.gui.rendersettings.framerate") + + ": " + (frameRateSlider.getValue() + 10)); + } + }).setSize(122, 20).setSteps(110); + public final GuiPanel videoResolutionPanel = new GuiPanel() + .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2)) + .addElements(null, videoWidth, new GuiLabel().setText("*"), videoHeight); + + public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20); + public final GuiDropdownMenu bitRateUnit = new GuiDropdownMenu() + .setSize(50, 20).setValues("bps", "kbps", "mbps").setSelected("mbps"); + + public final GuiButton outputFileButton = new GuiButton().setMinSize(new Dimension(0, 20)).onClick(new Runnable() { + @Override + public void run() { + Futures.addCallback( + GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save", + encodingPresetDropdown.getSelectedValue().getFileExtension()).getFuture(), + new FutureCallback() { + @Override + public void onSuccess(@Nullable File result) { + if (result != null) { + outputFile = result; + outputFileManuallySet = true; + outputFileButton.setLabel(result.getName()); + } + } + + @Override + public void onFailure(Throwable t) { + throw new RuntimeException(t); + } + }); + } + }); + + public final GuiPanel mainPanel = new GuiPanel() + .addElements(new GridLayout.Data(1, 0.5), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.renderer"), renderMethodDropdown, + new GuiLabel().setI18nText("replaymod.gui.rendersettings.presets"), encodingPresetDropdown, + new GuiLabel().setI18nText("replaymod.gui.rendersettings.customresolution"), videoResolutionPanel, + new GuiLabel().setI18nText("replaymod.gui.settings.bitrate"), new GuiPanel().addElements(null, + new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()), + frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton) + .setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(3)); + + public final GuiCheckbox nametagCheckbox = new GuiCheckbox() + .setI18nLabel("replaymod.gui.rendersettings.nametags"); + + public final GuiPanel stabilizePanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(10)); + public final GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel) + .setI18nLabel("replaymod.gui.yaw"); + public final GuiCheckbox stabilizePitch = new GuiCheckbox(stabilizePanel) + .setI18nLabel("replaymod.gui.pitch"); + public final GuiCheckbox stabilizeRoll = new GuiCheckbox(stabilizePanel) + .setI18nLabel("replaymod.gui.roll"); + + public final GuiCheckbox chromaKeyingCheckbox = new GuiCheckbox() + .setI18nLabel("replaymod.gui.rendersettings.chromakey"); + public final GuiColorPicker chromaKeyingColor = new GuiColorPicker().setSize(30, 15); + + public final GuiCheckbox inject360Metadata = new GuiCheckbox() + .setI18nLabel("replaymod.gui.rendersettings.360metadata"); + + public final GuiPanel advancedPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(15)) + .addElements(null, nametagCheckbox, new GuiPanel().setLayout( + new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) + .addElements(new GridLayout.Data(0, 0.5), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, + chromaKeyingCheckbox, chromaKeyingColor, + inject360Metadata)); + + public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command") + .setSize(55, 20).setMaxLength(100); + public final GuiTextField exportArguments = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.arguments") + .setMinSize(new Dimension(245, 20)).setMaxLength(500); + + public final GuiPanel commandlinePanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10)) + .addElements(null, + new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)).addElements(null, exportCommand, exportArguments), + new GuiLabel(new GuiPanel().setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + size(container.getChildren().iterator().next(), width, height); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + return new Dimension(300, 50); + } + })).setI18nText("replaymod.gui.rendersettings.ffmpeg.description").getContainer()); + + + public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(4)); + public final GuiButton renderButton = new GuiButton(buttonPanel).onClick(new Runnable() { + @Override + public void run() { + try { + VideoRenderer videoRenderer = new VideoRenderer(save(true), replayHandler, timeline); + videoRenderer.renderVideo(); + } catch (IOException e) { + e.printStackTrace(); + + GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), + I18n.format("replaymod.gui.rendering.error.message")); + getMinecraft().displayGuiScreen(errorScreen); + } catch (Throwable t) { + CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video"); + throw new ReportedException(crashReport); + } + } + }).setSize(148, 20).setI18nLabel("replaymod.gui.render"); + public final GuiButton cancelButton = new GuiButton(buttonPanel).onClick(new Runnable() { + @Override + public void run() { + getMinecraft().displayGuiScreen(null); + } + }).setSize(148, 20).setI18nLabel("replaymod.gui.cancel"); + + { + setBackground(Background.NONE); + Utils.link(videoWidth, videoHeight, bitRateField); + setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2); + } + }); + + contentPanel.setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + size(settingsList, width, height - height(buttonPanel) - 25); + pos(settingsList, width / 2 - width(settingsList) / 2, 5); + pos(buttonPanel, width / 2 - width(buttonPanel) / 2, y(settingsList) + height(settingsList) + 10); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + ReadableDimension screenSize = getMinSize(); + return new Dimension(screenSize.getWidth() - 40, screenSize.getHeight() - 40); + } + }); + + settingsList.getListPanel().setLayout(new VerticalLayout().setSpacing(10)) + .addElements(new VerticalLayout.Data(0.5), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.video"), mainPanel, new GuiPanel(), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.advanced"), advancedPanel, new GuiPanel(), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.commandline"), commandlinePanel); + + videoWidth.onTextChanged(new Consumer() { + @Override + public void consume(String old) { + updateInputs(); + } + }); + videoHeight.onTextChanged(new Consumer() { + @Override + public void consume(String obj) { + updateInputs(); + } + }); + } + + private final ReplayHandler replayHandler; + private final Timeline timeline; + private File outputFile; + private boolean outputFileManuallySet; + + public GuiRenderSettings(ReplayHandler replayHandler, Timeline timeline) { + this.replayHandler = replayHandler; + this.timeline = timeline; + + String json = getConfigProperty(ReplayModRender.instance.getConfiguration()).getString(); + RenderSettings settings = new GsonBuilder().registerTypeAdapter(RenderSettings.class, new InstanceCreator() { + @Override + public RenderSettings createInstance(Type type) { + return getDefaultRenderSettings(); + } + }).create().fromJson(json, RenderSettings.class); + load(settings); + } + + protected void updateInputs() { + // Validate video width and height + String error = isResolutionValid(); + if (error == null) { + renderButton.setEnabled().setTooltip(null); + videoWidth.setTextColor(Colors.WHITE); + videoHeight.setTextColor(Colors.WHITE); + } else { + renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(error)); + videoWidth.setTextColor(Colors.RED); + videoHeight.setTextColor(Colors.RED); + } + + // Enable/Disable camera stabilization checkboxes + switch (renderMethodDropdown.getSelectedValue()) { + case CUBIC: + case EQUIRECTANGULAR: + stabilizePanel.forEach(IGuiCheckbox.class).setEnabled(); + break; + default: + stabilizePanel.forEach(IGuiCheckbox.class).setDisabled(); + } + + // Enable/Disable inject metadata checkbox + if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4") + && renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR) { + inject360Metadata.setEnabled().setTooltip(null); + } else { + inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) + .setI18nText("replaymod.gui.rendersettings.360metadata.error")); + } + } + + protected String isResolutionValid() { + RenderSettings.EncodingPreset preset = encodingPresetDropdown.getSelectedValue(); + RenderSettings.RenderMethod method = renderMethodDropdown.getSelectedValue(); + int videoWidth = this.videoWidth.getInteger(); + int videoHeight = this.videoHeight.getInteger(); + + // Make sure the export arguments haven't been changed manually + if (exportArguments.getText().equals(preset.getValue())) { + // Yuv420 requires both dimensions to be even + if (preset.isYuv420() + && (videoWidth % 2 != 0 || videoHeight % 2 != 0)) { + return "replaymod.gui.rendersettings.customresolution.warning.yuv420"; + } + } + + if (method == RenderSettings.RenderMethod.CUBIC + && (videoWidth * 3 / 4 != videoHeight || videoWidth * 3 % 4 != 0)) { + return "replaymod.gui.rendersettings.customresolution.warning.cubic"; + } + if (method == RenderSettings.RenderMethod.EQUIRECTANGULAR + && (videoWidth / 2 != videoHeight || videoWidth % 2 != 0)) { + return "replaymod.gui.rendersettings.customresolution.warning.equirectangular"; + } + return null; + } + + public void load(RenderSettings settings) { + renderMethodDropdown.setSelected(settings.getRenderMethod()); + encodingPresetDropdown.setSelected(settings.getEncodingPreset()); + videoWidth.setValue(settings.getVideoWidth()); + videoHeight.setValue(settings.getVideoHeight()); + frameRateSlider.setValue(settings.getFramesPerSecond() - 10); + if (settings.getBitRate() % (1 << 20) == 0) { + bitRateField.setValue(settings.getBitRate() >> 20); + bitRateUnit.setSelected(2); + } else if (settings.getBitRate() % (1 << 10) == 0) { + bitRateField.setValue(settings.getBitRate() >> 10); + bitRateUnit.setSelected(1); + } else { + bitRateField.setValue(settings.getBitRate()); + bitRateUnit.setSelected(0); + } + if (settings.getOutputFile() == null) { + outputFile = generateOutputFile(settings.getEncodingPreset()); + outputFileManuallySet = false; + } else { + outputFile = settings.getOutputFile(); + outputFileManuallySet = true; + } + outputFileButton.setLabel(outputFile.getName()); + nametagCheckbox.setChecked(settings.isRenderNameTags()); + stabilizeYaw.setChecked(settings.isStabilizeYaw()); + stabilizePitch.setChecked(settings.isStabilizePitch()); + stabilizeRoll.setChecked(settings.isStabilizeRoll()); + if (settings.getChromaKeyingColor() == null) { + chromaKeyingCheckbox.setChecked(false); + chromaKeyingColor.setColor(Colors.GREEN); + } else { + chromaKeyingCheckbox.setChecked(true); + chromaKeyingColor.setColor(settings.getChromaKeyingColor()); + } + inject360Metadata.setChecked(settings.isInject360Metadata()); + exportCommand.setText(settings.getExportCommand()); + exportArguments.setText(settings.getExportArguments()); + } + + public RenderSettings save(boolean saveOutputFile) { + return new RenderSettings( + renderMethodDropdown.getSelectedValue(), + encodingPresetDropdown.getSelectedValue(), + videoWidth.getInteger(), + videoHeight.getInteger(), + frameRateSlider.getValue() + 10, + bitRateField.getInteger() << (10 * bitRateUnit.getSelected()), + saveOutputFile ? outputFile : null, + nametagCheckbox.isChecked(), + stabilizeYaw.isChecked(), + stabilizePitch.isChecked(), + stabilizeRoll.isChecked(), + chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null, + inject360Metadata.isChecked(), + exportCommand.getText(), + exportArguments.getText(), + net.minecraft.client.gui.GuiScreen.isCtrlKeyDown() + ); + } + + private File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) { + String fileName = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date()); + return new File("replay_videos/" + fileName + "." + encodingPreset.getFileExtension()); + } + + private RenderSettings getDefaultRenderSettings() { + return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null, + true, false, false, false, null, false, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false); + } + + @Override + public void close() { + RenderSettings settings = save(false); + String json = new Gson().toJson(settings); + Configuration config = ReplayModRender.instance.getConfiguration(); + getConfigProperty(config).set(json); + config.save(); + } + + private Property getConfigProperty(Configuration configuration) { + return configuration.get("rendersettings", "settings", "{}", + "Last state of the render settings GUI. Internal use only."); + } +} diff --git a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java new file mode 100644 index 00000000..d967681a --- /dev/null +++ b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java @@ -0,0 +1,284 @@ +package com.replaymod.render.gui; + +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.rendering.VideoRenderer; +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.RenderInfo; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiCheckbox; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import eu.crushedpixel.replaymod.utils.BoundingUtils; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; +import org.lwjgl.util.ReadablePoint; + +import java.nio.ByteBuffer; + +import static net.minecraft.client.renderer.GlStateManager.bindTexture; + +public class GuiVideoRenderer extends GuiScreen { + private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg"); + + private final VideoRenderer renderer; + + public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.rendering.title"); + public final GuiPanel imagePanel = new GuiPanel(){ + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + if (previewCheckbox.isChecked()) { + renderPreview(renderer, size); + } else { + renderNoPreview(renderer, size); + } + } + }.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + public final GuiCheckbox previewCheckbox = new GuiCheckbox().setI18nLabel("replaymod.gui.rendering.preview"); + public final GuiLabel renderTime = new GuiLabel(); + public final GuiLabel remainingTime = new GuiLabel(); + public final GuiProgressBar progressBar = new GuiProgressBar(); + public final GuiPanel buttonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)); + public final GuiButton pauseButton = new GuiButton(buttonPanel).onClick(new Runnable() { + @Override + public void run() { + if (renderer.isPaused()) { + pauseButton.setI18nLabel("replaymod.gui.rendering.pause"); + renderer.setPaused(false); + } else { + pauseButton.setI18nLabel("replaymod.gui.rendering.resume"); + renderer.setPaused(true); + } + } + }).setI18nLabel("replaymod.gui.rendering.pause").setSize(150, 20); + + public final GuiButton cancelButton = new GuiButton(buttonPanel){ + boolean waitingForConfirmation; + + @Override + public boolean mouseClick(ReadablePoint position, int button) { + boolean result = super.mouseClick(position, button); + if (waitingForConfirmation && !result) { + setI18nLabel("replaymod.gui.rendering.cancel"); + waitingForConfirmation = false; + } + return result; + } + + @Override + public void onClick() { + super.onClick(); + if (!waitingForConfirmation) { + setI18nLabel("replaymod.gui.rendering.cancel.callback"); + waitingForConfirmation = true; + } else { + renderer.cancel(); + } + } + }.setI18nLabel("replaymod.gui.rendering.cancel").setSize(150, 20); + + private DynamicTexture previewTexture; + private boolean previewTextureDirty; + + { + final GuiPanel contentPanel = new GuiPanel(this).setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + size(progressBar, width, 20); + pos(title, width / 2 - width(title) / 2, 0); + pos(imagePanel, 0, y(title) + height(title) + 5); + pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - height(buttonPanel)); + pos(progressBar, width / 2 - width(progressBar) / 2, y(buttonPanel) - 5 - height(progressBar)); + pos(renderTime, 0, y(progressBar) - 2 - height(renderTime)); + pos(remainingTime, width - width(remainingTime), y(progressBar) - 2 - height(renderTime)); + pos(previewCheckbox, width / 2 - width(previewCheckbox) / 2, y(renderTime) - 10 - height(previewCheckbox)); + size(imagePanel, width, y(previewCheckbox) - 5 - y(imagePanel)); + } + }).addElements(null, title, imagePanel, previewCheckbox, renderTime, remainingTime, progressBar, buttonPanel); + setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + // Make sure we have some space around our central GUI components + pos(contentPanel, 5, 3); + size(contentPanel, width - 10, height - 10); + } + }); + setBackground(Background.DIRT); + } + + public GuiVideoRenderer(VideoRenderer renderer) { + this.renderer = renderer; + } + + //the total render time + private int renderTimeTaken = 0; + + //the time at which the Screen was last updated + private long prevTime = -1; + + //the time at which the rendering of the current frame started + private long frameStartTime = -1; + + //the amount of frames that were rendered when the time left was last updated + private int prevRenderedFrames = 0; + + //the estimated render time that is left (in seconds) + private int renderTimeLeft = 0; + + //each of the durations the rendering process took for the last 50 frames + private int[] renderTimes = new int[50]; + //the algorithm's current position in the renderTimes array + private int currentIndex = 0; + + @Override + public void draw(GuiRenderer guiRenderer, ReadableDimension size, RenderInfo renderInfo) { + long current = System.currentTimeMillis(); + + //first, update the total render time (only if rendering is not paused and has already started) + if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) { + renderTimeTaken += (current - prevTime); + } else { + //if the rendering process is paused, we have to update the frame start time to prevent huge render times + //for the currently rendered frame(s) + frameStartTime = current; + } + + //always update prevTime, so we don't get huge time jumps when unpausing the rendering process + prevTime = current; + + + //calculate estimated time left + + //if the amount of rendered frames has increased since the last update + if(prevRenderedFrames < renderer.getFramesDone()) { + //we don't include the first frame in our calculations, + //as setting up the rendering process takes a few seconds + if(prevRenderedFrames > 0) { + + //calculate the amount of frames that have been rendered since the last update + int framesRendered = renderer.getFramesDone() - prevRenderedFrames; + + //calculate the time it took to render these frames + int renderTime = (int)(current - frameStartTime); + + //calculate the average time it took for each of these frames to render + int avgRenderTime = renderTime/framesRendered; + + //add all of the average render times to the render times array + for(int i=0; i= renderTimes.length) currentIndex = 0; + } + + //the renderTimes array initially contains lots of zeros, + //so we count the amount of valid timespans + int validValues = 0; + + int totalTime = 0; + for(int i : renderTimes) { + if(i > 0) { + totalTime += i; + validValues++; + } + } + + //calculate the average render time for the previous [up to 50] frames + float averageRenderTime = validValues > 0 ? totalTime / validValues : 0; + + //calculate the remaining render time in seconds + renderTimeLeft = Math.round((averageRenderTime * (renderer.getTotalFrames() - renderer.getFramesDone())) / 1000); + } + + //set the render start time of the next frame(s) to the current timestamp + frameStartTime = current; + //update the amount of rendered frames for the last calculation + prevRenderedFrames = renderer.getFramesDone(); + } + + renderTime.setText(I18n.format("replaymod.gui.rendering.timetaken") + ": " + secToString(renderTimeTaken/1000)); + remainingTime.setText(I18n.format("replaymod.gui.rendering.timeleft") + ": " + secToString(renderTimeLeft)); + + int framesDone = renderer.getFramesDone(), framesTotal = renderer.getTotalFrames(); + progressBar.setI18nLabel("replaymod.gui.rendering.progress", framesDone, framesTotal); + progressBar.setProgress((float) framesDone / framesTotal); + + super.draw(guiRenderer, size, renderInfo); + } + + private String secToString(int seconds) { + int hours = seconds/(60*60); + int min = seconds/60 - hours*60; + int sec = seconds - ((min*60) + (hours*60*60)); + + StringBuilder builder = new StringBuilder(); + if(hours > 0) builder.append(hours).append(I18n.format("replaymod.gui.hours")); + if(min > 0 || hours > 0) builder.append(min).append(I18n.format("replaymod.gui.minutes")); + builder.append(sec).append(I18n.format("replaymod.gui.seconds")); + + return builder.toString(); + } + + private synchronized void renderPreview(GuiRenderer guiRenderer, ReadableDimension size) { + ReadableDimension videoSize = renderer.getFrameSize(); + final int videoWidth = videoSize.getWidth(); + final int videoHeight = videoSize.getHeight(); + + if (previewTexture == null) { + previewTexture = new DynamicTexture(videoWidth, videoHeight) { + @Override + public void updateDynamicTexture() { + bindTexture(getGlTextureId()); + TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false); + } + }; + } + + if (previewTextureDirty) { + previewTexture.updateDynamicTexture(); + previewTextureDirty = false; + } + + guiRenderer.bindTexture(previewTexture); + renderPreviewTexture(guiRenderer, size, videoWidth, videoHeight); + } + + private void renderNoPreview(GuiRenderer guiRenderer, ReadableDimension size) { + guiRenderer.bindTexture(NO_PREVIEW_TEXTURE); + renderPreviewTexture(guiRenderer, size, 1280, 720); + } + + private void renderPreviewTexture(GuiRenderer guiRenderer, ReadableDimension size, + int videoWidth, int videoHeight) { + Dimension dimension = BoundingUtils.fitIntoBounds(new Dimension(videoWidth, videoHeight), size); + + int width = dimension.getWidth(); + int height = dimension.getHeight(); + int x = (size.getWidth() - width) / 2; + int y = (size.getHeight() - height) / 2; + + guiRenderer.drawTexturedRect(x, y, 0, 0, width, height, videoWidth, videoHeight, videoWidth, videoHeight); + } + + public void updatePreview(RGBFrame frame) { + if (previewCheckbox.isChecked() && previewTexture != null) { + ByteBuffer buffer = frame.getByteBuffer(); + buffer.mark(); + synchronized (this) { + int[] data = previewTexture.getTextureData(); + for (int i = 0; i < data.length; i++) { + data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); + } + previewTextureDirty = true; + } + buffer.reset(); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/renderer/ChunkLoadingRenderGlobal.java b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java similarity index 61% rename from src/main/java/eu/crushedpixel/replaymod/renderer/ChunkLoadingRenderGlobal.java rename to src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java index a34e83f2..ec2fb1a6 100644 --- a/src/main/java/eu/crushedpixel/replaymod/renderer/ChunkLoadingRenderGlobal.java +++ b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.renderer; +package com.replaymod.render.hooks; import eu.crushedpixel.replaymod.utils.JailingQueue; import net.minecraft.client.renderer.RegionRenderCacheBuilder; @@ -7,9 +7,6 @@ import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator; import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; import net.minecraft.client.renderer.chunk.ChunkRenderWorker; import net.minecraft.client.renderer.chunk.RenderChunk; -import net.minecraft.client.renderer.culling.ICamera; -import net.minecraft.entity.Entity; -import net.minecraft.util.BlockPos; import java.lang.reflect.Field; import java.util.Iterator; @@ -31,42 +28,31 @@ public class ChunkLoadingRenderGlobal { int workerThreads = renderDispatcher.listThreadedWorkers.size(); BlockingQueue queueChunkUpdates = renderDispatcher.queueChunkUpdates; - workerJailingQueue = new JailingQueue(queueChunkUpdates); + workerJailingQueue = new JailingQueue<>(queueChunkUpdates); renderDispatcher.queueChunkUpdates = workerJailingQueue; ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null); element.finish(); for (int i = 0; i < workerThreads; i++) { queueChunkUpdates.add(element); } + + // Temporary workaround for dead lock, will be replaced by a new (ShaderMod compatible) mechanism later + //noinspection StatementWithEmptyBody + while (renderDispatcher.runChunkUploads(0)) {} + workerJailingQueue.jail(workerThreads); renderDispatcher.queueChunkUpdates = queueChunkUpdates; try { - Field hookField = RenderGlobal.class.getField("hook"); + Field hookField = RenderGlobal.class.getField("replayModRender_hook"); hookField.set(hooked, this); - } catch (NoSuchFieldException e) { - throw new Error(e); - } catch (IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); } } - @SuppressWarnings("unused") // Method called by ASM hook - public void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) { - // There has to be a better way to force minecraft into queueing all chunks at once - // Someone should probably find and implement it! - do { - original_setupTerrain(viewEntity, partialTicks, camera, frame++, playerSpectator); - } while (hooked.displayListEntitiesDirty); - } - - @SuppressWarnings("unused") // Method called by ASM hook - public boolean isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) { - return true; - } - - @SuppressWarnings({"unchecked", "unused"}) // Method called by ASM hook - public void updateChunks(long finishTimeNano) { + @SuppressWarnings("unchecked") + public void updateChunks() { while (renderDispatcher.runChunkUploads(0)) { hooked.displayListEntitiesDirty = true; } @@ -88,35 +74,21 @@ public class ChunkLoadingRenderGlobal { } } - @SuppressWarnings("unused") - private void original_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) { - // Method body generated by ASM - } - - @SuppressWarnings("unused") - private boolean original_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) { - // Method body generated by ASM - return false; - } - - @SuppressWarnings("unused") - private void original_updateChunks(long finishTimeNano) { - // Method body generated by ASM - } - public void uninstall() { workerJailingQueue.freeAll(); try { - Field hookField = RenderGlobal.class.getField("hook"); + Field hookField = RenderGlobal.class.getField("replayModRender_hook"); hookField.set(hooked, null); - } catch (NoSuchFieldException e) { - throw new Error(e); - } catch (IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); } } + public int nextFrameId() { + return frame++; + } + /** * Custom ChunkRenderWorker class providing access to the protected processTask method */ diff --git a/src/main/java/eu/crushedpixel/replaymod/video/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java similarity index 72% rename from src/main/java/eu/crushedpixel/replaymod/video/EntityRendererHandler.java rename to src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index 3130ae2a..1bbe9038 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -1,8 +1,8 @@ -package eu.crushedpixel.replaymod.video; +package com.replaymod.render.hooks; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.video.capturer.CaptureData; -import eu.crushedpixel.replaymod.video.capturer.WorldRenderer; +import com.replaymod.render.RenderSettings; +import com.replaymod.render.capturer.CaptureData; +import com.replaymod.render.capturer.WorldRenderer; import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.culling.ClippingHelper; @@ -16,14 +16,14 @@ public class EntityRendererHandler implements WorldRenderer { public final Minecraft mc = Minecraft.getMinecraft(); @Getter - protected final RenderOptions options; + protected final RenderSettings settings; public CaptureData data; - public EntityRendererHandler(RenderOptions options) { - this.options = options; + public EntityRendererHandler(RenderSettings settings) { + this.settings = settings; - ((IEntityRenderer) mc.entityRenderer).setHandler(this); + ((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(this); } public void withDisplaySize(int displayWidth, int displayHeight, Runnable runnable) { @@ -61,7 +61,7 @@ public class EntityRendererHandler implements WorldRenderer { @Override public void close() throws IOException { - ((IEntityRenderer) mc.entityRenderer).setHandler(null); + ((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(null); } public static final class NoCullingClippingHelper extends ClippingHelper { @@ -72,11 +72,11 @@ public class EntityRendererHandler implements WorldRenderer { } public interface GluPerspective { - void gluPerspective(float fovY, float aspect, float zNear, float zFar); + void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar); } public interface IEntityRenderer { - void setHandler(EntityRendererHandler handler); - EntityRendererHandler getHandler(); + void replayModRender_setHandler(EntityRendererHandler handler); + EntityRendererHandler replayModRender_getHandler(); } } diff --git a/src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java b/src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java new file mode 100644 index 00000000..e7b998fb --- /dev/null +++ b/src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java @@ -0,0 +1,26 @@ +package com.replaymod.render.hooks; + +import com.replaymod.core.utils.WrappedTimer; +import net.minecraft.util.Timer; + +/** + * Wrapper around the current timer that prevents the timer from advancing by itself. + */ +public class RenderReplayTimer extends WrappedTimer { + private final Timer state = new Timer(0); + + public RenderReplayTimer(Timer wrapped) { + super(wrapped); + } + + @Override + public void updateTimer() { + copy(this, state); // Save our current state + super.updateTimer(); // Update current state + copy(state, this); // Restore our old state + } + + public Timer getWrapped() { + return wrapped; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/metadata/MetadataInjector.java b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java similarity index 98% rename from src/main/java/eu/crushedpixel/replaymod/video/metadata/MetadataInjector.java rename to src/main/java/com/replaymod/render/metadata/MetadataInjector.java index bde64fd0..e8ac41ca 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/metadata/MetadataInjector.java +++ b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.metadata; +package com.replaymod.render.metadata; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.*; diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEffectRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java similarity index 90% rename from src/main/java/eu/crushedpixel/replaymod/mixin/MixinEffectRenderer.java rename to src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java index a72d7513..5ae8f2d1 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEffectRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.mixin; +package com.replaymod.render.mixin; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; -import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer; +import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; +import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.particle.EntityFX; @@ -12,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(EffectRenderer.class) -public class MixinEffectRenderer { +public abstract class MixinEffectRenderer { @Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;func_180434_a(Lnet/minecraft/client/renderer/WorldRenderer;Lnet/minecraft/entity/Entity;FFFFFF)V")) private void renderNormalParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) { @@ -27,7 +27,7 @@ public class MixinEffectRenderer { private void renderParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler(); + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) { // Align all particles towards the camera double dx = fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - view.posX; diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java new file mode 100644 index 00000000..0359b5fc --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -0,0 +1,241 @@ +package com.replaymod.render.mixin; + +import com.replaymod.render.RenderSettings; +import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; +import com.replaymod.render.capturer.StereoscopicOpenGlFrameCapturer; +import com.replaymod.render.hooks.EntityRendererHandler; +import com.replaymod.replay.camera.CameraEntity; +import eu.crushedpixel.replaymod.renderer.SpectatorRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.MovingObjectPosition; +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.ReadableColor; +import org.lwjgl.util.glu.Project; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = EntityRenderer.class) +public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective { + @Shadow + public Minecraft mc; + + private EntityRendererHandler replayModRender_handler; + + @Shadow(remap = false) + private SpectatorRenderer spectatorRenderer; + + @Override + public void replayModRender_setHandler(EntityRendererHandler handler) { + this.replayModRender_handler = handler; + } + + @Override + public EntityRendererHandler replayModRender_getHandler() { + return replayModRender_handler; + } + + @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) + private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) { + if (replayModRender_handler == null) return; + if (replayModRender_handler.getSettings().getChromaKeyingColor() != null) { + ci.cancel(); + } + } + + @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/culling/Frustum;setPosition(DDD)V")) + public void replayModRender_createNoCullingFrustum(Frustum frustum, double x, double y, double z) { + if (replayModRender_handler != null) { + frustum.clippingHelper = new EntityRendererHandler.NoCullingClippingHelper(); + } + frustum.setPosition(x, y, z); + } + + @Inject(method = "orientCamera", at = @At("HEAD")) + private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) { + if (replayModRender_handler != null) { + Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); + RenderSettings settings = replayModRender_handler.getSettings(); + if (settings.isStabilizeYaw()) { + entity.prevRotationYaw = entity.rotationYaw = 0; + } + if (settings.isStabilizePitch()) { + entity.prevRotationPitch = entity.rotationPitch = 0; + } + if (settings.isStabilizeRoll() && entity instanceof CameraEntity) { + ((CameraEntity) entity).roll = 0; + } + } + } + + private float orgYaw; + private float orgPitch; + private float orgPrevYaw; + private float orgPrevPitch; + private float orgRoll; + + @Inject(method = "setupCameraTransform", at = @At("HEAD")) + private void replayModRender_beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { + if (replayModRender_handler != null) { + Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); + orgYaw = entity.rotationYaw; + orgPitch = entity.rotationPitch; + orgPrevYaw = entity.prevRotationYaw; + orgPrevPitch = entity.prevRotationPitch; + orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0; + } + } + + @Inject(method = "setupCameraTransform", at = @At("RETURN")) + private void replayModRender_afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { + if (replayModRender_handler != null) { + Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); + entity.rotationYaw = orgYaw; + entity.rotationPitch = orgPitch; + entity.prevRotationYaw = orgPrevYaw; + entity.prevRotationPitch = orgPrevPitch; + if (entity instanceof CameraEntity) { + ((CameraEntity) entity).roll = orgRoll; + } + } + } + + @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) + private void replayModRender_renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) { + if (replayModRender_handler != null) { + if (replayModRender_handler.data instanceof CubicOpenGlFrameCapturer.Data) { + ci.cancel(); + return; // No spectator hands during 360° view, we wouldn't even know where to put it + } + Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity(); + if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof CameraEntity)) { + renderPass = replayModRender_handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0; + spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass); + ci.cancel(); // No further hand rendering needed + } + } + } + + @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V")) + private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition mop, int alwaysZero, float partialTicks) { + if (replayModRender_handler == null) { + instance.drawSelectionBox(player, mop, alwaysZero, partialTicks); + } + } + + private int orgRenderDistanceChunks; + + @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0)) + private void replayModRender_beforeRenderSky(CallbackInfo ci) { + if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) { + GameSettings settings = Minecraft.getMinecraft().gameSettings; + orgRenderDistanceChunks = settings.renderDistanceChunks; + settings.renderDistanceChunks = 5; // Set render distance to 5 so we're always rendering sky when chroma keying + } + } + + @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V")) + private void replayModRender_renderSky(RenderGlobal instance, float partialTicks, int renderPass) { + if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) { + Minecraft.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks; + ReadableColor color = replayModRender_handler.getSettings().getChromaKeyingColor(); + GlStateManager.clearColor(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1); + GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT); + } else { + instance.renderSky(partialTicks, renderPass); + } + } + + /* + * Stereoscopic Renderer + */ + + @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 0)) + private void replayModRender_setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) { + if (replayModRender_getHandler() != null) { + if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { + GlStateManager.translate(0.07, 0, 0); + } else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { + GlStateManager.translate(-0.07, 0, 0); + } + } + } + + @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 1)) + private void replayModRender_setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) { + if (replayModRender_getHandler() != null) { + if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { + GlStateManager.translate(0.1, 0, 0); + } else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { + GlStateManager.translate(-0.1, 0, 0); + } + } + } + + /* + * Cubic Renderer + */ + + @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + private void replayModRender_gluPerspective$0(float fovY, float aspect, float zNear, float zFar) { + replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + } + + @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + private void replayModRender_gluPerspective$1(float fovY, float aspect, float zNear, float zFar) { + replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + } + + @Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + private void replayModRender_gluPerspective$2(float fovY, float aspect, float zNear, float zFar) { + replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + } + + @Override + public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { + if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { + fovY = 90; + aspect = 1; + } + Project.gluPerspective(fovY, aspect, zNear, zFar); + } + + @Inject(method = "orientCamera", at = @At("HEAD")) + private void replayModRender_setupCubicFrameRotation(float partialTicks, CallbackInfo ci) { + if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { + switch ((CubicOpenGlFrameCapturer.Data) replayModRender_getHandler().data) { + case FRONT: + GlStateManager.rotate(0, 0.0F, 1.0F, 0.0F); + break; + case RIGHT: + GlStateManager.rotate(90, 0.0F, 1.0F, 0.0F); + break; + case BACK: + GlStateManager.rotate(180, 0.0F, 1.0F, 0.0F); + break; + case LEFT: + GlStateManager.rotate(-90, 0.0F, 1.0F, 0.0F); + break; + case BOTTOM: + GlStateManager.rotate(90, 1.0F, 0.0F, 0.0F); + break; + case TOP: + GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F); + break; + } + + // Minecraft goes back a little so we have to revert that + GlStateManager.translate(0.0F, 0.0F, 0.1F); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRender.java b/src/main/java/com/replaymod/render/mixin/MixinRender.java similarity index 63% rename from src/main/java/eu/crushedpixel/replaymod/mixin/MixinRender.java rename to src/main/java/com/replaymod/render/mixin/MixinRender.java index b6a1ad4c..f29678b8 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRender.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRender.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.mixin; +package com.replaymod.render.mixin; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; +import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.Render; import net.minecraft.entity.Entity; @@ -12,9 +12,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(Render.class) public abstract class MixinRender { @Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true) - private void areAllNamesHidden(Entity entity, CallbackInfoReturnable ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler(); - if (handler != null && handler.getOptions().isHideNameTags()) { + private void replayModRender_areAllNamesHidden(Entity entity, CallbackInfoReturnable ci) { + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + if (handler != null && !handler.getSettings().isRenderNameTags()) { ci.setReturnValue(false); ci.cancel(); } diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java new file mode 100644 index 00000000..34ee45e0 --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java @@ -0,0 +1,61 @@ +package com.replaymod.render.mixin; + +import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; +import net.minecraft.client.renderer.chunk.RenderChunk; +import net.minecraft.client.renderer.culling.ICamera; +import net.minecraft.entity.Entity; +import net.minecraft.util.BlockPos; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(RenderGlobal.class) +public abstract class MixinRenderGlobal { + public ChunkLoadingRenderGlobal replayModRender_hook; + private boolean replayModRender_passThroughSetupTerrain; + + @Shadow + public boolean displayListEntitiesDirty; + + @Shadow + public ChunkRenderDispatcher renderDispatcher; + + @Shadow + public abstract void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, + int frameCount, boolean playerSpectator); + + @Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true) + public void replayModRender_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, + int frameCount, boolean playerSpectator, CallbackInfo ci) { + if (replayModRender_hook != null && !replayModRender_passThroughSetupTerrain) { + replayModRender_passThroughSetupTerrain = true; + + do { + setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator); + } while (displayListEntitiesDirty); + + replayModRender_passThroughSetupTerrain = false; + ci.cancel(); + } + } + + @Inject(method = "isPositionInRenderChunk", at = @At("HEAD"), cancellable = true) + public void replayModRender_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk, CallbackInfoReturnable ci) { + if (replayModRender_hook != null) { + ci.setReturnValue(true); + } + } + + @Inject(method = "updateChunks", at = @At("HEAD"), cancellable = true) + public void replayModRender_updateChunks(long finishTimeNano, CallbackInfo ci) { + if (replayModRender_hook != null) { + replayModRender_hook.updateChunks(); + ci.cancel(); + } + } +} diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java new file mode 100644 index 00000000..cbf3dfb2 --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java @@ -0,0 +1,32 @@ +package com.replaymod.render.mixin; + +import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; +import com.replaymod.render.hooks.EntityRendererHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.RenderManager; +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.CallbackInfoReturnable; + +@Mixin(RenderManager.class) +public abstract class MixinRenderManager { + @Shadow + private float playerViewX; + + @Shadow + private float playerViewY; + + @Inject(method = "doRenderEntity", at = @At("HEAD")) + private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) { + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) { + double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)); + double yaw = -Math.atan2(dx, dz); + playerViewX = (float) Math.toDegrees(pitch); + playerViewY = (float) Math.toDegrees(yaw); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRendererLivingEntity.java b/src/main/java/com/replaymod/render/mixin/MixinRendererLivingEntity.java similarity index 75% rename from src/main/java/eu/crushedpixel/replaymod/mixin/MixinRendererLivingEntity.java rename to src/main/java/com/replaymod/render/mixin/MixinRendererLivingEntity.java index 2dd14743..7d87b1b7 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRendererLivingEntity.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRendererLivingEntity.java @@ -1,6 +1,6 @@ -package eu.crushedpixel.replaymod.mixin; +package com.replaymod.render.mixin; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; +import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.RendererLivingEntity; import net.minecraft.entity.EntityLivingBase; @@ -14,9 +14,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(RendererLivingEntity.class) public abstract class MixinRendererLivingEntity { @Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true) - private void areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler(); - if (handler != null && handler.getOptions().isHideNameTags()) { + private void replayModRender_areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable ci) { + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + if (handler != null && !handler.getSettings().isRenderNameTags()) { ci.setReturnValue(false); //this calls the cancel method } @@ -28,7 +28,7 @@ public abstract class MixinRendererLivingEntity { } @Redirect(method = "renderModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/EntityLivingBase;isInvisibleToPlayer(Lnet/minecraft/entity/player/EntityPlayer;)Z")) - private boolean shouldInvisibleNotBeRendered(EntityLivingBase entity, EntityPlayer thePlayer) { + private boolean replayModRender_shouldInvisibleNotBeRendered(EntityLivingBase entity, EntityPlayer thePlayer) { // TODO // if(ReplaySettings.ReplayOptions.renderInvisible.getValue() == Boolean.TRUE|| !ReplayHandler.isInReplay()) { // return entity.isInvisibleToPlayer(thePlayer); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/processor/AbstractFrameProcessor.java b/src/main/java/com/replaymod/render/processor/AbstractFrameProcessor.java similarity index 55% rename from src/main/java/eu/crushedpixel/replaymod/video/processor/AbstractFrameProcessor.java rename to src/main/java/com/replaymod/render/processor/AbstractFrameProcessor.java index d340a53c..6c9e26c0 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/processor/AbstractFrameProcessor.java +++ b/src/main/java/com/replaymod/render/processor/AbstractFrameProcessor.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.video.processor; +package com.replaymod.render.processor; -import eu.crushedpixel.replaymod.video.rendering.Frame; -import eu.crushedpixel.replaymod.video.rendering.FrameProcessor; +import com.replaymod.render.rendering.Frame; +import com.replaymod.render.rendering.FrameProcessor; import java.io.IOException; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/processor/CubicToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java similarity index 83% rename from src/main/java/eu/crushedpixel/replaymod/video/processor/CubicToRGBProcessor.java rename to src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java index 929c150f..223ea375 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/processor/CubicToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java @@ -1,13 +1,13 @@ -package eu.crushedpixel.replaymod.video.processor; +package com.replaymod.render.processor; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; +import com.replaymod.render.frame.CubicOpenGlFrame; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.utils.ByteBufferPool; import org.lwjgl.util.Dimension; import java.nio.ByteBuffer; -import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG; +import static com.replaymod.render.utils.Utils.openGlBytesToRBG; public class CubicToRGBProcessor extends AbstractFrameProcessor { @Override diff --git a/src/main/java/eu/crushedpixel/replaymod/video/processor/EquirectangularToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java similarity index 95% rename from src/main/java/eu/crushedpixel/replaymod/video/processor/EquirectangularToRGBProcessor.java rename to src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java index cbf10b02..4d19bc11 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/processor/EquirectangularToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java @@ -1,8 +1,8 @@ -package eu.crushedpixel.replaymod.video.processor; +package com.replaymod.render.processor; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; +import com.replaymod.render.frame.CubicOpenGlFrame; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.utils.ByteBufferPool; import org.apache.commons.lang3.Validate; import org.lwjgl.util.Dimension; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/processor/OpenGlToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java similarity index 87% rename from src/main/java/eu/crushedpixel/replaymod/video/processor/OpenGlToRGBProcessor.java rename to src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java index cf9ddf86..0cd54e5f 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/processor/OpenGlToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java @@ -1,7 +1,7 @@ -package eu.crushedpixel.replaymod.video.processor; +package com.replaymod.render.processor; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.frame.RGBFrame; import org.lwjgl.util.ReadableDimension; import java.nio.ByteBuffer; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/processor/StereoscopicToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java similarity index 76% rename from src/main/java/eu/crushedpixel/replaymod/video/processor/StereoscopicToRGBProcessor.java rename to src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java index 3b0017c1..02ed1de3 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/processor/StereoscopicToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java @@ -1,14 +1,14 @@ -package eu.crushedpixel.replaymod.video.processor; +package com.replaymod.render.processor; -import eu.crushedpixel.replaymod.utils.ByteBufferPool; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; -import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.utils.ByteBufferPool; import org.lwjgl.util.Dimension; import org.lwjgl.util.ReadableDimension; import java.nio.ByteBuffer; -import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG; +import static com.replaymod.render.utils.Utils.openGlBytesToRBG; public class StereoscopicToRGBProcessor extends AbstractFrameProcessor { @Override diff --git a/src/main/java/com/replaymod/render/rendering/Frame.java b/src/main/java/com/replaymod/render/rendering/Frame.java new file mode 100644 index 00000000..17de1ba5 --- /dev/null +++ b/src/main/java/com/replaymod/render/rendering/Frame.java @@ -0,0 +1,5 @@ +package com.replaymod.render.rendering; + +public interface Frame { + int getFrameId(); +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameCapturer.java b/src/main/java/com/replaymod/render/rendering/FrameCapturer.java similarity index 73% rename from src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameCapturer.java rename to src/main/java/com/replaymod/render/rendering/FrameCapturer.java index 0bb9bd20..a2826e7e 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameCapturer.java +++ b/src/main/java/com/replaymod/render/rendering/FrameCapturer.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.rendering; +package com.replaymod.render.rendering; import java.io.Closeable; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameConsumer.java b/src/main/java/com/replaymod/render/rendering/FrameConsumer.java similarity index 71% rename from src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameConsumer.java rename to src/main/java/com/replaymod/render/rendering/FrameConsumer.java index 966dda2d..e2f0fd07 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameConsumer.java +++ b/src/main/java/com/replaymod/render/rendering/FrameConsumer.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.rendering; +package com.replaymod.render.rendering; import java.io.Closeable; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameProcessor.java b/src/main/java/com/replaymod/render/rendering/FrameProcessor.java similarity index 73% rename from src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameProcessor.java rename to src/main/java/com/replaymod/render/rendering/FrameProcessor.java index 4f3c5192..5786fd09 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/FrameProcessor.java +++ b/src/main/java/com/replaymod/render/rendering/FrameProcessor.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.rendering; +package com.replaymod.render.rendering; import java.io.Closeable; diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipeline.java b/src/main/java/com/replaymod/render/rendering/Pipeline.java similarity index 98% rename from src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipeline.java rename to src/main/java/com/replaymod/render/rendering/Pipeline.java index e50e8606..5b771aac 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipeline.java +++ b/src/main/java/com/replaymod/render/rendering/Pipeline.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.video.rendering; +package com.replaymod.render.rendering; import lombok.RequiredArgsConstructor; import net.minecraft.client.Minecraft; diff --git a/src/main/java/com/replaymod/render/rendering/Pipelines.java b/src/main/java/com/replaymod/render/rendering/Pipelines.java new file mode 100644 index 00000000..fed5ca42 --- /dev/null +++ b/src/main/java/com/replaymod/render/rendering/Pipelines.java @@ -0,0 +1,76 @@ +package com.replaymod.render.rendering; + +import com.replaymod.render.RenderSettings; +import com.replaymod.render.capturer.*; +import com.replaymod.render.frame.CubicOpenGlFrame; +import com.replaymod.render.frame.OpenGlFrame; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.hooks.EntityRendererHandler; +import com.replaymod.render.processor.CubicToRGBProcessor; +import com.replaymod.render.processor.EquirectangularToRGBProcessor; +import com.replaymod.render.processor.OpenGlToRGBProcessor; +import com.replaymod.render.processor.StereoscopicToRGBProcessor; +import com.replaymod.render.utils.PixelBufferObject; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class Pipelines { + public static Pipeline newPipeline(RenderSettings.RenderMethod method, RenderInfo renderInfo, FrameConsumer consumer) { + switch (method) { + case DEFAULT: + return newDefaultPipeline(renderInfo, consumer); + case STEREOSCOPIC: + return newStereoscopicPipeline(renderInfo, consumer); + case CUBIC: + return newCubicPipeline(renderInfo, consumer); + case EQUIRECTANGULAR: + return newEquirectangularPipeline(renderInfo, consumer); + } + throw new UnsupportedOperationException("Unknown method: " + method); + } + + public static Pipeline newDefaultPipeline(RenderInfo renderInfo, FrameConsumer consumer) { + RenderSettings settings = renderInfo.getRenderSettings(); + FrameCapturer capturer; + if (PixelBufferObject.SUPPORTED) { + capturer = new SimplePboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo); + } else { + capturer = new SimpleOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo); + } + return new Pipeline<>(capturer, new OpenGlToRGBProcessor(), consumer); + } + + public static Pipeline newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer consumer) { + RenderSettings settings = renderInfo.getRenderSettings(); + FrameCapturer capturer; + if (PixelBufferObject.SUPPORTED) { + capturer = new StereoscopicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo); + } else { + capturer = new StereoscopicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo); + } + return new Pipeline<>(capturer, new StereoscopicToRGBProcessor(), consumer); + } + + public static Pipeline newCubicPipeline(RenderInfo renderInfo, FrameConsumer consumer) { + RenderSettings settings = renderInfo.getRenderSettings(); + FrameCapturer capturer; + if (PixelBufferObject.SUPPORTED) { + capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4); + } else { + capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4); + } + return new Pipeline<>(capturer, new CubicToRGBProcessor(), consumer); + } + + public static Pipeline newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer consumer) { + RenderSettings settings = renderInfo.getRenderSettings(); + FrameCapturer capturer; + if (PixelBufferObject.SUPPORTED) { + capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4); + } else { + capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4); + } + return new Pipeline<>(capturer, new EquirectangularToRGBProcessor(settings.getVideoWidth() / 4), consumer); + } +} diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java new file mode 100644 index 00000000..28e04db6 --- /dev/null +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -0,0 +1,308 @@ +package com.replaymod.render.rendering; + +import com.google.common.base.Optional; +import com.replaymod.core.ReplayMod; +import com.replaymod.pathing.path.Keyframe; +import com.replaymod.pathing.path.Path; +import com.replaymod.pathing.path.Timeline; +import com.replaymod.pathing.properties.TimestampProperty; +import com.replaymod.render.RenderSettings; +import com.replaymod.render.VideoWriter; +import com.replaymod.render.capturer.RenderInfo; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.gui.GuiVideoRenderer; +import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; +import com.replaymod.render.hooks.RenderReplayTimer; +import com.replaymod.render.metadata.MetadataInjector; +import com.replaymod.replay.ReplayHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.SoundCategory; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.util.Timer; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +import java.io.IOException; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Map; +import java.util.concurrent.FutureTask; + +import static com.google.common.collect.Iterables.getLast; +import static net.minecraft.client.renderer.GlStateManager.*; +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; + +public class VideoRenderer implements RenderInfo { + private final Minecraft mc = Minecraft.getMinecraft(); + private final RenderSettings settings; + private final ReplayHandler replayHandler; + private final Timeline timeline; + private final Pipeline renderingPipeline; + private final VideoWriter videoWriter; + + private int fps; + private boolean mouseWasGrabbed; + private boolean debugInfoWasShown; + private Map originalSoundLevels; + + private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal; + + private int framesDone; + private int totalFrames; + + private final GuiVideoRenderer gui; + private boolean paused; + private boolean cancelled; + + public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException { + this.settings = settings; + this.replayHandler = replayHandler; + this.timeline = timeline; + this.gui = new GuiVideoRenderer(this); + this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this, + videoWriter = new VideoWriter(settings) { + @Override + public void consume(RGBFrame frame) { + gui.updatePreview(frame); + super.consume(frame); + } + }); + } + + /** + * Render this video. + * @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed) + */ + public boolean renderVideo() { + setup(); + + // Because this might take some time to prepare we'll render the GUI at least once to not confuse the user + drawGui(); + + Timer timer = mc.timer; + + // Play up to one second before starting to render + // This is necessary in order to ensure that all entities have at least two position packets + // and their first position in the recording is correct. + // Note that it is impossible to also get the interpolation between their latest position + // and the one in the recording correct as there's no reliable way to tell when the server ticks + // or when we should be done with the interpolation of the entity + Optional optionalVideoStartTime = timeline.getValue(TimestampProperty.PROPERTY, 0); + if (optionalVideoStartTime.isPresent()) { + int videoStart = optionalVideoStartTime.get(); + + if (videoStart > 1000) { + int replayTime = videoStart - 1000; + timer.elapsedPartialTicks = timer.renderPartialTicks = 0; + timer.timerSpeed = 1; + while (replayTime < videoStart) { + timer.elapsedTicks = 1; + replayTime += 50; + replayHandler.getReplaySender().sendPacketsTill(replayTime); + tick(); + } + } + } + + mc.renderGlobal.renderEntitiesStartupCounter = 0; + + renderingPipeline.run(); + + if (settings.isInject360Metadata()) { + MetadataInjector.inject360Metadata(settings.getOutputFile()); + } + + finish(); + return !cancelled; + } + + @Override + public float updateForNextFrame() { + if (!settings.isHighPerformance() || framesDone % fps == 0) { + drawGui(); + } + + timeline.applyToGame(getVideoTime(), replayHandler); + + int elapsedTicks = mc.timer.elapsedTicks; + while (elapsedTicks-- > 0) { + tick(); + } + + framesDone++; + return mc.timer.renderPartialTicks; + } + + @Override + public RenderSettings getRenderSettings() { + return settings; + } + + private void setup() { + replayHandler.getReplaySender().setSyncModeAndWait(); + + if (!OpenGlHelper.isFramebufferEnabled()) { + Display.setResizable(false); + } + if (mc.gameSettings.showDebugInfo) { + debugInfoWasShown = true; + mc.gameSettings.showDebugInfo = false; + } + if (Mouse.isGrabbed()) { + mouseWasGrabbed = true; + } + Mouse.setGrabbed(false); + mc.timer = new RenderReplayTimer(mc.timer); + mc.timer.timerSpeed = 1; + + // Mute all sounds except GUI sounds (buttons, etc.) + Map mutedSounds = new EnumMap<>(SoundCategory.class); + for (SoundCategory category : SoundCategory.values()) { + mutedSounds.put(category, 0f); + } + originalSoundLevels = mc.gameSettings.mapSoundLevels; + mutedSounds.put(SoundCategory.MASTER, (Float) originalSoundLevels.get(SoundCategory.MASTER)); + mc.gameSettings.mapSoundLevels = mutedSounds; + + fps = settings.getFramesPerSecond(); + + long duration = 0; + for (Path path : timeline.getPaths()) { + if (!path.isActive()) continue; + + // Prepare path interpolations + path.updateAll(); + // Find end time + Collection keyframes = path.getKeyframes(); + if (keyframes.size() > 0) { + duration = Math.max(duration, getLast(keyframes).getTime()); + } + } + + totalFrames = (int) (duration*fps/1000); + + ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight()); + + chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal); + } + + private void finish() { + replayHandler.getReplaySender().setAsyncMode(true); + + if (!OpenGlHelper.isFramebufferEnabled()) { + Display.setResizable(true); + } + mc.gameSettings.showDebugInfo = debugInfoWasShown; + if (mouseWasGrabbed) { + mc.mouseHelper.grabMouseCursor(); + } + mc.timer = ((RenderReplayTimer) mc.timer).getWrapped(); + mc.gameSettings.mapSoundLevels = originalSoundLevels; + mc.displayGuiScreen(null); + if (chunkLoadingRenderGlobal != null) { + chunkLoadingRenderGlobal.uninstall(); + } + + ReplayMod.soundHandler.playRenderSuccessSound(); + + mc.displayGuiScreen(null); + } + + private void tick() { + synchronized (mc.scheduledTasks) { + while (!mc.scheduledTasks.isEmpty()) { + ((FutureTask) mc.scheduledTasks.poll()).run(); + } + } + + mc.currentScreen = gui.toMinecraft(); + try { + mc.runTick(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void drawGui() { + do { + pushMatrix(); + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + mc.getFramebuffer().bindFramebuffer(true); + mc.entityRenderer.setupOverlayRendering(); + + try { + gui.toMinecraft().handleInput(); + } catch (IOException e) { + // That's a strange exception from this kind of method O_o + // It isn't actually thrown here, so we'll deal with it the easy way + throw new RuntimeException(e); + } + + ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; + int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; + gui.toMinecraft().drawScreen(mouseX, mouseY, 0); + + mc.getFramebuffer().unbindFramebuffer(); + popMatrix(); + pushMatrix(); + mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + popMatrix(); + + + // if not in high performance mode, update the gui size if screen size changed + // otherwise just swap the progress gui to screen + if (settings.isHighPerformance()) { + Display.update(); + } else { + mc.updateDisplay(); + } + if (Mouse.isGrabbed()) { + Mouse.setGrabbed(false); + } + if (paused) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + } while (paused); + } + + public int getFramesDone() { + return framesDone; + } + + @Override + public ReadableDimension getFrameSize() { + return new Dimension(settings.getVideoWidth(), settings.getVideoHeight()); + } + + public int getTotalFrames() { + return totalFrames; + } + + public int getVideoTime() { return framesDone * 1000 / fps; } + + public void setPaused(boolean paused) { + this.paused = paused; + } + + public boolean isPaused() { + return paused; + } + + public void cancel() { + videoWriter.abort(); + this.cancelled = true; + renderingPipeline.cancel(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ByteBufferPool.java b/src/main/java/com/replaymod/render/utils/ByteBufferPool.java similarity index 89% rename from src/main/java/eu/crushedpixel/replaymod/utils/ByteBufferPool.java rename to src/main/java/com/replaymod/render/utils/ByteBufferPool.java index e5560407..a9cf04cc 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ByteBufferPool.java +++ b/src/main/java/com/replaymod/render/utils/ByteBufferPool.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.utils; +package com.replaymod.render.utils; import com.google.common.collect.Maps; import org.lwjgl.BufferUtils; @@ -40,9 +40,9 @@ public class ByteBufferPool { int size = buffer.capacity(); List> available = bufferPool.get(size); if (available == null) { - available = new LinkedList>(); + available = new LinkedList<>(); bufferPool.put(size, available); } - available.add(new SoftReference(buffer)); + available.add(new SoftReference<>(buffer)); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java similarity index 92% rename from src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java rename to src/main/java/com/replaymod/render/utils/PixelBufferObject.java index 2b64b916..47ae30d7 100644 --- a/src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java +++ b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java @@ -1,7 +1,6 @@ -package eu.crushedpixel.replaymod.opengl; +package com.replaymod.render.utils; -import eu.crushedpixel.replaymod.utils.Api; -import eu.crushedpixel.replaymod.utils.Objects; +import com.google.common.base.Objects; import lombok.RequiredArgsConstructor; import net.minecraft.client.Minecraft; import org.apache.logging.log4j.LogManager; @@ -15,7 +14,6 @@ import static org.lwjgl.opengl.ARBPixelBufferObject.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL21.GL_PIXEL_PACK_BUFFER; -@Api public class PixelBufferObject { @RequiredArgsConstructor public enum Usage { @@ -29,8 +27,8 @@ public class PixelBufferObject { public static final boolean SUPPORTED = GLContext.getCapabilities().GL_ARB_pixel_buffer_object || GLContext.getCapabilities().OpenGL15; private static final boolean arb = !GLContext.getCapabilities().OpenGL15; - private static ThreadLocal bound = new ThreadLocal(); - private static ThreadLocal mapped = new ThreadLocal(); + private static ThreadLocal bound = new ThreadLocal<>(); + private static ThreadLocal mapped = new ThreadLocal<>(); private final long size; private long handle; @@ -52,7 +50,6 @@ public class PixelBufferObject { } } - @Api private int getHandle() { if (handle == -1) { throw new IllegalStateException("PBO not allocated."); @@ -60,7 +57,6 @@ public class PixelBufferObject { return (int) handle; } - @Api public void bind() { if (arb) { ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle()); @@ -70,7 +66,6 @@ public class PixelBufferObject { bound.set(getHandle()); } - @Api public void unbind() { checkBound(); if (arb) { @@ -82,18 +77,17 @@ public class PixelBufferObject { } private void checkBound() { - if (!Objects.equals(getHandle(), bound.get())) { + if (!Objects.equal(getHandle(), bound.get())) { throw new IllegalStateException("Buffer not bound."); } } private void checkNotMapped() { - if (Objects.equals(getHandle(), mapped.get())) { + if (Objects.equal(getHandle(), mapped.get())) { throw new IllegalStateException("Buffer already mapped."); } } - @Api public ByteBuffer mapReadOnly() { checkBound(); checkNotMapped(); @@ -110,7 +104,6 @@ public class PixelBufferObject { return buffer; } - @Api public ByteBuffer mapWriteOnly() { checkBound(); checkNotMapped(); @@ -127,7 +120,6 @@ public class PixelBufferObject { return buffer; } - @Api public ByteBuffer mapReadWrite() { checkBound(); checkNotMapped(); @@ -144,10 +136,9 @@ public class PixelBufferObject { return buffer; } - @Api public void unmap() { checkBound(); - if (!Objects.equals(mapped.get(), getHandle())) { + if (!Objects.equal(mapped.get(), getHandle())) { throw new IllegalStateException("Buffer not mapped."); } if (arb) { @@ -158,7 +149,6 @@ public class PixelBufferObject { mapped.set(0); } - @Api public void delete() { if (handle != -1) { if (arb) { diff --git a/src/main/java/com/replaymod/render/utils/Utils.java b/src/main/java/com/replaymod/render/utils/Utils.java new file mode 100644 index 00000000..aac54ccf --- /dev/null +++ b/src/main/java/com/replaymod/render/utils/Utils.java @@ -0,0 +1,26 @@ +package com.replaymod.render.utils; + +import java.nio.ByteBuffer; + +public class Utils { + /** + * Copies the rgb image (flipped vertically) to the specified position in the target buffer + * @param buffer Source image + * @param bufferWidth Source image width + * @param xOffset X offset in target image + * @param yOffset Y offset in target image + * @param to Target image + * @param width Target image width + */ + public static void openGlBytesToRBG(ByteBuffer buffer, int bufferWidth, int xOffset, int yOffset, ByteBuffer to, int width) { + byte[] rowBuf = new byte[bufferWidth * 3]; + // Copy image flipped vertically to target buffer + int rows = buffer.remaining() / 3 / bufferWidth; + for (int i = 0; i < rows; i++) { + buffer.get(rowBuf); + to.position(((yOffset + rows - i - 1) * width + xOffset) * 3); + to.put(rowBuf); + } + to.rewind(); + } +} diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java index 8408ba6a..f9cec7e5 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java @@ -15,6 +15,7 @@ import com.replaymod.pathing.path.Timeline; import com.replaymod.pathing.player.RealtimeTimelinePlayer; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.TimestampProperty; +import com.replaymod.render.gui.GuiRenderSettings; import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replay.gui.overlay.GuiReplayOverlay; @@ -28,6 +29,7 @@ import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; +import net.minecraftforge.fml.common.Loader; import org.apache.commons.lang3.tuple.Triple; import org.lwjgl.input.Keyboard; import org.lwjgl.util.Dimension; @@ -48,6 +50,14 @@ public class GuiPathing { public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20) .setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); + public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() { + @Override + public void run() { + preparePathsForPlayback(); + new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display(); + } + }).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0); + public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton().setSize(20, 20) .setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @@ -117,9 +127,15 @@ public class GuiPathing { }).addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel); public final GuiPanel panel = new GuiPanel() - .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)) - .addElements(new HorizontalLayout.Data(0.5), playPauseButton, positionKeyframeButton, timeKeyframeButton, - timelinePanel); + .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)); + + { + panel.addElements(new HorizontalLayout.Data(0.5), playPauseButton); + if (Loader.isModLoaded("replaymod-render")) { + panel.addElements(new HorizontalLayout.Data(0.5), renderButton); + } + panel.addElements(new HorizontalLayout.Data(0.5), positionKeyframeButton, timeKeyframeButton, timelinePanel); + } /** * IGuiClickable dummy component that is inserted at a high level. @@ -187,22 +203,8 @@ public class GuiPathing { } else { Timeline timeline = mod.getCurrentTimeline(); Path timePath = timeline.getPaths().get(TIME_PATH); - Path positionPath = timeline.getPaths().get(POSITION_PATH); - // TODO Change interpolator via Change class - AbstractInterpolator interpolator = new LinearInterpolator(); - interpolator.registerProperty(TimestampProperty.PROPERTY); - for (PathSegment segment : timePath.getSegments()) { - segment.setInterpolator(interpolator); - } - interpolator = new CubicSplineInterpolator(); - interpolator.registerProperty(CameraProperties.POSITION); - interpolator.registerProperty(CameraProperties.ROTATION); - for (PathSegment segment : positionPath.getSegments()) { - segment.setInterpolator(interpolator); - } - timePath.updateAll(); - positionPath.updateAll(); + preparePathsForPlayback(); timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)); player.start(timeline); @@ -298,6 +300,27 @@ public class GuiPathing { }); } + private void preparePathsForPlayback() { + Timeline timeline = mod.getCurrentTimeline(); + Path timePath = timeline.getPaths().get(TIME_PATH); + Path positionPath = timeline.getPaths().get(POSITION_PATH); + + // TODO Change interpolator via Change class + AbstractInterpolator interpolator = new LinearInterpolator(); + interpolator.registerProperty(TimestampProperty.PROPERTY); + for (PathSegment segment : timePath.getSegments()) { + segment.setInterpolator(interpolator); + } + interpolator = new CubicSplineInterpolator(); + interpolator.registerProperty(CameraProperties.POSITION); + interpolator.registerProperty(CameraProperties.ROTATION); + for (PathSegment segment : positionPath.getSegments()) { + segment.setInterpolator(interpolator); + } + timePath.updateAll(); + positionPath.updateAll(); + } + public void zoomTimeline(double factor) { scrollbar.setZoom(scrollbar.getZoom() * factor); } diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/ForceChunkLoadingCT.java b/src/main/java/eu/crushedpixel/replaymod/coremod/ForceChunkLoadingCT.java deleted file mode 100644 index 6a63b88b..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/coremod/ForceChunkLoadingCT.java +++ /dev/null @@ -1,255 +0,0 @@ -package eu.crushedpixel.replaymod.coremod; - -import net.minecraft.launchwrapper.IClassTransformer; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.tree.*; - -import static org.objectweb.asm.Opcodes.*; - -/** - * Transforms the RenderGlobal class: - * - Adds a new field 'ChunkLoadingRenderGlobal hook' - * - Moves code of 'setupTerrain' into 'setupTerrain$Original' - * - Creates 'setupTerrain' method (see {@link #createSetupTerrain(ClassNode, MethodNode)} ()} - * - Moves code of 'isPositionInRenderChunk' into 'isPositionInRenderChunk$Original' - * - Creates 'isPositionInRenderChunk' method (see {@link #createIsPositionInRenderChunk(ClassNode, MethodNode)} ()} - * - Moves code of 'updateChunks' into 'updateChunk$Original' - * - Creates 'updateChunks' method (see {@link #createUpdateChunks(ClassNode, MethodNode)} ()} - * - * Transforms the ChunkLoadingRenderGlobal class: - * - Method 'original_setupTerrain' calls 'setupTerrain$Original' - * - Method 'original_isPositionInRenderChunk' calls 'isPositionInRenderChunk$Original' - * - Method 'original_updateChunks' calls 'updateChunks$Original' - */ -public class ForceChunkLoadingCT implements IClassTransformer { - - private static final String HOOK = "eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal"; - private static final String HOOK_JVM = HOOK.replace('.', '/'); - private static final String HOOK_TYPE = "L" + HOOK_JVM + ";"; - private static final String RENDER_GLOBAL = "net.minecraft.client.renderer.RenderGlobal"; - private static final String RENDER_GLOBAL_JVM = RENDER_GLOBAL.replace('.', '/'); - private static final String RENDER_GLOBAL_TYPE = "L" + RENDER_GLOBAL_JVM + ";"; - - @Override - public byte[] transform(String name, String transformedName, byte[] bytes) { - if (RENDER_GLOBAL.equals(transformedName)) { - if (name.equals(transformedName)) { - return transformRenderGlobal(bytes, "setupTerrain", "(Lnet/minecraft/entity/Entity;DLnet/minecraft/client/renderer/culling/ICamera;IZ)V", - "isPositionInRenderChunk", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/client/renderer/chunk/RenderChunk;)Z", - "updateChunks", "(J)V"); - } else { - return transformRenderGlobal(bytes, "a", "(Lwv;DLcox;IZ)V", - "a", "(Ldt;Lcop;)Z", - "a", "(J)V"); - } - } else if (HOOK.equals(transformedName)) { - return transformHook(bytes); - } - return bytes; - } - - private byte[] transformHook(byte[] bytes) { - ClassReader classReader = new ClassReader(bytes); - ClassNode classNode = new ClassNode(); - classReader.accept(classNode, 0); - - // Find methods - for (MethodNode m : classNode.methods) { - if ("original_setupTerrain".equals(m.name)) { - m.instructions.clear(); - InsnList iter = m.instructions; - iter.add(new VarInsnNode(ALOAD, 0)); // this - iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE)); - iter.add(new VarInsnNode(ALOAD, 1)); // viewEntity - iter.add(new VarInsnNode(DLOAD, 2)); // partialTicks - iter.add(new VarInsnNode(ALOAD, 4)); // camera - iter.add(new VarInsnNode(ILOAD, 5)); // frameCount - iter.add(new VarInsnNode(ILOAD, 6)); // playerSpectator - iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "setupTerrain$Original", m.desc, false)); - iter.add(new InsnNode(RETURN)); - } - if ("original_isPositionInRenderChunk".equals(m.name)) { - m.instructions.clear(); - InsnList iter = m.instructions; - iter.add(new VarInsnNode(ALOAD, 0)); // this - iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE)); - iter.add(new VarInsnNode(ALOAD, 1)); // pos - iter.add(new VarInsnNode(ALOAD, 2)); // chunk - iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "isPositionInRenderChunk$Original", m.desc, false)); - iter.add(new InsnNode(IRETURN)); - } - if ("original_updateChunks".equals(m.name)) { - m.instructions.clear(); - InsnList iter = m.instructions; - iter.add(new VarInsnNode(ALOAD, 0)); // this - iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE)); - iter.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano - iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "updateChunks$Original", m.desc, false)); - iter.add(new InsnNode(RETURN)); - } - } - - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); - classNode.accept(classWriter); - return classWriter.toByteArray(); - } - - private byte[] transformRenderGlobal(byte[] bytes, String name_setupTerrain, String desc_setupTerrain, - String name_isPositionInRenderChunk, String desc_isPositionInRenderChunk, - String name_updateChunks, String desc_updateChunks) { - ClassReader classReader = new ClassReader(bytes); - ClassNode classNode = new ClassNode(); - classReader.accept(classNode, 0); - - // Add field - classNode.visitField(ACC_PUBLIC, "hook", HOOK_TYPE, null, null); - - // Find methods - MethodNode setupTerrain = null; - MethodNode isPositionInRenderChunk = null; - MethodNode updateChunks = null; - for (MethodNode m : classNode.methods) { - if (name_setupTerrain.equals(m.name) && desc_setupTerrain.equals(m.desc)) { - setupTerrain = m; - } - if (name_isPositionInRenderChunk.equals(m.name) && desc_isPositionInRenderChunk.equals(m.desc)) { - isPositionInRenderChunk = m; - } - if (name_updateChunks.equals(m.name) && desc_updateChunks.equals(m.desc)) { - updateChunks = m; - } - } - if (setupTerrain == null) { - throw new NoSuchMethodError("setupTerrain"); - } - if (isPositionInRenderChunk == null) { - throw new NoSuchMethodError("isPositionInRenderChunk"); - } - if (updateChunks == null) { - throw new NoSuchMethodError("updateChunks"); - } - - // Generate new methods - classNode.methods.add(createSetupTerrain(classNode, setupTerrain)); - classNode.methods.add(createIsPositionInRenderChunk(classNode, isPositionInRenderChunk)); - classNode.methods.add(createUpdateChunks(classNode, updateChunks)); - - // Rename original methods - setupTerrain.name = "setupTerrain$Original"; - isPositionInRenderChunk.name = "isPositionInRenderChunk$Original"; - updateChunks.name = "updateChunks$Original"; - - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); - classNode.accept(classWriter); - return classWriter.toByteArray(); - } - - /** - * public void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) { - * if (this.hook == null) { - * setupTerrain$Original(viewEntity, partialTicks, camera, frameCount, playerSpectator); - * return; - * } - * hook.setupTerrain(viewEntity, partialTicks, camera, frameCount, playerSpectator); - * } - */ - private MethodNode createSetupTerrain(ClassNode classNode, MethodNode org) { - MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]); - InsnList list = method.instructions; - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - LabelNode labelNotNull = new LabelNode(); - list.add(new JumpInsnNode(IFNONNULL, labelNotNull)); - - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new VarInsnNode(ALOAD, 1)); // viewEntity - list.add(new VarInsnNode(DLOAD, 2)); // partialTicks - list.add(new VarInsnNode(ALOAD, 4)); // camera - list.add(new VarInsnNode(ILOAD, 5)); // frameCount - list.add(new VarInsnNode(ILOAD, 6)); // playerSpectator - list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "setupTerrain$Original", org.desc, false)); - list.add(new InsnNode(RETURN)); - - list.add(labelNotNull); - list.add(new FrameNode(F_SAME, 0, null, 0, null)); - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - list.add(new VarInsnNode(ALOAD, 1)); // viewEntity - list.add(new VarInsnNode(DLOAD, 2)); // partialTicks - list.add(new VarInsnNode(ALOAD, 4)); // camera - list.add(new VarInsnNode(ILOAD, 5)); // frameCount - list.add(new VarInsnNode(ILOAD, 6)); // playerSpectator - list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "setupTerrain", org.desc, false)); - list.add(new InsnNode(RETURN)); - - return method; - } - - /** - * public boolean isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) { - * if (this.hook == null) { - * return isPositionInRenderChunk$Original(pos, chunk); - * } - * return hook.isPositionInRenderChunk(pos, chunk); - * } - */ - private MethodNode createIsPositionInRenderChunk(ClassNode classNode, MethodNode org) { - MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]); - InsnList list = method.instructions; - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - LabelNode labelNotNull = new LabelNode(); - list.add(new JumpInsnNode(IFNONNULL, labelNotNull)); - - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new VarInsnNode(ALOAD, 1)); // pos - list.add(new VarInsnNode(ALOAD, 2)); // chunk - list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "isPositionInRenderChunk$Original", org.desc, false)); - list.add(new InsnNode(IRETURN)); - - list.add(labelNotNull); - list.add(new FrameNode(F_SAME, 0, null, 0, null)); - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - list.add(new VarInsnNode(ALOAD, 1)); // pos - list.add(new VarInsnNode(ALOAD, 2)); // chunk - list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "isPositionInRenderChunk", org.desc, false)); - list.add(new InsnNode(IRETURN)); - - return method; - } - - /** - * public void updateChunks(long finishTimeNano) { - * if (this.hook == null) { - * updateChunks$Original(finishTimeNano); - * return; - * } - * hook.updateChunks(finishTimeNano); - * } - */ - private MethodNode createUpdateChunks(ClassNode classNode, MethodNode org) { - MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]); - InsnList list = method.instructions; - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - LabelNode labelNotNull = new LabelNode(); - list.add(new JumpInsnNode(IFNONNULL, labelNotNull)); - - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano - list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "updateChunks$Original", org.desc, false)); - list.add(new InsnNode(RETURN)); - - list.add(labelNotNull); - list.add(new FrameNode(F_SAME, 0, null, 0, null)); - list.add(new VarInsnNode(ALOAD, 0)); // this - list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); - list.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano - list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "updateChunks", org.desc, false)); - list.add(new InsnNode(RETURN)); - - return method; - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java index 02536d38..bf1329d6 100755 --- a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java +++ b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java @@ -18,6 +18,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin { MixinBootstrap.init(); MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.replaymod.json"); MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.recording.replaymod.json"); + MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.render.replaymod.json"); CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); if (codeSource != null) { @@ -41,7 +42,6 @@ public class LoadingPlugin implements IFMLLoadingPlugin { @Override public String[] getASMTransformerClass() { return new String[]{ - ForceChunkLoadingCT.class.getName() }; } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java deleted file mode 100644 index f38b1c9b..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java +++ /dev/null @@ -1,675 +0,0 @@ -package eu.crushedpixel.replaymod.gui; - -import com.replaymod.core.ReplayMod; -import eu.crushedpixel.replaymod.gui.elements.*; -import eu.crushedpixel.replaymod.gui.elements.listeners.CheckBoxListener; -import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; -import eu.crushedpixel.replaymod.holders.GuiEntryListEntry; -import eu.crushedpixel.replaymod.settings.EncodingPreset; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.utils.MouseUtils; -import eu.crushedpixel.replaymod.utils.ReplayFileIO; -import eu.crushedpixel.replaymod.utils.StringUtils; -import eu.crushedpixel.replaymod.video.rendering.Pipelines; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiErrorScreen; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.resources.I18n; -import net.minecraftforge.fml.client.FMLClientHandler; -import org.apache.commons.io.FilenameUtils; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.Point; - -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.List; - -public class GuiRenderSettings extends GuiScreen { - - private final Minecraft mc = Minecraft.getMinecraft(); - - //for default file - private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss"; - private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT); - - private static final int LEFT_BORDER = 10; - - private final RenderOptions initialRenderOptions = RenderOptions.loadFromConfig(ReplayMod.config); - private boolean initialized = false; - - private GuiAdvancedButton renderButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.render"), new Runnable() { - @Override - public void run() { - startRendering(); - } - }, null); - - private GuiAdvancedButton cancelButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.cancel"), new Runnable() { - @Override - public void run() { - mc.displayGuiScreen(null); - } - }, null); - - private GuiAdvancedButton toggleTabButton = new GuiAdvancedButton(0, 0, 150, 20, I18n.format("replaymod.gui.rendersettings.advanced"), new Runnable() { - @Override - public void run() { - toggleTab(); - } - }, null); - - private GuiNumberInput xRes = new GuiNumberInput(mc.fontRendererObj, 0, 0, 50, 1, 100000, mc.displayWidth, false) { - @Override - public void moveCursorBy(int move) { - super.moveCursorBy(move); - RendererSettings renderer = rendererDropdown.getElement(rendererDropdown.getSelectionIndex()); - Integer value = getIntValueNullable(); - if (value != null) { - if (renderer == RendererSettings.CUBIC) { - yRes.text = Integer.toString(Math.max(1, value * 3 / 4)); - } - if (renderer == RendererSettings.EQUIRECTANGULAR) { - yRes.text = Integer.toString(Math.max(1, value / 2)); - } - } - yRes.setCursorPositionEnd(); - validateInputs(); - } - }; - - private GuiNumberInput yRes = new GuiNumberInput(mc.fontRendererObj, 0, 0, 50, 1, 10000, mc.displayHeight, false) { - @Override - public void moveCursorBy(int move) { - super.moveCursorBy(move); - RendererSettings renderer = rendererDropdown.getElement(rendererDropdown.getSelectionIndex()); - Integer value = getIntValueNullable(); - if (value != null) { - if (renderer == RendererSettings.CUBIC) { - xRes.text = Integer.toString(value * 4 / 3); - } - if (renderer == RendererSettings.EQUIRECTANGULAR) { - xRes.text = Integer.toString(value * 2); - } - } - xRes.setCursorPositionEnd(); - validateInputs(); - } - }; - - private GuiString rendererString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.renderer")+":"); - private GuiString presetsString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.presets")+":"); - private GuiString resolutionString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.customresolution")+":"); - private GuiString asteriksString = new GuiString(0, 0, Color.WHITE, "*"); - private GuiString bitrateString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.settings.bitrate")+":"); - private GuiString fileChooserString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.outputfile")+":"); - - private GuiDropdown rendererDropdown = new GuiDropdown(mc.fontRendererObj, 0, 0, 200, 5); - private GuiDropdown encodingPresetDropdown = new GuiDropdown(mc.fontRendererObj, 0, 1, 200, 5); - - private GuiString stabilizeString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.stabilizecamera")+":"); - - private GuiAdvancedCheckBox stablePitch = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.pitch"), false, false); - private GuiAdvancedCheckBox stableYaw = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.yaw"), false, false); - private GuiAdvancedCheckBox stableRoll = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.roll"), false, false); - - private int virtualY, virtualHeight; - - private GuiAdvancedCheckBox enableGreenscreen = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.chromakey"), false); - private GuiAdvancedCheckBox renderNameTags = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.nametags"), true); - - { - enableGreenscreen.addCheckBoxListener(new CheckBoxListener() { - @Override - public void onCheck(boolean checked) { - colorPicker.setElementEnabled(checked); - } - }); - } - - private GuiAdvancedCheckBox injectMetadata = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.360metadata"), false); - - private GuiVideoFramerateSlider framerateSlider = new GuiVideoFramerateSlider(0, 0, ReplayMod.replaySettings.getVideoFramerate(), - I18n.format("replaymod.gui.rendersettings.framerate")); - - private GuiNumberInput bitrateInput = new GuiNumberInputWithText(mc.fontRendererObj, 0, 0, 50, 1D, null, 10000D, false, " kbps"); - private GuiColorPicker colorPicker = new GuiColorPicker(GuiConstants.RENDER_SETTINGS_COLOR_PICKER, 0, 0, I18n.format("replaymod.gui.rendersettings.skycolor")+": ", 0, 0); - private GuiAdvancedTextField commandInput = new GuiAdvancedTextField(mc.fontRendererObj, I18n.format("replaymod.gui.rendersettings.command"), 3000); - private GuiAdvancedTextField ffmpegArguments = new GuiAdvancedTextField(mc.fontRendererObj, I18n.format("replaymod.gui.rendersettings.ffmpeghint"), 3000) { - @Override - public void moveCursorBy(int move) { - super.moveCursorBy(move); - validateInputs(); //the file extension may have changed, so we revalidate the inputs - } - }; - - private File defaultFile = null; - { - try { - defaultFile = new File(ReplayFileIO.getRenderFolder(), FILE_FORMAT.format(Calendar.getInstance().getTime())+"."+ EncodingPreset.MP4DEFAULT.getFileExtension()); - } catch(IOException ioe) { - ioe.printStackTrace(); - } - } - - private GuiFileChooser outputFileChooser = new GuiFileChooser(GuiConstants.RENDER_SETTINGS_OUTPUT_CHOOSER, 0, 0, - "", defaultFile, new String[]{"webm"}, true); - - { - Keyboard.enableRepeatEvents(true); - - rendererDropdown.addSelectionListener(new RendererDropdownListener()); - - int i = 0; - for (RendererSettings r : RendererSettings.values()) { - rendererDropdown.addElement(r); - rendererDropdown.setHoverText(i, r.getDescription()); - i++; - } - - encodingPresetDropdown.addSelectionListener(new EncodingDropdownListener()); - - for (EncodingPreset preset : EncodingPreset.values()) { - encodingPresetDropdown.addElement(preset); - } - - encodingPresetDropdown.setSelectionIndex(2); - } - - private String[] tabNames = new String[]{I18n.format("replaymod.gui.rendersettings.video"), - I18n.format("replaymod.gui.rendersettings.advanced"), I18n.format("replaymod.gui.rendersettings.commandline")}; - - private int currentTab = 0; - - private void toggleTab() { - currentTab++; - if(currentTab >= 3) currentTab = 0; - - int nextTab = currentTab+1; - if(nextTab >= 3) nextTab = 0; - toggleTabButton.displayString = tabNames[nextTab]; - } - - private DelegatingElement currentScreen = new DelegatingElement() { - - private ComposedElement videoSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton, - rendererString, rendererDropdown, presetsString, encodingPresetDropdown, resolutionString, asteriksString, - xRes, yRes, framerateSlider, bitrateString, bitrateInput, fileChooserString, outputFileChooser); - - private ComposedElement advancedSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton, - renderNameTags, injectMetadata, stabilizeString, stablePitch, stableYaw, stableRoll, enableGreenscreen, colorPicker); - - private ComposedElement commandLineSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton, - commandInput, ffmpegArguments); - - @Override - public GuiElement delegate() { - switch(currentTab) { - case 0: - return videoSettings; - case 1: - return advancedSettings; - case 2: - return commandLineSettings; - default: - return null; - } - } - }; - - @Override - public void initGui() { - virtualHeight = 200; - virtualY = (this.height-virtualHeight)/2; - - cancelButton.xPosition = width-LEFT_BORDER-5-100; - renderButton.xPosition = cancelButton.xPosition-5-100; - toggleTabButton.xPosition = renderButton.xPosition-5-150; - - cancelButton.yPosition = renderButton.yPosition = toggleTabButton.yPosition = virtualY+virtualHeight-5-20; - - initializeVideoTab(); - initializeAdvancedTab(); - initializeCommandLineTab(); - - if(!initialized) { - initializeValues(); - } - - validateInputs(); - - initialized = true; - } - - private void initializeVideoTab() { - int largerWidth = Math.max(Math.max(rendererString.getWidth(), presetsString.getWidth()), - resolutionString.getWidth())+10; - int rowWidth = largerWidth+rendererDropdown.getWidth(); - rendererDropdown.xPosition = encodingPresetDropdown.xPosition = xRes.xPosition = - bitrateInput.xPosition = outputFileChooser.xPosition = (width - rowWidth)/2 + largerWidth; - - rendererString.positionX = presetsString.positionX = fileChooserString.positionX = - resolutionString.positionX = bitrateString.positionX = (width - rowWidth)/2; - - rendererString.positionX += (largerWidth-10)-rendererString.getWidth(); - presetsString.positionX += (largerWidth-10)-presetsString.getWidth(); - fileChooserString.positionX += (largerWidth-10)-fileChooserString.getWidth(); - resolutionString.positionX += (largerWidth-10)-resolutionString.getWidth(); - bitrateString.positionX += (largerWidth-10)-bitrateString.getWidth(); - - rendererDropdown.yPosition = virtualY + 25; - rendererString.positionY = rendererDropdown.yPosition + 6; - - encodingPresetDropdown.yPosition = rendererDropdown.yPosition+20+10; - presetsString.positionY = encodingPresetDropdown.yPosition + 6; - - asteriksString.positionX = xRes.xPosition+xRes.width+5; - yRes.xPosition = asteriksString.positionX+asteriksString.getWidth()+5; - - xRes.yPosition = yRes.yPosition = encodingPresetDropdown.yPosition+20+10; - resolutionString.positionY = asteriksString.positionY = xRes.yPosition + 6; - - bitrateInput.width = 70; - - framerateSlider.xPosition = bitrateInput.xPosition + bitrateInput.width + 5; - framerateSlider.width = 125; - - bitrateInput.yPosition = framerateSlider.yPosition = xRes.yPosition+20+10; - bitrateString.positionY = bitrateInput.yPosition+6; - - outputFileChooser.yPosition = bitrateInput.yPosition+20+10; - fileChooserString.positionY = outputFileChooser.yPosition+6; - - outputFileChooser.width = 200; - } - - private void initializeAdvancedTab() { - int singleWidth = 150; - int middleGap = 10; - - int totalWidth = (2*singleWidth)+middleGap; - - int leftX = width/2 - (singleWidth+middleGap/2); - - //might be of use in the future - //int rightX = width/2 + (middleGap/2); - - int heightDiff = 25; - - renderNameTags.xPosition = leftX; - renderNameTags.yPosition = virtualY + 25; - renderNameTags.width = singleWidth; - - stabilizeString.positionX = leftX; - - int left = leftX+stabilizeString.getWidth()+middleGap; - int width = (totalWidth - (stabilizeString.getWidth()+middleGap)) / 3; - - stableYaw.xPosition = left; - stablePitch.xPosition = left + width; - stableRoll.xPosition = left + 2*width; - - stablePitch.yPosition = stableYaw.yPosition = stableRoll.yPosition = renderNameTags.yPosition+heightDiff; - stabilizeString.positionY = stablePitch.yPosition + 2; - - enableGreenscreen.xPosition = leftX; - colorPicker.xPosition = leftX + (enableGreenscreen.width+5); - colorPicker.width = totalWidth - (enableGreenscreen.width+5); - - colorPicker.yPosition = stablePitch.yPosition+heightDiff; - enableGreenscreen.yPosition = colorPicker.yPosition+4; - - colorPicker.pickerX = colorPicker.xPosition; - colorPicker.pickerY = colorPicker.yPosition + 20; - - colorPicker.setElementEnabled(enableGreenscreen.isChecked()); - - injectMetadata.xPos(leftX); - injectMetadata.yPos(enableGreenscreen.yPos()+heightDiff); - } - - private void initializeCommandLineTab() { - commandInput.width = 55; - commandInput.xPosition = (this.width-305)/2; - commandInput.yPosition = ffmpegArguments.yPosition = virtualY + 25; - - ffmpegArguments.width = 245; - ffmpegArguments.xPosition = commandInput.xPosition+commandInput.width+5; - } - - private void initializeValues() { - RendererSettings defRS = RendererSettings.valueOf(initialRenderOptions.getMode().name()); - - List settingsList = rendererDropdown.getAllElements(); - for(int i=0; i presetList = encodingPresetDropdown.getAllElements(); - if(preset != null) { - for(int i=0; i 0) { - options.setExportCommand(commandInput.getText().trim()); - } - - if(ffmpegArguments.getText().trim().length() > 0) { - options.setExportCommandArgs(ffmpegArguments.getText().trim()); - } - - options.saveToConfig(ReplayMod.config); - - if(FMLClientHandler.instance().hasOptifine()) { - mc.displayGuiScreen(new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), I18n.format("replaymod.gui.rendering.error.optifine"))); - } else { - // TODO -// ReplayHandler.startPath(options, true); - } - - } - - private int getWidthSetting() { - return xRes.getIntValue(); - } - - private int getHeightSetting() { return yRes.getIntValue(); } - - private class RendererDropdownListener implements SelectionListener { - @Override - public void onSelectionChanged(int selectionIndex) { - xRes.setCursorPositionEnd(); - xRes.setSelectionPos(xRes.getCursorPosition()); - - yRes.setCursorPositionEnd(); - yRes.setSelectionPos(yRes.getCursorPosition()); - - yRes.moveCursorBy(0); //This causes the Aspect Ratio to be recalculated based on the Y Resolution - - validateInputs(); - } - } - - private class EncodingDropdownListener implements SelectionListener { - @Override - public void onSelectionChanged(int selectionIndex) { - EncodingPreset preset = encodingPresetDropdown.getElement(selectionIndex); - - bitrateInput.setEnabled(preset.hasBitrateSetting()); - ffmpegArguments.setText(preset.getCommandLineArgs()); - ffmpegArguments.setCursorPositionZero(); - - outputFileChooser.setAllowedExtensions(new String[]{preset.getFileExtension()}); - - File selectedFile = outputFileChooser.getSelectedFile(); - if(selectedFile != null) { - String newName = FilenameUtils.getBaseName(selectedFile.getAbsolutePath())+"."+preset.getFileExtension(); - outputFileChooser.setSelectedFile(new File(selectedFile.getParent(), newName)); - } - - validateInputs(); - } - } - - @Override - public void onGuiClosed() { - Keyboard.enableRepeatEvents(false); - super.onGuiClosed(); - } - - private void validateInputs() { - boolean valid = true; - - boolean isMp4 = false; - - boolean isPreset = false; - EncodingPreset curPreset = encodingPresetDropdown.getElement(encodingPresetDropdown.getSelectionIndex()); - if(ffmpegArguments.getText().trim().equals(curPreset.getCommandLineArgs())) { - isPreset = true; - } - - if(isPreset) { - if(curPreset.isYuv420()) { - if(getWidthSetting() % 2 != 0 || getHeightSetting() % 2 != 0) { - valid = false; - renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.yuv420"); - } - } - - //injecting Metadata is only possible with mp4 containers. - //To assure the mp4 file extension (which we need to obtain the file path), - //we can't allow custom command line arguments - if("mp4".equals(curPreset.getFileExtension())) { - isMp4 = true; - } - } - - boolean isPanoramic = false; - boolean isEquirectangular = false; - - switch (rendererDropdown.getElement(rendererDropdown.getSelectionIndex())) { - case CUBIC: - isPanoramic = true; - if (getWidthSetting() * 3 / 4 != getHeightSetting() - || getWidthSetting() * 3 % 4 != 0) { - valid = false; - renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.cubic"); - } - break; - case EQUIRECTANGULAR: - isPanoramic = true; - isEquirectangular = true; - if (getWidthSetting() / 2 != getHeightSetting() - || getWidthSetting() % 2 != 0) { - valid = false; - renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.equirectangular"); - } - break; - } - - stabilizeString.setElementEnabled(isPanoramic); - stableYaw.setElementEnabled(isPanoramic); - stablePitch.setElementEnabled(isPanoramic); - stableRoll.setElementEnabled(isPanoramic); - - injectMetadata.setElementEnabled(isEquirectangular && isMp4); - if(injectMetadata.enabled) { - injectMetadata.hoverText = null; - } else { - injectMetadata.hoverText = I18n.format("replaymod.gui.rendersettings.360metadata.error"); - injectMetadata.hoverTextColor = Color.RED; - } - - if (valid) { - renderButton.enabled = true; - renderButton.hoverText = ""; - xRes.setTextColor(0xffffffff); - yRes.setTextColor(0xffffffff); - xRes.setDisabledTextColour(0xff707070); - yRes.setDisabledTextColour(0xff707070); - } else { - renderButton.enabled = false; - xRes.setTextColor(0xffff0000); - yRes.setTextColor(0xffff0000); - xRes.setDisabledTextColour(0xffff0000); - yRes.setDisabledTextColour(0xffff0000); - } - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoRenderer.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoRenderer.java deleted file mode 100644 index b8ba6ba3..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoRenderer.java +++ /dev/null @@ -1,305 +0,0 @@ -package eu.crushedpixel.replaymod.gui; - -import eu.crushedpixel.replaymod.gui.elements.GuiProgressBar; -import eu.crushedpixel.replaymod.utils.BoundingUtils; -import eu.crushedpixel.replaymod.utils.DurationUtils; -import eu.crushedpixel.replaymod.video.VideoRenderer; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.renderer.texture.DynamicTexture; -import net.minecraft.client.renderer.texture.TextureUtil; -import net.minecraft.client.resources.I18n; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.client.config.GuiCheckBox; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; - -import java.awt.*; -import java.io.IOException; -import java.nio.ByteBuffer; - -import static net.minecraft.client.renderer.GlStateManager.bindTexture; -import static net.minecraft.client.renderer.GlStateManager.color; - -public class GuiVideoRenderer extends GuiScreen { - private static final ResourceLocation noPreviewTexture = new ResourceLocation("replaymod", "logo.jpg"); - - private final VideoRenderer renderer; - - private final String SCREEN_TITLE = I18n.format("replaymod.gui.rendering.title"); - private final String PAUSE_RENDERING = I18n.format("replaymod.gui.rendering.pause"); - private final String RESUME_RENDERING = I18n.format("replaymod.gui.rendering.resume"); - private final String CANCEL = I18n.format("replaymod.gui.rendering.cancel"); - private final String CANCEL_CONFIRM = I18n.format("replaymod.gui.rendering.cancel.callback"); - private final String PREVIEW = I18n.format("replaymod.gui.rendering.preview"); - - private GuiButton pauseButton; - private GuiButton cancelButton; - private GuiCheckBox previewCheckBox; - private GuiProgressBar progressBar; - - private DynamicTexture previewTexture; - private boolean previewTextureDirty; - - private boolean initialized = false; - - public GuiVideoRenderer(VideoRenderer renderer) { - this.renderer = renderer; - } - - @Override - @SuppressWarnings("unchecked") // I blame forge for not re-adding generics to that list - public void initGui() { - if(!initialized) { - String text = PAUSE_RENDERING; - pauseButton = new GuiButton(1, width / 2 - 152, height - 10 - 20, text); - - text = CANCEL; - cancelButton = new GuiButton(1, width / 2 + 2, height - 10 - 20, text); - - pauseButton.width = cancelButton.width = 150; - - progressBar = new GuiProgressBar(10, height - 10 - 20 - 10 - 20, width-20, 20); - - text = PREVIEW; - previewCheckBox = new GuiCheckBox(0, (width - fontRendererObj.getStringWidth(text)) / 2 - 8, - pauseButton.yPosition - 10 - 20 - 10 - 20 - 5 , text, false); - } else { - pauseButton.xPosition = width / 2 - 152; - pauseButton.yPosition = height - 10 - 20; - - cancelButton.xPosition = width / 2 + 2; - cancelButton.yPosition = height - 10 - 20; - - progressBar.setBounds(10, height - 10 - 20 - 10 - 20, width - 20, 20); - - previewCheckBox.xPosition = (width - fontRendererObj.getStringWidth(PREVIEW)) / 2 - 8; - previewCheckBox.yPosition = pauseButton.yPosition - 10 - 20 - 10 - 20 - 5; - } - - buttonList.add(pauseButton); - buttonList.add(cancelButton); - buttonList.add(previewCheckBox); - - initialized = true; - - super.initGui(); - } - - @Override - protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { - String cancelBefore = cancelButton.displayString; - super.mouseClicked(mouseX, mouseY, mouseButton); - String cancelAfter = cancelButton.displayString; - if (cancelBefore.equals(cancelAfter)) { - cancelButton.displayString = CANCEL; - } - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException { - if (button == pauseButton) { - if (PAUSE_RENDERING.equals(pauseButton.displayString)) { - renderer.setPaused(true); - pauseButton.displayString = RESUME_RENDERING; - } else if (RESUME_RENDERING.equals(pauseButton.displayString)) { - renderer.setPaused(false); - pauseButton.displayString = PAUSE_RENDERING; - } else { - throw new IllegalStateException(pauseButton.displayString); - } - } else if (button == cancelButton) { - if (CANCEL.equals(cancelButton.displayString)) { - cancelButton.displayString = CANCEL_CONFIRM; - } else if (CANCEL_CONFIRM.equals(cancelButton.displayString)) { - renderer.cancel(); - } - } - } - - //the total render time - private int renderTimeTaken = 0; - - //the time at which the Screen was last updated - private long prevTime = -1; - - //the time at which the rendering of the current frame started - private long frameStartTime = -1; - - //the amount of frames that were rendered when the time left was last updated - private int prevRenderedFrames = 0; - - //the estimated render time that is left (in seconds) - private int renderTimeLeft = 0; - - //each of the durations the rendering process took for the last 50 frames - private int[] renderTimes = new int[50]; - //the algorithm's current position in the renderTimes array - private int currentIndex = 0; - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - long current = System.currentTimeMillis(); - - //first, update the total render time (only if rendering is not paused and has already started) - if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) { - renderTimeTaken += (current - prevTime); - } else { - //if the rendering process is paused, we have to update the frame start time to prevent huge render times - //for the currently rendered frame(s) - frameStartTime = current; - } - - //always update prevTime, so we don't get huge time jumps when unpausing the rendering process - prevTime = current; - - - //calculate estimated time left - - //if the amount of rendered frames has increased since the last update - if(prevRenderedFrames < renderer.getFramesDone()) { - //we don't include the first frame in our calculations, - //as setting up the rendering process takes a few seconds - if(prevRenderedFrames > 0) { - - //calculate the amount of frames that have been rendered since the last update - int framesRendered = renderer.getFramesDone() - prevRenderedFrames; - - //calculate the time it took to render these frames - int renderTime = (int)(current - frameStartTime); - - //calculate the average time it took for each of these frames to render - int avgRenderTime = renderTime/framesRendered; - - //add all of the average render times to the render times array - for(int i=0; i= renderTimes.length) currentIndex = 0; - } - - //the renderTimes array initially contains lots of zeros, - //so we count the amount of valid timespans - int validValues = 0; - - int totalTime = 0; - for(int i : renderTimes) { - if(i > 0) { - totalTime += i; - validValues++; - } - } - - //calculate the average render time for the previous [up to 50] frames - float averageRenderTime = validValues > 0 ? totalTime / validValues : 0; - - //calculate the remaining render time in seconds - renderTimeLeft = Math.round((averageRenderTime * (renderer.getTotalFrames() - renderer.getFramesDone())) / 1000); - } - - //set the render start time of the next frame(s) to the current timestamp - frameStartTime = current; - //update the amount of rendered frames for the last calculation - prevRenderedFrames = renderer.getFramesDone(); - } - - String takenString = I18n.format("replaymod.gui.rendering.timetaken")+": "+DurationUtils.convertSecondsToString(renderTimeTaken/1000); - String leftString = I18n.format("replaymod.gui.rendering.timeleft")+": "+DurationUtils.convertSecondsToString(renderTimeLeft); - - int centerX = width / 2; - - drawBackground(0); - - drawCenteredString(fontRendererObj, SCREEN_TITLE, centerX, 5, Color.WHITE.getRGB()); - - String framesProgress = I18n.format("replaymod.gui.rendering.progress", renderer.getFramesDone(), renderer.getTotalFrames()); - progressBar.setProgressString(framesProgress); - progressBar.setProgress((float) renderer.getFramesDone() / renderer.getTotalFrames()); - - progressBar.drawProgressBar(); - - int previewX = 10; - int previewWidth = width - 20; - - int previewHeight = previewCheckBox.yPosition - 10 - 20; - int previewY = previewCheckBox.yPosition - 10 - previewHeight; - - if(previewCheckBox.isChecked()) { - renderPreview(previewX, previewY, previewWidth, previewHeight); - } else { - renderNoPreview(previewX, previewY, previewWidth, previewHeight); - } - - drawString(fontRendererObj, takenString, 12, previewCheckBox.yPosition + 5 + 20, Color.WHITE.getRGB()); - drawString(fontRendererObj, leftString, width - 12 - fontRendererObj.getStringWidth(leftString), - previewCheckBox.yPosition + 5 + 20, Color.WHITE.getRGB()); - - super.drawScreen(mouseX, mouseY, partialTicks); - } - - private synchronized void renderPreview(int x, int y, int width, int height) { - ReadableDimension videoSize = renderer.getFrameSize(); - final int videoWidth = videoSize.getWidth(); - final int videoHeight = videoSize.getHeight(); - - if (previewTexture == null) { - previewTexture = new DynamicTexture(videoWidth, videoHeight) { - @Override - public void updateDynamicTexture() { - bindTexture(getGlTextureId()); - TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false); - } - }; - } - - if (previewTextureDirty) { - previewTexture.updateDynamicTexture(); - previewTextureDirty = false; - } - - Dimension dimension = BoundingUtils.fitIntoBounds(new Dimension(videoSize), new Dimension(width, height)); - - x += (width - dimension.getWidth()) / 2; - y += (height - dimension.getHeight()) / 2; - width = dimension.getWidth(); - height = dimension.getHeight(); - - color(1, 1, 1, 1); - bindTexture(previewTexture.getGlTextureId()); - drawScaledCustomSizeModalRect(x, y, 0, 0, videoWidth, videoHeight, width, height, videoWidth, videoHeight); - } - - private void renderNoPreview(int x, int y, int width, int height) { - int actualWidth = width; - int actualHeight = height; - if (width / height > 1280 / 720) { - actualWidth = height * 1280 / 720; - } else { - actualHeight = width * 720 / 1280; - } - - x += (width - actualWidth) / 2; - y += (height - actualHeight) / 2; - - Minecraft.getMinecraft().getTextureManager().bindTexture(noPreviewTexture); - color(1, 1, 1, 1); - drawScaledCustomSizeModalRect(x, y, 0, 0, 1280, 720, actualWidth, actualHeight, 1280, 720); - } - - public void updatePreview(RGBFrame frame) { - if (previewCheckBox.isChecked() && previewTexture != null) { - ByteBuffer buffer = frame.getByteBuffer(); - buffer.mark(); - synchronized (this) { - int[] data = previewTexture.getTextureData(); - for (int i = 0; i < data.length; i++) { - data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); - } - previewTextureDirty = true; - } - buffer.reset(); - } - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEntityRenderer.java index a841ea37..658bf483 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEntityRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/mixin/MixinEntityRenderer.java @@ -2,251 +2,34 @@ package eu.crushedpixel.replaymod.mixin; import com.replaymod.replay.camera.CameraEntity; import eu.crushedpixel.replaymod.renderer.SpectatorRenderer; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; -import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer; -import eu.crushedpixel.replaymod.video.capturer.StereoscopicOpenGlFrameCapturer; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.renderer.EntityRenderer; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.RenderGlobal; -import net.minecraft.client.renderer.culling.Frustum; -import net.minecraft.client.settings.GameSettings; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.MovingObjectPosition; import org.lwjgl.opengl.GL11; -import org.lwjgl.util.glu.Project; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(EntityRenderer.class) -public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective { +public abstract class MixinEntityRenderer { @Shadow public Minecraft mc; - private EntityRendererHandler handler; private SpectatorRenderer spectatorRenderer = new SpectatorRenderer(); - @Override - public void setHandler(EntityRendererHandler handler) { - this.handler = handler; - } - - @Override - public EntityRendererHandler getHandler() { - return handler; - } - - @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) - private void onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) { - if (handler == null) return; - if (!handler.getOptions().isDefaultSky()) { - ci.cancel(); - } - } - - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/culling/Frustum;setPosition(DDD)V")) - public void createNoCullingFrustum(Frustum frustum, double x, double y, double z) { - if (handler != null) { - frustum.clippingHelper = new EntityRendererHandler.NoCullingClippingHelper(); - } - frustum.setPosition(x, y, z); - } - - @Inject(method = "orientCamera", at = @At("HEAD")) - private void resetRotationIfNeeded(float partialTicks, CallbackInfo ci) { - if (handler != null) { - Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); - RenderOptions options = handler.getOptions(); - if (options.getIgnoreCameraRotation()[0]) { - entity.prevRotationYaw = entity.rotationYaw = 0; - } - if (options.getIgnoreCameraRotation()[1]) { - entity.prevRotationPitch = entity.rotationPitch = 0; - } - if (options.getIgnoreCameraRotation()[2] && entity instanceof CameraEntity) { - ((CameraEntity) entity).roll = 0; - } - } - } - - private float orgYaw; - private float orgPitch; - private float orgPrevYaw; - private float orgPrevPitch; - private float orgRoll; - - @Inject(method = "setupCameraTransform", at = @At("HEAD")) - private void beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { - if (handler != null) { - Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); - orgYaw = entity.rotationYaw; - orgPitch = entity.rotationPitch; - orgPrevYaw = entity.prevRotationYaw; - orgPrevPitch = entity.prevRotationPitch; - orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0; - } - } - - @Inject(method = "setupCameraTransform", at = @At("RETURN")) - private void afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { - if (handler != null) { - Entity entity = Minecraft.getMinecraft().getRenderViewEntity(); - entity.rotationYaw = orgYaw; - entity.rotationPitch = orgPitch; - entity.prevRotationYaw = orgPrevYaw; - entity.prevRotationPitch = orgPrevPitch; - if (entity instanceof CameraEntity) { - ((CameraEntity) entity).roll = orgRoll; - } - } - } - - @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) + @Inject(method = "renderHand", at = @At("HEAD")) private void renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) { - // TODO -// if (handler != null) { -// if (handler.data instanceof CubicOpenGlFrameCapturer.Data) { -// ci.cancel(); -// return; // No spectator hands during 360° view, we wouldn't even know where to put it -// } -// Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity(); -// if (!ReplayHandler.isCameraView() && currentEntity instanceof EntityPlayer) { -// renderPass = handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0; -// spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass); -// } -// } else if (ReplayHandler.isInReplay() && !ReplayHandler.isCameraView()) { -// Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity(); -// if (!ReplayHandler.isCameraView() && currentEntity instanceof EntityPlayer) { -// spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass); -// } -// } - } - - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V")) - private void drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition mop, int alwaysZero, float partialTicks) { - if (handler == null) { - instance.drawSelectionBox(player, mop, alwaysZero, partialTicks); + // TODO Check that this gets cancelled in 360 and doesn't misbehave in SP/MP + Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity(); + if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof EntityPlayerSP)) { + spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass); } } - private int orgRenderDistanceChunks; - - @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0)) - private void beforeRenderSky(CallbackInfo ci) { - if (handler != null && !handler.getOptions().isDefaultSky()) { - GameSettings settings = Minecraft.getMinecraft().gameSettings; - orgRenderDistanceChunks = settings.renderDistanceChunks; - settings.renderDistanceChunks = 5; // Set render distance to 5 so we're always rendering sky when chroma keying - } - } - - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V")) - private void renderSky(RenderGlobal instance, float partialTicks, int renderPass) { - if (handler != null && !handler.getOptions().isDefaultSky()) { - Minecraft.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks; - int c = handler.getOptions().getSkyColor(); - GlStateManager.clearColor((c >> 16 & 0xff) / (float) 0xff, (c >> 8 & 0xff) / (float) 0xff, (c & 0xff) / (float) 0xff, 1); - GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT); - } else { - instance.renderSky(partialTicks, renderPass); - } - } - - /* - * Cubic Renderer - */ - - @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 0)) - private void setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) { - if (getHandler() != null) { - if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { - GlStateManager.translate(0.07, 0, 0); - } else if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { - GlStateManager.translate(-0.07, 0, 0); - } - } - } - - @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 1)) - private void setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) { - if (getHandler() != null) { - if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { - GlStateManager.translate(0.1, 0, 0); - } else if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { - GlStateManager.translate(-0.1, 0, 0); - } - } - } - - /* - * Cubic Renderer - */ - - @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - private void gluPerspective$0(float fovY, float aspect, float zNear, float zFar) { - gluPerspective(fovY, aspect, zNear, zFar); - } - - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - private void gluPerspective$1(float fovY, float aspect, float zNear, float zFar) { - gluPerspective(fovY, aspect, zNear, zFar); - } - - @Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - private void gluPerspective$2(float fovY, float aspect, float zNear, float zFar) { - gluPerspective(fovY, aspect, zNear, zFar); - } - - @Override - public void gluPerspective(float fovY, float aspect, float zNear, float zFar) { - if (getHandler() != null && getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { - fovY = 90; - aspect = 1; - } - Project.gluPerspective(fovY, aspect, zNear, zFar); - } - - @Inject(method = "orientCamera", at = @At("HEAD")) - private void setupCubicFrameRotation(float partialTicks, CallbackInfo ci) { - if (getHandler() != null && getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { - switch ((CubicOpenGlFrameCapturer.Data) getHandler().data) { - case FRONT: - GlStateManager.rotate(0, 0.0F, 1.0F, 0.0F); - break; - case RIGHT: - GlStateManager.rotate(90, 0.0F, 1.0F, 0.0F); - break; - case BACK: - GlStateManager.rotate(180, 0.0F, 1.0F, 0.0F); - break; - case LEFT: - GlStateManager.rotate(-90, 0.0F, 1.0F, 0.0F); - break; - case BOTTOM: - GlStateManager.rotate(90, 1.0F, 0.0F, 0.0F); - break; - case TOP: - GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F); - break; - } - - // Minecraft goes back a little so we have to invert that - GlStateManager.translate(0.0F, 0.0F, 0.1F); - } - } - - - /* - * Misc - */ - - @Inject(method = "orientCamera", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;translate(FFF)V", shift = At.Shift.AFTER, ordinal = 3)) private void setupCameraRoll(float partialTicks, CallbackInfo ci) { if (mc.getRenderViewEntity() instanceof CameraEntity) { diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderManager.java b/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderManager.java index d6a2de3e..35a69c17 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderManager.java +++ b/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderManager.java @@ -1,8 +1,5 @@ package eu.crushedpixel.replaymod.mixin; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; -import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.settings.GameSettings; @@ -14,27 +11,12 @@ 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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(RenderManager.class) public class MixinRenderManager { - @Shadow - private float playerViewX; - @Shadow private float playerViewY; - @Inject(method = "doRenderEntity", at = @At("HEAD")) - private void reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler(); - if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) { - double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)); - double yaw = -Math.atan2(dx, dz); - playerViewX = (float) Math.toDegrees(pitch); - playerViewY = (float) Math.toDegrees(yaw); - } - } - @Inject(method = "cacheActiveRenderInfo", at = @At("RETURN")) public void fixHeadRotationForAnimals(World world, FontRenderer font, Entity view, Entity target, GameSettings settings, float partialRenderTick, CallbackInfo ci) { if (view instanceof EntityAnimal && !((EntityAnimal) view).isPlayerSleeping()) { diff --git a/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java b/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java index 10ea6763..1352fc05 100644 --- a/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java @@ -1,7 +1,7 @@ package eu.crushedpixel.replaymod.renderer; +import com.replaymod.render.hooks.EntityRendererHandler; import eu.crushedpixel.replaymod.utils.SkinProvider; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; @@ -57,7 +57,7 @@ public class SpectatorRenderer { GlStateManager.translate((float)(-(renderPass * 2 - 1)) * f1, 0.0F, 0.0F); } - ((EntityRendererHandler.GluPerspective) mc.entityRenderer).gluPerspective(mc.entityRenderer.getFOVModifier(partialTicks, false), + ((EntityRendererHandler.GluPerspective) mc.entityRenderer).replayModRender_gluPerspective(mc.entityRenderer.getFOVModifier(partialTicks, false), (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, mc.entityRenderer.farPlaneDistance * 2.0F); GlStateManager.matrixMode(GL11.GL_MODELVIEW); diff --git a/src/main/java/eu/crushedpixel/replaymod/settings/EncodingPreset.java b/src/main/java/eu/crushedpixel/replaymod/settings/EncodingPreset.java deleted file mode 100644 index 85cc3fb5..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/settings/EncodingPreset.java +++ /dev/null @@ -1,54 +0,0 @@ -package eu.crushedpixel.replaymod.settings; - -import eu.crushedpixel.replaymod.holders.GuiEntryListEntry; -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.minecraft.client.resources.I18n; - -@AllArgsConstructor -public enum EncodingPreset implements GuiEntryListEntry { - - MP4CUSTOM("replaymod.gui.rendersettings.presets.mp4.custom", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -b:v %BITRATE% -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"), - - MP4HIGH("replaymod.gui.rendersettings.presets.mp4.high", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -qp 1 -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"), - - MP4DEFAULT("replaymod.gui.rendersettings.presets.mp4.default", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"), - - MP4POTATO("replaymod.gui.rendersettings.presets.mp4.potato", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"), - - WEBMCUSTOM("replaymod.gui.rendersettings.presets.webm.custom", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libvpx -b:v %BITRATE% \"%FILENAME%.webm\"", "webm"), - - MKVLOSSLESS("replaymod.gui.rendersettings.presets.mkv.lossless", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -qp 0 \"%FILENAME%.mkv\"", "mkv"), - - PNGSEQUENCE("replaymod.gui.rendersettings.presets.png", - "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - \"%FILENAME%-%06d.png\"", "png"); - - private String name; - - @Getter - private String commandLineArgs; - - @Getter - private String fileExtension; - - public boolean hasBitrateSetting() { - return commandLineArgs.contains("%BITRATE%"); - } - - public boolean isYuv420() { return commandLineArgs.contains("-pix_fmt yuv420p"); } - - @Override - public String getDisplayString() { - return getI18nName(); - } - - public String getI18nName() { - return I18n.format(name); - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java b/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java deleted file mode 100644 index 0207e9fd..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java +++ /dev/null @@ -1,267 +0,0 @@ -package eu.crushedpixel.replaymod.settings; - -import eu.crushedpixel.replaymod.video.rendering.Pipelines; -import lombok.Data; -import net.minecraft.client.Minecraft; -import net.minecraftforge.common.config.Configuration; - -import java.io.File; -import java.util.Arrays; - -import static org.apache.commons.lang3.Validate.isTrue; - -@Data -public final class RenderOptions { - private Pipelines.Preset mode = Pipelines.Preset.DEFAULT; - private String bitrate = "10000K"; - private int fps = 30; - - private File outputFile; - - /** - * Whether to ignore camera rotation. On yaw, pitch, roll axis. - */ - private boolean[] ignoreCameraRotation = new boolean[3]; - - // Advanced - private boolean waitForChunks = true; - private boolean isLinearMovement = false; - private boolean hideNameTags = false; - private int skyColor = -1; - private int width = Minecraft.getMinecraft().displayWidth; - private int height = Minecraft.getMinecraft().displayHeight; - - private boolean inject360Metadata = false; - - // Highly advanced - private boolean highPerformance; - private String exportCommand = "ffmpeg"; - private String exportCommandArgs = ""; - - public int getFps() { - return fps; - } - - public void setFps(int fps) { - isTrue(fps > 0, "Fps must be positive."); - this.fps = fps; - } - - public boolean isDefaultSky() { - return skyColor == -1; - } - - public void setIgnoreCameraRotation(boolean yaw, boolean pitch, boolean roll) { - ignoreCameraRotation = new boolean[]{yaw, pitch, roll}; - } - - public RenderOptions copy() { - RenderOptions copy = new RenderOptions(); - copy.mode = this.mode; - copy.bitrate = this.bitrate; - copy.fps = this.fps; - copy.ignoreCameraRotation = Arrays.copyOf(this.ignoreCameraRotation, 3); - copy.waitForChunks = this.waitForChunks; - copy.isLinearMovement = this.isLinearMovement; - copy.hideNameTags = this.hideNameTags; - copy.skyColor = this.skyColor; - copy.width = this.width; - copy.height = this.height; - copy.inject360Metadata = this.inject360Metadata; - copy.highPerformance = this.highPerformance; - copy.exportCommand = this.exportCommand; - copy.exportCommandArgs = this.exportCommandArgs; - return copy; - } - - /*/ - /* Methods and Fields used to save/load a RenderOptions instance from/to a Configuration File - /*/ - - private static final String CATEGORY_NAME = "settings_cache"; - - private static final String MODE_ENTRY = "mode"; - private static final String BITRATE_ENTRY = "bitrate"; - private static final String FPS_ENTRY = "fps"; - private static final String IGNORE_ROTATION_ENTRY = "ignoreRotation"; - private static final String CHUNKS_ENTRY = "waitForChunks"; - private static final String LINEAR_ENTRY = "linearMovement"; - private static final String HIDE_NAMETAGS_ENTRY = "hideNametags"; - private static final String SKY_COLOR_ENTRY = "skyColor"; - private static final String INJECT_METADATA_ENTRY = "injectMetadata"; - private static final String WIDTH_ENTRY = "xRes"; - private static final String HEIGHT_ENTRY = "yRes"; - private static final String HIGH_PERFORMANCE_ENTRY = "highPerformance"; - private static final String EXPORT_COMMAND_ENTRY = "exportCommand"; - private static final String EXPORT_COMMAND_ARGS_ENTRY = "exportCommandArgs"; - - public static RenderOptions loadFromConfig(Configuration config) { - RenderOptions loaded = new RenderOptions(); - - config.load(); - - loaded.mode = getModeFromConfig(config); - loaded.bitrate = getBitrateFromConfig(config); - loaded.fps = getFramerateFromConfig(config); - loaded.ignoreCameraRotation = getIgnoreCameraRotationFromConfig(config); - loaded.waitForChunks = getWaitForChunksFromConfig(config); - loaded.isLinearMovement = getLinearFromConfig(config); - loaded.hideNameTags = getHideNametagsFromConfig(config); - loaded.skyColor = getSkyColorFromConfig(config); - loaded.inject360Metadata = getInject360MetadataFromConfig(config); - loaded.width = getWidthFromConfig(config); - loaded.height = getHeightFromConfig(config); - loaded.highPerformance = getHighPerformanceFromConfig(config); - loaded.exportCommand = getExportCommandFromConfig(config); - loaded.exportCommandArgs = getExportCommandArgsFromConfig(config); - - return loaded; - } - - public void saveToConfig(Configuration config) { - config.load(); - - config.removeCategory(config.getCategory(CATEGORY_NAME)); - - getModeFromConfig(config, getMode().name()); - getBitrateFromConfig(config, getBitrate()); - getFramerateFromConfig(config, getFps()); - getIgnoreCameraRotationFromConfig(config, getIgnoreCameraRotation()); - getWaitForChunksFromConfig(config, isWaitForChunks()); - getLinearFromConfig(config, isLinearMovement()); - getHideNametagsFromConfig(config, isHideNameTags()); - getSkyColorFromConfig(config, getSkyColor()); - getInject360MetadataFromConfig(config, isInject360Metadata()); - getWidthFromConfig(config, getWidth()); - getHeightFromConfig(config, getHeight()); - getHighPerformanceFromConfig(config, isHighPerformance()); - getExportCommandFromConfig(config, getExportCommand()); - getExportCommandArgsFromConfig(config, getExportCommandArgs()); - - config.save(); - } - - private static Pipelines.Preset getModeFromConfig(Configuration config) { - return getModeFromConfig(config, Pipelines.Preset.DEFAULT.name()); - } - - private static Pipelines.Preset getModeFromConfig(Configuration config, String def) { - return Pipelines.Preset.valueOf(config.get(CATEGORY_NAME, MODE_ENTRY, def).getString()); - } - - private static String getBitrateFromConfig(Configuration config) { - return getBitrateFromConfig(config, "10000K"); - } - - private static String getBitrateFromConfig(Configuration config, String def) { - return config.get(CATEGORY_NAME, BITRATE_ENTRY, def).getString(); - } - - private static int getFramerateFromConfig(Configuration config) { - return getFramerateFromConfig(config, 30); - } - - private static int getFramerateFromConfig(Configuration config, int def) { - return config.get(CATEGORY_NAME, FPS_ENTRY, def).getInt(); - } - - private static boolean[] getIgnoreCameraRotationFromConfig(Configuration config) { - return getIgnoreCameraRotationFromConfig(config, new boolean[3]); - } - - private static boolean[] getIgnoreCameraRotationFromConfig(Configuration config, boolean[] def) { - String converted = ""; - for(boolean b : def) converted += b+" "; - - String result = config.get(CATEGORY_NAME, IGNORE_ROTATION_ENTRY, converted).getString(); - - String[] split = result.split(" "); - - boolean[] ret = new boolean[3]; - for(int i=0; i<3; i++) { - ret[i] = Boolean.valueOf(split[i]); - } - - return ret; - } - - private static boolean getWaitForChunksFromConfig(Configuration config) { - return getWaitForChunksFromConfig(config, true); - } - - private static boolean getWaitForChunksFromConfig(Configuration config, boolean def) { - return config.get(CATEGORY_NAME, CHUNKS_ENTRY, def).getBoolean(); - } - - private static boolean getLinearFromConfig(Configuration config) { - return getLinearFromConfig(config, false); - } - - private static boolean getLinearFromConfig(Configuration config, boolean def) { - return config.get(CATEGORY_NAME, LINEAR_ENTRY, def).getBoolean(); - } - - private static boolean getHideNametagsFromConfig(Configuration config) { - return getHideNametagsFromConfig(config, false); - } - - private static boolean getHideNametagsFromConfig(Configuration config, boolean def) { - return config.get(CATEGORY_NAME, HIDE_NAMETAGS_ENTRY, def).getBoolean(); - } - - private static int getSkyColorFromConfig(Configuration config) { - return getSkyColorFromConfig(config, -1); - } - - private static int getSkyColorFromConfig(Configuration config, int def) { - return config.get(CATEGORY_NAME, SKY_COLOR_ENTRY, def).getInt(); - } - - private static boolean getInject360MetadataFromConfig(Configuration config) { - return getInject360MetadataFromConfig(config, false); - } - - private static boolean getInject360MetadataFromConfig(Configuration config, boolean def) { - return config.get(CATEGORY_NAME, INJECT_METADATA_ENTRY, def).getBoolean(); - } - - private static int getWidthFromConfig(Configuration config) { - return getWidthFromConfig(config, Minecraft.getMinecraft().displayWidth); - } - - private static int getWidthFromConfig(Configuration config, int def) { - return config.get(CATEGORY_NAME, WIDTH_ENTRY, def).getInt(); - } - - private static int getHeightFromConfig(Configuration config) { - return getHeightFromConfig(config, Minecraft.getMinecraft().displayHeight); - } - - private static int getHeightFromConfig(Configuration config, int def) { - return config.get(CATEGORY_NAME, HEIGHT_ENTRY, def).getInt(); - } - - private static boolean getHighPerformanceFromConfig(Configuration config) { - return getHighPerformanceFromConfig(config, false); - } - - private static boolean getHighPerformanceFromConfig(Configuration config, boolean def) { - return config.get(CATEGORY_NAME, HIGH_PERFORMANCE_ENTRY, def).getBoolean(); - } - - private static String getExportCommandFromConfig(Configuration config) { - return getExportCommandFromConfig(config, "ffmpeg"); - } - - private static String getExportCommandFromConfig(Configuration config, String def) { - return config.get(CATEGORY_NAME, EXPORT_COMMAND_ENTRY, def).getString(); - } - - private static String getExportCommandArgsFromConfig(Configuration config) { - return getExportCommandArgsFromConfig(config, ""); - } - - private static String getExportCommandArgsFromConfig(Configuration config, String def) { - return config.get(CATEGORY_NAME, EXPORT_COMMAND_ARGS_ENTRY, def).getString(); - } -} \ No newline at end of file diff --git a/src/main/java/eu/crushedpixel/replaymod/timer/ReplayTimer.java b/src/main/java/eu/crushedpixel/replaymod/timer/ReplayTimer.java deleted file mode 100755 index 36e5b9f2..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/timer/ReplayTimer.java +++ /dev/null @@ -1,43 +0,0 @@ -package eu.crushedpixel.replaymod.timer; - -import eu.crushedpixel.replaymod.events.handlers.MinecraftTicker; -import net.minecraft.client.Minecraft; -import net.minecraft.util.Timer; - -public class ReplayTimer extends Timer { - - public static ReplayTimer get(Minecraft mc) { - Timer timer = mc.timer; - if (!(timer instanceof ReplayTimer)) { - throw new IllegalStateException("ReplayTimer not installed"); - } - return (ReplayTimer) timer; - } - - /** - * When the timer is set to passive, it does not advance the (render) ticks on it's own. - */ - public boolean passive; - - public ReplayTimer() { - super(20); - } - - @Override - public void updateTimer() { - if (!passive) { - super.updateTimer(); - } - - if (timerSpeed == 0) { - try { - MinecraftTicker.runMouseKeyboardTick(Minecraft.getMinecraft()); - } catch (OutOfMemoryError e) { - // Disable passive mode and reset timer speed so we can use the buttons on the OOM screen - passive = false; - timerSpeed = 1; - throw e; - } - } - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/BoundingUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/BoundingUtils.java index 6358007f..55078b65 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/BoundingUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/BoundingUtils.java @@ -1,10 +1,11 @@ package eu.crushedpixel.replaymod.utils; import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; public class BoundingUtils { - public static Dimension fitIntoBounds(Dimension toFit, Dimension bounds) { + public static Dimension fitIntoBounds(ReadableDimension toFit, ReadableDimension bounds) { int width = toFit.getWidth(); int height = toFit.getHeight(); diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/DurationUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/DurationUtils.java index 9fae964d..70a419d9 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/DurationUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/DurationUtils.java @@ -1,22 +1,7 @@ package eu.crushedpixel.replaymod.utils; -import net.minecraft.client.resources.I18n; - public class DurationUtils { - public static String convertSecondsToString(int seconds) { - int hours = seconds/(60*60); - int min = seconds/60 - hours*60; - int sec = seconds - ((min*60) + (hours*60*60)); - - StringBuilder builder = new StringBuilder(); - if(hours > 0) builder.append(hours).append(I18n.format("replaymod.gui.hours")); - if(min > 0 || hours > 0) builder.append(min).append(I18n.format("replaymod.gui.minutes")); - builder.append(sec).append(I18n.format("replaymod.gui.seconds")); - - return builder.toString(); - } - public static String convertSecondsToShortString(int seconds) { int hours = seconds/(60*60); int min = seconds/60 - hours*60; diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java index 16fb3605..eaa526bb 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java @@ -6,7 +6,6 @@ import net.minecraft.client.renderer.WorldRenderer; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; -import java.nio.ByteBuffer; import java.nio.IntBuffer; public class OpenGLUtils { @@ -28,18 +27,6 @@ public class OpenGLUtils { public static void init() { } - public static void openGlBytesToRBG(ByteBuffer buffer, int bufferWidth, int xOffset, int yOffset, ByteBuffer to, int width) { - byte[] rowBuf = new byte[bufferWidth * 3]; - // Copy image flipped vertically to target buffer - int rows = buffer.remaining() / 3 / bufferWidth; - for (int i = 0; i < rows; i++) { - buffer.get(rowBuf); - to.position(((yOffset + rows - i - 1) * width + xOffset) * 3); - to.put(rowBuf); - } - to.rewind(); - } - public static void drawRotatedRectWithCustomSizedTexture(int x, int y, float rotation, float u, float v, int width, int height, float textureWidth, float textureHeight) { GlStateManager.pushMatrix(); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java b/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java deleted file mode 100755 index ab9bed81..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java +++ /dev/null @@ -1,73 +0,0 @@ -package eu.crushedpixel.replaymod.video; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.texture.TextureUtil; -import net.minecraft.client.shader.Framebuffer; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; - -import java.awt.image.BufferedImage; -import java.nio.IntBuffer; - -public class ScreenCapture { - - private static final Minecraft mc = Minecraft.getMinecraft(); - - private static IntBuffer pixelBuffer; - private static int[] pixelValues; - - public static BufferedImage captureScreen() { - int width = mc.displayWidth; - int height = mc.displayHeight; - - Framebuffer buffer = mc.getFramebuffer(); - - if(OpenGlHelper.isFramebufferEnabled()) { - width = buffer.framebufferTextureWidth; - height = buffer.framebufferTextureHeight; - } - - int k = width * height; - - if(pixelBuffer == null || pixelBuffer.capacity() < k) { - pixelBuffer = BufferUtils.createIntBuffer(k); - pixelValues = new int[k]; - } - - GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); - GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); - pixelBuffer.clear(); - - if(OpenGlHelper.isFramebufferEnabled()) { - GlStateManager.bindTexture(buffer.framebufferTexture); - GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); - } else { - GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); - } - - pixelBuffer.get(pixelValues); - TextureUtil.processPixelValues(pixelValues, width, height); - BufferedImage bufferedimage; - - - if(OpenGlHelper.isFramebufferEnabled()) { - bufferedimage = new BufferedImage(buffer.framebufferWidth, buffer.framebufferHeight, 1); - int l = buffer.framebufferTextureHeight - buffer.framebufferHeight; - - for(int i1 = l; i1 < buffer.framebufferTextureHeight; ++i1) { - for(int j1 = 0; j1 < buffer.framebufferWidth; ++j1) { - bufferedimage.setRGB(j1, i1 - l, pixelValues[i1 * buffer.framebufferTextureWidth + j1]); - } - } - } else { - bufferedimage = new BufferedImage(width, height, 1); - bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width); - } - - - return bufferedimage; - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java deleted file mode 100644 index 132e2f6b..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java +++ /dev/null @@ -1,397 +0,0 @@ -package eu.crushedpixel.replaymod.video; - -import com.replaymod.core.ReplayMod; -import com.replaymod.replay.ReplaySender; -import eu.crushedpixel.replaymod.gui.GuiVideoRenderer; -import eu.crushedpixel.replaymod.holders.AdvancedPosition; -import eu.crushedpixel.replaymod.holders.TimestampValue; -import eu.crushedpixel.replaymod.interpolation.KeyframeList; -import eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.timer.ReplayTimer; -import eu.crushedpixel.replaymod.video.capturer.RenderInfo; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; -import eu.crushedpixel.replaymod.video.metadata.MetadataInjector; -import eu.crushedpixel.replaymod.video.rendering.Pipeline; -import eu.crushedpixel.replaymod.video.rendering.Pipelines; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.util.Timer; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.FutureTask; - -import static net.minecraft.client.renderer.GlStateManager.*; -import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; - -public class VideoRenderer implements RenderInfo { - private final Minecraft mc = Minecraft.getMinecraft(); - private final ReplaySender replaySender; - private final RenderOptions options; - private final VideoWriter videoWriter; - - private int fps; - private boolean mouseWasGrabbed; - - private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal; - - private int framesDone; - private int totalFrames; - - private KeyframeList positionKeyframes; - private KeyframeList timeKeyframes; - - private final GuiVideoRenderer gui; - private boolean paused; - private boolean cancelled; - - private final Pipeline renderingPipeline; - - public VideoRenderer(RenderOptions options) throws IOException { - this.gui = new GuiVideoRenderer(this); - this.replaySender = ReplayMod.replaySender; - this.options = options; - this.renderingPipeline = Pipelines.newPipeline(options.getMode(), this, videoWriter = new VideoWriter(options) { - @Override - public void consume(RGBFrame frame) { - gui.updatePreview(frame); - super.consume(frame); - } - }); - } - - /** - * Render this video. - * @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed) - */ - public boolean renderVideo() { - setup(); - - // Because this might take some time to prepare we'll render the GUI at least once to not confuse the user - drawGui(); - - Timer timer = mc.timer; - - // Play up to one second before starting to render - // This is necessary in order to ensure that all entities have at least two position packets - // and their first position in the recording is correct. - // Note that it is impossible to also get the interpolation between their latest position - // and the one in the recording correct as there's no reliable way to tell when the server ticks - // or when we should be done with the interpolation of the entity - int videoStart = timeKeyframes.getInterpolatedValueForPathPosition(0, true).asInt(); - - if (videoStart > 1000) { - int replayTime = videoStart - 1000; - timer.elapsedPartialTicks = timer.renderPartialTicks = 0; - timer.timerSpeed = 1; - while (replayTime < videoStart) { - timer.elapsedTicks = 1; - replayTime += 50; - replaySender.sendPacketsTill(replayTime); - tick(); - } - } - - mc.renderGlobal.renderEntitiesStartupCounter = 0; - - renderingPipeline.run(); - - if(options.isInject360Metadata()) { - File outputFile = options.getOutputFile(); - MetadataInjector.inject360Metadata(new File(outputFile.getParentFile(), outputFile.getName() + ".mp4")); - } - - finish(); - return !cancelled; - } - - @Override - public float updateForNextFrame() { - if (!options.isHighPerformance() || framesDone % fps == 0) { - drawGui(); - } - - updateTime(mc.timer, framesDone); - - int elapsedTicks = mc.timer.elapsedTicks; - while (elapsedTicks-- > 0) { - tick(); - } - - updateCam(); - framesDone++; - return mc.timer.renderPartialTicks; - } - - @Override - public RenderOptions getRenderOptions() { - return options; - } - - private void setup() { - if (Mouse.isGrabbed()) { - mouseWasGrabbed = true; - } - Mouse.setGrabbed(false); - ReplayTimer.get(mc).passive = true; - mc.timer.timerSpeed = 1; - - fps = options.getFps(); - - // TODO -// positionKeyframes = ReplayHandler.getPositionKeyframes(); -// timeKeyframes = ReplayHandler.getTimeKeyframes(); - - int duration = Math.max(timeKeyframes.last().getRealTimestamp(), positionKeyframes.last().getRealTimestamp()); - - totalFrames = duration*fps/1000; - - ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); - gui.setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight()); - - if (options.isWaitForChunks()) { - chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal); - } - } - - private void finish() { - if (mouseWasGrabbed) { - mc.mouseHelper.grabMouseCursor(); - } - ReplayTimer.get(mc).passive = false; - mc.displayGuiScreen(null); - if (chunkLoadingRenderGlobal != null) { - chunkLoadingRenderGlobal.uninstall(); - } - - ReplayMod.soundHandler.playRenderSuccessSound(); - - mc.displayGuiScreen(null); - } - - private void updateCam() { - //TODO -// KeyframeList positionKeyframes = ReplayHandler.getPositionKeyframes(); -// -// if (mc.thePlayer == null) { -// return; // Not ready yet -// } -// int videoTime = framesDone * 1000 / fps; -// int posCount = ReplayHandler.getPositionKeyframes().size(); -// -// AdvancedPosition pos = new AdvancedPosition(); -// Keyframe lastPos = positionKeyframes.getPreviousKeyframe(videoTime, true); -// Keyframe nextPos = null; -// -// // Position interpolation -// nextPos = positionKeyframes.getNextKeyframe(videoTime, true); -// -// int lastPosStamp = lastPos.getRealTimestamp(); -// int nextPosStamp = (nextPos == null ? lastPos : nextPos).getRealTimestamp(); -// -// int diffLength = nextPosStamp - lastPosStamp; -// float diffPct = (float) (videoTime - lastPosStamp) / diffLength; -// if(Float.isInfinite(diffPct) || Float.isNaN(diffPct)) diffPct = 0; -// -// float totalPct = (positionKeyframes.indexOf(lastPos) + diffPct) / (posCount-1); -// pos = positionKeyframes.getInterpolatedValueForPathPosition(Math.max(0, Math.min(1, totalPct)), ReplayMod.replaySettings.isLinearMovement()); -// -// boolean spectateCamera = true; -// -// //check whether between two First Person Spectator Keyframes -// if(lastPos != null && nextPos != null) { -// if(lastPos.getValue() instanceof SpectatorData && nextPos.getValue() instanceof SpectatorData) { -// SpectatorData previousSpectatorData = (SpectatorData)lastPos.getValue(); -// SpectatorData nextSpectatorData = (SpectatorData)nextPos.getValue(); -// if(previousSpectatorData.getSpectatedEntityID() == nextSpectatorData.getSpectatedEntityID()) { -// if(previousSpectatorData.getSpectatingMethod() == SpectatorData.SpectatingMethod.FIRST_PERSON -// && nextSpectatorData.getSpectatingMethod() == SpectatorData.SpectatingMethod.FIRST_PERSON) { -// spectateCamera = false; -// } -// } -// } -// } -// -// if(spectateCamera) { -// // Make sure we're spectating the camera entity -// // We do not use .spectateCamera() as that method sets the position of the camera to the previous -// // entity, overriding our calculations -// ReplayHandler.spectateEntity(ReplayHandler.getCameraEntity()); -// -// if(pos != null) { -// ReplayHandler.setCameraTilt((float)pos.getRoll()); -// ReplayHandler.getCameraEntity().movePath(pos); -// mc.entityRenderer.fovModifierHand = mc.entityRenderer.fovModifierHandPrev = 1; -// } -// } else { -// ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(((SpectatorData)lastPos.getValue()).getSpectatedEntityID())); -// } - } - - private void updateTime(Timer timer, int framesDone) { - // TODO -// KeyframeList timeKeyframes = ReplayHandler.getTimeKeyframes(); -// -// int videoTime = framesDone * 1000 / fps; -// int timeCount = timeKeyframes.size(); -// -// // WARNING: The rest of this method contains some magic for which Marius is responsible -// // Time interpolation -// Keyframe lastTime = timeKeyframes.getPreviousKeyframe(videoTime, true); -// Keyframe nextTime = timeKeyframes.getNextKeyframe(videoTime, true); -// -// int lastTimeStamp = 0; -// int nextTimeStamp = 0; -// -// double curSpeed = 0; -// -// if(nextTime != null || lastTime != null) { -// if(nextTime != null && lastTime != null && nextTime.getRealTimestamp() == lastTime.getRealTimestamp()) { -// curSpeed = 0; -// } else { -// if(nextTime != null) { -// nextTimeStamp = nextTime.getRealTimestamp(); -// } else { -// nextTimeStamp = lastTime.getRealTimestamp(); -// } -// -// if(lastTime != null) { -// lastTimeStamp = lastTime.getRealTimestamp(); -// } else { -// lastTimeStamp = nextTime.getRealTimestamp(); -// } -// -// if(!(nextTime == null || lastTime == null)) { -// curSpeed = ((double)(((int)nextTime.getValue().value-(int)lastTime.getValue().value)))/((double)((nextTimeStamp-lastTimeStamp))); -// } -// -// if(lastTimeStamp == nextTimeStamp) { -// curSpeed = 0f; -// } -// } -// } -// -// int currentTimeDiff = nextTimeStamp - lastTimeStamp; -// int currentTime = videoTime - lastTimeStamp; -// -// float currentTimeStepPerc = (float)currentTime/(float)currentTimeDiff; //The percentage of the travelled path between the current timestamps -// if(Float.isInfinite(currentTimeStepPerc) || Float.isNaN(currentTimeStepPerc)) currentTimeStepPerc = 0; -// -// float timePos = (timeKeyframes.indexOf(lastTime) + currentTimeStepPerc) / (timeCount-1f); -// -// Integer replayTime = timeKeyframes.getInterpolatedValueForPathPosition(Math.max(0, Math.min(1, timePos)), true).asInt(); -// -// replaySender.sendPacketsTill(replayTime); -// -// if (curSpeed >= 0) { -// replaySender.setReplaySpeed(curSpeed); -// } -// -// // Update Timer -// EnchantmentTimer.increaseRecordingTime(1000 / fps); -// -// timer.elapsedPartialTicks += timer.timerSpeed * 20 / fps; -// timer.elapsedTicks = (int) timer.elapsedPartialTicks; -// timer.elapsedPartialTicks -= timer.elapsedTicks; -// timer.renderPartialTicks = timer.elapsedPartialTicks; - } - - private void tick() { - synchronized (mc.scheduledTasks) { - while (!mc.scheduledTasks.isEmpty()) { - ((FutureTask) mc.scheduledTasks.poll()).run(); - } - } - - mc.currentScreen = gui; - try { - mc.runTick(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void drawGui() { - do { - pushMatrix(); - clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - enableTexture2D(); - mc.getFramebuffer().bindFramebuffer(true); - mc.entityRenderer.setupOverlayRendering(); - - try { - gui.handleInput(); - } catch (IOException e) { - // That's a strange exception from this kind of method O_o - // It isn't actually thrown here, so we'll deal with it the easy way - throw new RuntimeException(e); - } - - ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); - int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; - int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; - gui.drawScreen(mouseX, mouseY, 0); - - mc.getFramebuffer().unbindFramebuffer(); - popMatrix(); - pushMatrix(); - mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); - popMatrix(); - - - // if not in high performance mode, update the gui size if screen size changed - // otherwise just swap the progress gui to screen - if (options.isHighPerformance()) { - Display.update(); - } else { - mc.updateDisplay(); - } - if (Mouse.isGrabbed()) { - Mouse.setGrabbed(false); - } - if (paused) { - try { - Thread.sleep(50); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - } - } while (paused); - } - - public int getFramesDone() { - return framesDone; - } - - @Override - public ReadableDimension getFrameSize() { - return new Dimension(options.getWidth(), options.getHeight()); - } - - public int getTotalFrames() { - return totalFrames; - } - - public int getVideoTime() { return framesDone * 1000 / fps; } - - public void setPaused(boolean paused) { - this.paused = paused; - } - - public boolean isPaused() { - return paused; - } - - public void cancel() { - videoWriter.abort(); - this.cancelled = true; - renderingPipeline.cancel(); - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CaptureData.java b/src/main/java/eu/crushedpixel/replaymod/video/capturer/CaptureData.java deleted file mode 100644 index 958193d2..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/CaptureData.java +++ /dev/null @@ -1,4 +0,0 @@ -package eu.crushedpixel.replaymod.video.capturer; - -public interface CaptureData { -} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Frame.java b/src/main/java/eu/crushedpixel/replaymod/video/rendering/Frame.java deleted file mode 100644 index 491700f2..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Frame.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.crushedpixel.replaymod.video.rendering; - -public interface Frame { - int getFrameId(); -} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java b/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java deleted file mode 100644 index 91d12aa0..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java +++ /dev/null @@ -1,80 +0,0 @@ -package eu.crushedpixel.replaymod.video.rendering; - -import eu.crushedpixel.replaymod.opengl.PixelBufferObject; -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.video.EntityRendererHandler; -import eu.crushedpixel.replaymod.video.capturer.*; -import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.OpenGlFrame; -import eu.crushedpixel.replaymod.video.frame.RGBFrame; -import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame; -import eu.crushedpixel.replaymod.video.processor.CubicToRGBProcessor; -import eu.crushedpixel.replaymod.video.processor.EquirectangularToRGBProcessor; -import eu.crushedpixel.replaymod.video.processor.OpenGlToRGBProcessor; -import eu.crushedpixel.replaymod.video.processor.StereoscopicToRGBProcessor; -import lombok.experimental.UtilityClass; - -@UtilityClass -public class Pipelines { - public enum Preset { - DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR - } - - public static Pipeline newPipeline(Preset preset, RenderInfo renderInfo, FrameConsumer consumer) { - switch (preset) { - case DEFAULT: - return newDefaultPipeline(renderInfo, consumer); - case STEREOSCOPIC: - return newStereoscopicPipeline(renderInfo, consumer); - case CUBIC: - return newCubicPipeline(renderInfo, consumer); - case EQUIRECTANGULAR: - return newEquirectangularPipeline(renderInfo, consumer); - } - throw new UnsupportedOperationException("Unknown preset: " + preset); - } - - public static Pipeline newDefaultPipeline(RenderInfo renderInfo, FrameConsumer consumer) { - RenderOptions options = renderInfo.getRenderOptions(); - FrameCapturer capturer; - if (PixelBufferObject.SUPPORTED) { - capturer = new SimplePboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo); - } else { - capturer = new SimpleOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo); - } - return new Pipeline(capturer, new OpenGlToRGBProcessor(), consumer); - } - - public static Pipeline newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer consumer) { - RenderOptions options = renderInfo.getRenderOptions(); - FrameCapturer capturer; - if (PixelBufferObject.SUPPORTED) { - capturer = new StereoscopicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo); - } else { - capturer = new StereoscopicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo); - } - return new Pipeline(capturer, new StereoscopicToRGBProcessor(), consumer); - } - - public static Pipeline newCubicPipeline(RenderInfo renderInfo, FrameConsumer consumer) { - RenderOptions options = renderInfo.getRenderOptions(); - FrameCapturer capturer; - if (PixelBufferObject.SUPPORTED) { - capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4); - } else { - capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4); - } - return new Pipeline(capturer, new CubicToRGBProcessor(), consumer); - } - - public static Pipeline newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer consumer) { - RenderOptions options = renderInfo.getRenderOptions(); - FrameCapturer capturer; - if (PixelBufferObject.SUPPORTED) { - capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4); - } else { - capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4); - } - return new Pipeline(capturer, new EquirectangularToRGBProcessor(options.getWidth() / 4), consumer); - } -} diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 1ebcdfd6..bf81b322 100755 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -81,6 +81,23 @@ "logoFile": "replaymod_logo.png", "parent": "replaymod", "screenshots": [], + "dependencies": ["required-after:replaymod-replay", "required-after:replaymod-render"] + }, + { + "modid": "replaymod-render", + "name": "Replay Mod - Render", + "description": "Render Module of the ReplayMod", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://replaymod.com", + "updateUrl": "https://replaymod.com/download", + "authorList": [ + "CrushedPixel", + "johni0702" + ], + "logoFile": "replaymod_logo.png", + "parent": "replaymod", + "screenshots": [], "dependencies": ["required-after:replaymod-replay"] }, { diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json new file mode 100644 index 00000000..164a61d2 --- /dev/null +++ b/src/main/resources/mixins.render.replaymod.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "com.replaymod.render.mixin", + "mixins": [ + "MixinEffectRenderer", + "MixinEntityRenderer", + "MixinRender", + "MixinRendererLivingEntity", + "MixinRenderGlobal", + "MixinRenderManager" + ], + "server": [], + "client": [], + "refmap": "mixins.replaymod.refmap.json" +} \ No newline at end of file diff --git a/src/main/resources/mixins.replaymod.json b/src/main/resources/mixins.replaymod.json index 9a3b623e..36242d53 100644 --- a/src/main/resources/mixins.replaymod.json +++ b/src/main/resources/mixins.replaymod.json @@ -2,14 +2,11 @@ "required": true, "package": "eu.crushedpixel.replaymod.mixin", "mixins": [ - "MixinEffectRenderer", "MixinEntityRenderer", "MixinGuiSpectator", "MixinMinecraft", "MixinPlayerControllerMP", - "MixinRender", "MixinRenderArrow", - "MixinRendererLivingEntity", "MixinRenderItem", "MixinRenderManager", "MixinTileEntityEndPortalRenderer",