Separate rendering into its own module

This commit is contained in:
johni0702
2016-06-14 17:01:32 +02:00
parent 65cfdd13ec
commit 25582b6a51
68 changed files with 1822 additions and 2635 deletions

View File

@@ -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<ListenableFutureTask> tasks = mc.scheduledTasks;

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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<RGBFrame> {
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<String> command = new ArrayList<String>();
command.add(options.getExportCommand());
List<String> 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<RGBFrame> {
}
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<RGBFrame> {
}
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() {

View File

@@ -0,0 +1,4 @@
package com.replaymod.render.capturer;
public interface CaptureData {
}

View File

@@ -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<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
public enum Data implements CaptureData {

View File

@@ -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<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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<OpenGlFrame, CaptureData> {

View File

@@ -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;

View File

@@ -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<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {

View File

@@ -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<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {

View File

@@ -1,4 +1,4 @@
package eu.crushedpixel.replaymod.video.capturer;
package com.replaymod.render.capturer;
import org.lwjgl.util.ReadableDimension;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<RenderSettings.RenderMethod> renderMethodDropdown =
new GuiDropdownMenu<RenderSettings.RenderMethod>().onSelection(new Consumer<Integer>() {
@Override
public void consume(Integer old) {
updateInputs();
}
}).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.RenderMethod.values());
{
for (Map.Entry<RenderSettings.RenderMethod, IGuiClickable> entry :
renderMethodDropdown.getDropdownEntries().entrySet()) {
entry.getValue().setTooltip(new GuiTooltip().setText(entry.getKey().getDescription()));
}
}
public final GuiDropdownMenu<RenderSettings.EncodingPreset> encodingPresetDropdown =
new GuiDropdownMenu<RenderSettings.EncodingPreset>().onSelection(new Consumer<Integer>() {
@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<String> bitRateUnit = new GuiDropdownMenu<String>()
.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<File>() {
@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<GuiPanel>() {
@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<GuiScreen>() {
@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<GuiPanel>() {
@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<String>() {
@Override
public void consume(String old) {
updateInputs();
}
});
videoHeight.onTextChanged(new Consumer<String>() {
@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<RenderSettings>() {
@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.");
}
}

View File

@@ -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<GuiPanel>() {
@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<GuiScreen>() {
@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<framesRendered; i++) {
renderTimes[currentIndex] = avgRenderTime;
currentIndex++;
if(currentIndex >= 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();
}
}
}

View File

@@ -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<Object> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
workerJailingQueue = new JailingQueue<Object>(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
*/

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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.*;

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
if (handler != null && handler.getOptions().isHideNameTags()) {
private void replayModRender_areAllNamesHidden(Entity entity, CallbackInfoReturnable<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && !handler.getSettings().isRenderNameTags()) {
ci.setReturnValue(false);
ci.cancel();
}

View File

@@ -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<Boolean> 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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
if (handler != null && handler.getOptions().isHideNameTags()) {
private void replayModRender_areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable<Boolean> 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);

View File

@@ -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;

View File

@@ -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<CubicOpenGlFrame, RGBFrame> {
@Override

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<StereoscopicOpenGlFrame, RGBFrame> {
@Override

View File

@@ -0,0 +1,5 @@
package com.replaymod.render.rendering;
public interface Frame {
int getFrameId();
}

View File

@@ -1,4 +1,4 @@
package eu.crushedpixel.replaymod.video.rendering;
package com.replaymod.render.rendering;
import java.io.Closeable;

View File

@@ -1,4 +1,4 @@
package eu.crushedpixel.replaymod.video.rendering;
package com.replaymod.render.rendering;
import java.io.Closeable;

View File

@@ -1,4 +1,4 @@
package eu.crushedpixel.replaymod.video.rendering;
package com.replaymod.render.rendering;
import java.io.Closeable;

View File

@@ -1,4 +1,4 @@
package eu.crushedpixel.replaymod.video.rendering;
package com.replaymod.render.rendering;
import lombok.RequiredArgsConstructor;
import net.minecraft.client.Minecraft;

View File

@@ -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<RGBFrame> 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<OpenGlFrame, RGBFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderSettings settings = renderInfo.getRenderSettings();
FrameCapturer<OpenGlFrame> 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<StereoscopicOpenGlFrame, RGBFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderSettings settings = renderInfo.getRenderSettings();
FrameCapturer<StereoscopicOpenGlFrame> 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<CubicOpenGlFrame, RGBFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderSettings settings = renderInfo.getRenderSettings();
FrameCapturer<CubicOpenGlFrame> 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<CubicOpenGlFrame, RGBFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderSettings settings = renderInfo.getRenderSettings();
FrameCapturer<CubicOpenGlFrame> 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);
}
}

View File

@@ -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<Integer> 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<SoundCategory, Float> 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<Keyframe> 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();
}
}

View File

@@ -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<SoftReference<ByteBuffer>> available = bufferPool.get(size);
if (available == null) {
available = new LinkedList<SoftReference<ByteBuffer>>();
available = new LinkedList<>();
bufferPool.put(size, available);
}
available.add(new SoftReference<ByteBuffer>(buffer));
available.add(new SoftReference<>(buffer));
}
}

View File

@@ -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<Integer> bound = new ThreadLocal<Integer>();
private static ThreadLocal<Integer> mapped = new ThreadLocal<Integer>();
private static ThreadLocal<Integer> bound = new ThreadLocal<>();
private static ThreadLocal<Integer> 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) {

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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()
};
}

View File

@@ -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<RendererSettings> rendererDropdown = new GuiDropdown<RendererSettings>(mc.fontRendererObj, 0, 0, 200, 5);
private GuiDropdown<EncodingPreset> encodingPresetDropdown = new GuiDropdown<EncodingPreset>(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<RendererSettings> settingsList = rendererDropdown.getAllElements();
for(int i=0; i<settingsList.size(); i++) {
if(defRS == settingsList.get(i)) {
rendererDropdown.setSelectionIndex(i);
break;
}
}
EncodingPreset preset = null;
for(EncodingPreset p : EncodingPreset.values()) {
if(p.getCommandLineArgs().equals(initialRenderOptions.getExportCommandArgs())) {
preset = p;
break;
}
}
List<EncodingPreset> presetList = encodingPresetDropdown.getAllElements();
if(preset != null) {
for(int i=0; i<presetList.size(); i++) {
if(preset == encodingPresetDropdown.getElement(i)) {
encodingPresetDropdown.setSelectionIndex(i);
break;
}
}
}
xRes.setValueQuietly(initialRenderOptions.getWidth());
yRes.setValueQuietly(initialRenderOptions.getHeight());
stableYaw.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[0]);
stablePitch.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[1]);
stableRoll.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[2]);
enableGreenscreen.setIsChecked(!initialRenderOptions.isDefaultSky());
renderNameTags.setIsChecked(!initialRenderOptions.isHideNameTags());
injectMetadata.setIsChecked(initialRenderOptions.isInject360Metadata());
framerateSlider.setFPS(initialRenderOptions.getFps());
String bitrateString = initialRenderOptions.getBitrate();
//trim away the "K" at the end of the returned String
int bitrateValue = Integer.valueOf(bitrateString.substring(0, bitrateString.length() - 1));
bitrateInput.setValueQuietly(bitrateValue);
if(!initialRenderOptions.isDefaultSky())
colorPicker.setPickerColor(initialRenderOptions.getSkyColor());
commandInput.setText(initialRenderOptions.getExportCommand());
if(!initialRenderOptions.getExportCommandArgs().isEmpty()) {
ffmpegArguments.setText(initialRenderOptions.getExportCommandArgs());
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
drawGradientRect(LEFT_BORDER, virtualY, width - LEFT_BORDER, virtualY + virtualHeight, -1072689136, -804253680);
this.drawCenteredString(fontRendererObj, tabNames[currentTab],
this.width / 2, virtualY + 5, Color.WHITE.getRGB());
if(currentTab == 2) {
String[] rows = StringUtils.splitStringInMultipleRows(I18n.format("replaymod.gui.rendersettings.ffmpeg.description"), 305);
int i = 0;
for(String row : rows) {
drawString(fontRendererObj, row, commandInput.xPosition, commandInput.yPosition + 30 + (15 * i), Color.WHITE.getRGB());
i++;
}
}
currentScreen.draw(mc, mouseX, mouseY);
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
currentScreen.mouseClick(mc, mouseX, mouseY, mouseButton);
}
@Override
protected void mouseReleased(int mouseX, int mouseY, int state) {
currentScreen.mouseRelease(mc, mouseX, mouseY, state);
}
@Override
protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
currentScreen.mouseDrag(mc, mouseX, mouseY, clickedMouseButton);
}
@Override
protected void keyTyped(char typedChar, int keyCode) throws IOException {
Point mousePos = MouseUtils.getMousePos();
currentScreen.buttonPressed(mc, mousePos.getX(), mousePos.getY(), typedChar, keyCode);
super.keyTyped(typedChar, keyCode);
}
@Override
public void updateScreen() {
xRes.updateCursorCounter();
yRes.updateCursorCounter();
bitrateInput.updateCursorCounter();
commandInput.updateCursorCounter();
ffmpegArguments.updateCursorCounter();
super.updateScreen();
}
private enum RendererSettings implements GuiEntryListEntry {
DEFAULT("default"),
STEREOSCOPIC("stereoscopic"),
CUBIC("cubic"),
EQUIRECTANGULAR("equirectangular");
private String name, desc;
RendererSettings(String name) {
this.name = "replaymod.gui.rendersettings.renderer."+name;
this.desc = this.name+".description";
}
@Override
public String toString() {
return I18n.format(name);
}
public String getDescription() { return I18n.format(desc); }
@Override
public String getDisplayString() {
return toString();
}
}
private void startRendering() {
RendererSettings r = rendererDropdown.getElement(rendererDropdown.getSelectionIndex());
RenderOptions options = new RenderOptions();
options.setLinearMovement(ReplayMod.replaySettings.isLinearMovement());
options.setWaitForChunks(true);
options.setFps(framerateSlider.getFPS());
ReplayMod.replaySettings.setVideoFramerate(framerateSlider.getFPS());
options.setBitrate(bitrateInput.getIntValue() + "K"); //Bitrate value is in Kilobytes
//remove the extension from the output file
File outputFile = outputFileChooser.getSelectedFile();
outputFile = new File(outputFile.getParent(), FilenameUtils.getBaseName(outputFile.getAbsolutePath()));
options.setOutputFile(outputFile);
if(enableGreenscreen.isChecked()) {
options.setSkyColor(colorPicker.getPickedColor());
}
options.setHideNameTags(!renderNameTags.isChecked());
if(injectMetadata.enabled) {
options.setInject360Metadata(injectMetadata.isChecked());
}
options.setWidth(getWidthSetting());
options.setHeight(getHeightSetting());
Pipelines.Preset pipePreset = Pipelines.Preset.DEFAULT;
if(r == RendererSettings.DEFAULT) {
pipePreset = Pipelines.Preset.DEFAULT;
} else if(r == RendererSettings.STEREOSCOPIC) {
pipePreset = Pipelines.Preset.STEREOSCOPIC;
} else if(r == RendererSettings.CUBIC) {
pipePreset = Pipelines.Preset.CUBIC;
} else if(r == RendererSettings.EQUIRECTANGULAR) {
pipePreset = Pipelines.Preset.EQUIRECTANGULAR;
}
options.setMode(pipePreset);
options.setIgnoreCameraRotation(stableYaw.enabled && stableYaw.isChecked(),
stablePitch.enabled && stablePitch.isChecked(), stableRoll.enabled && stableRoll.isChecked());
if (isCtrlKeyDown()) {
options.setHighPerformance(true);
}
if(commandInput.getText().trim().length() > 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);
}
}
}

View File

@@ -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<framesRendered; i++) {
renderTimes[currentIndex] = avgRenderTime;
currentIndex++;
if(currentIndex >= 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();
}
}
}

