package com.replaymod.render.gui; import com.replaymod.core.utils.Utils; 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.function.Tickable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.NativeImage; import net.minecraft.client.resources.I18n; import net.minecraft.util.ResourceLocation; import java.nio.ByteBuffer; public class GuiVideoRenderer extends GuiScreen implements Tickable { private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg"); private final VideoRenderer renderer; public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.rendering.title"); public final GuiPanel imagePanel = new GuiPanel(){ @Override public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { if (previewCheckbox.isChecked()) { renderPreview(renderer, size); } else { renderNoPreview(renderer, size); } } }.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); public final GuiCheckbox previewCheckbox = new GuiCheckbox().setI18nLabel("replaymod.gui.rendering.preview"); public final GuiLabel renderTime = new GuiLabel(); public final GuiLabel remainingTime = new GuiLabel(); public final GuiProgressBar progressBar = new GuiProgressBar(); public final GuiPanel buttonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)); public final GuiButton pauseButton = new GuiButton(buttonPanel).onClick(new Runnable() { @Override public void run() { if (renderer.isPaused()) { pauseButton.setI18nLabel("replaymod.gui.rendering.pause"); renderer.setPaused(false); } else { pauseButton.setI18nLabel("replaymod.gui.rendering.resume"); renderer.setPaused(true); } } }).setI18nLabel("replaymod.gui.rendering.pause").setSize(150, 20); public final GuiButton cancelButton = new GuiButton(buttonPanel){ boolean waitingForConfirmation; @Override public boolean mouseClick(ReadablePoint position, int button) { boolean result = super.mouseClick(position, button); if (waitingForConfirmation && !result) { setI18nLabel("replaymod.gui.rendering.cancel"); waitingForConfirmation = false; } return result; } @Override public void onClick() { super.onClick(); if (!waitingForConfirmation) { setI18nLabel("replaymod.gui.rendering.cancel.callback"); waitingForConfirmation = true; } else { renderer.cancel(); } } }.setI18nLabel("replaymod.gui.rendering.cancel").setSize(150, 20); private DynamicTexture previewTexture; private boolean previewTextureDirty; { final GuiPanel contentPanel = new GuiPanel(this).setLayout(new CustomLayout() { @Override protected void layout(GuiPanel container, int width, int height) { size(progressBar, width, 20); pos(title, width / 2 - width(title) / 2, 0); pos(imagePanel, 0, y(title) + height(title) + 5); pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - height(buttonPanel)); pos(progressBar, width / 2 - width(progressBar) / 2, y(buttonPanel) - 5 - height(progressBar)); pos(renderTime, 0, y(progressBar) - 2 - height(renderTime)); pos(remainingTime, width - width(remainingTime), y(progressBar) - 2 - height(renderTime)); pos(previewCheckbox, width / 2 - width(previewCheckbox) / 2, y(renderTime) - 10 - height(previewCheckbox)); size(imagePanel, width, y(previewCheckbox) - 5 - y(imagePanel)); } }).addElements(null, title, imagePanel, previewCheckbox, renderTime, remainingTime, progressBar, buttonPanel); setLayout(new CustomLayout() { @Override protected void layout(GuiScreen container, int width, int height) { // Make sure we have some space around our central GUI components pos(contentPanel, 5, 3); size(contentPanel, width - 10, height - 10); } }); setBackground(Background.DIRT); } public GuiVideoRenderer(VideoRenderer renderer) { this.renderer = renderer; } //the total render time private int renderTimeTaken = 0; //the time at which the Screen was last updated private long prevTime = -1; //the time at which the rendering of the current frame started private long frameStartTime = -1; //the amount of frames that were rendered when the time left was last updated private int prevRenderedFrames = 0; //the estimated render time that is left (in seconds) private int renderTimeLeft = 0; //each of the durations the rendering process took for the last 50 frames private int[] renderTimes = new int[50]; //the algorithm's current position in the renderTimes array private int currentIndex = 0; @Override public void tick() { long current = System.currentTimeMillis(); //first, update the total render time (only if rendering is not paused and has already started) if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) { renderTimeTaken += (current - prevTime); } else { //if the rendering process is paused, we have to update the frame start time to prevent huge render times //for the currently rendered frame(s) frameStartTime = current; } //always update prevTime, so we don't get huge time jumps when unpausing the rendering process prevTime = current; //calculate estimated time left //if the amount of rendered frames has increased since the last update if(prevRenderedFrames < renderer.getFramesDone()) { //we don't include the first frame in our calculations, //as setting up the rendering process takes a few seconds if(prevRenderedFrames > 0) { //calculate the amount of frames that have been rendered since the last update int framesRendered = renderer.getFramesDone() - prevRenderedFrames; //calculate the time it took to render these frames int renderTime = (int)(current - frameStartTime); //calculate the average time it took for each of these frames to render int avgRenderTime = renderTime/framesRendered; //add all of the average render times to the render times array for(int i=0; i= renderTimes.length) currentIndex = 0; } //the renderTimes array initially contains lots of zeros, //so we count the amount of valid timespans int validValues = 0; int totalTime = 0; for(int i : renderTimes) { if(i > 0) { totalTime += i; validValues++; } } //calculate the average render time for the previous [up to 50] frames float averageRenderTime = validValues > 0 ? totalTime / validValues : 0; //calculate the remaining render time in seconds renderTimeLeft = Math.round((averageRenderTime * (renderer.getTotalFrames() - renderer.getFramesDone())) / 1000); } //set the render start time of the next frame(s) to the current timestamp frameStartTime = current; //update the amount of rendered frames for the last calculation prevRenderedFrames = renderer.getFramesDone(); } renderTime.setText(I18n.format("replaymod.gui.rendering.timetaken") + ": " + secToString(renderTimeTaken/1000)); remainingTime.setText(I18n.format("replaymod.gui.rendering.timeleft") + ": " + secToString(renderTimeLeft)); int framesDone = renderer.getFramesDone(), framesTotal = renderer.getTotalFrames(); progressBar.setI18nLabel("replaymod.gui.rendering.progress", framesDone, framesTotal); progressBar.setProgress((float) framesDone / framesTotal); } 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) { //#if MC>=11300 previewTexture = new DynamicTexture(videoWidth, videoHeight, true); //#else //$$ previewTexture = new DynamicTexture(videoWidth, videoHeight); //#endif } 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 = Utils.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) { //#if MC>=11300 NativeImage data = previewTexture.getTextureData(); assert data != null; for (int x = 0; x < data.getWidth(); x++) { for (int y = 0; y < data.getHeight(); y++) { int value = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); data.setPixelRGBA(x, y, value); } } //#else //$$ int[] data = previewTexture.getTextureData(); //$$ // Optifine changes the texture data array to be three times as long (for use by shaders), //$$ // we only want to initialize the first third which is why we use the length of the buffer instead //$$ // of the length of the data array //$$ for (int i = 0; buffer.remaining() > 0; i++) { //$$ data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); //$$ } //#endif previewTextureDirty = true; } buffer.reset(); } } }