Separate rendering into its own module
This commit is contained in:
414
src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
Normal file
414
src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
284
src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java
Normal file
284
src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user