View File

@@ -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) {

View File

@@ -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()) {

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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<AdvancedPosition> positionKeyframes;
private KeyframeList<TimestampValue> 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<AdvancedPosition> 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<AdvancedPosition> lastPos = positionKeyframes.getPreviousKeyframe(videoTime, true);
// Keyframe<AdvancedPosition> 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<TimestampValue> 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<TimestampValue> lastTime = timeKeyframes.getPreviousKeyframe(videoTime, true);
// Keyframe<TimestampValue> 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();
}
}

View File

@@ -1,4 +0,0 @@
package eu.crushedpixel.replaymod.video.capturer;
public interface CaptureData {
}

View File

@@ -1,5 +0,0 @@
package eu.crushedpixel.replaymod.video.rendering;
public interface Frame {
int getFrameId();
}

View File

@@ -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<RGBFrame> 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<OpenGlFrame, RGBFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderOptions options = renderInfo.getRenderOptions();
FrameCapturer<OpenGlFrame> capturer;
if (PixelBufferObject.SUPPORTED) {
capturer = new SimplePboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
} else {
capturer = new SimpleOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
}
return new Pipeline<OpenGlFrame, RGBFrame>(capturer, new OpenGlToRGBProcessor(), consumer);
}
public static Pipeline<StereoscopicOpenGlFrame, RGBFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderOptions options = renderInfo.getRenderOptions();
FrameCapturer<StereoscopicOpenGlFrame> capturer;
if (PixelBufferObject.SUPPORTED) {
capturer = new StereoscopicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
} else {
capturer = new StereoscopicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
}
return new Pipeline<StereoscopicOpenGlFrame, RGBFrame>(capturer, new StereoscopicToRGBProcessor(), consumer);
}
public static Pipeline<CubicOpenGlFrame, RGBFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderOptions options = renderInfo.getRenderOptions();
FrameCapturer<CubicOpenGlFrame> 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<CubicOpenGlFrame, RGBFrame>(capturer, new CubicToRGBProcessor(), consumer);
}
public static Pipeline<CubicOpenGlFrame, RGBFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderOptions options = renderInfo.getRenderOptions();
FrameCapturer<CubicOpenGlFrame> 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<CubicOpenGlFrame, RGBFrame>(capturer, new EquirectangularToRGBProcessor(options.getWidth() / 4), consumer);
}
}

View File

@@ -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"]
},
{

View File

@@ -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"
}

View File

@@ -2,14 +2,11 @@
"required": true,
"package": "eu.crushedpixel.replaymod.mixin",
"mixins": [
"MixinEffectRenderer",
"MixinEntityRenderer",
"MixinGuiSpectator",
"MixinMinecraft",
"MixinPlayerControllerMP",
"MixinRender",
"MixinRenderArrow",
"MixinRendererLivingEntity",
"MixinRenderItem",
"MixinRenderManager",
"MixinTileEntityEndPortalRenderer",