diff --git a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java index f30459f8..97c5f882 100755 --- a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java +++ b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java @@ -176,6 +176,8 @@ public class ReplayMod { String waitForChunks = System.getProperty("replaymod.render.waitforchunks"); String linearMovement = System.getProperty("replaymod.render.linearmovement"); String skyColor = System.getProperty("replaymod.render.skycolor"); + String width = System.getProperty("replaymod.render.width"); + String height = System.getProperty("replaymod.render.height"); final RenderOptions options = new RenderOptions(); if (quality != null) { options.setQuality(Float.parseFloat(quality)); @@ -196,6 +198,12 @@ public class ReplayMod { options.setSkyColor(Integer.parseInt(skyColor)); } } + if (width != null) { + options.setWidth(Integer.parseInt(width)); + } + if (height != null) { + options.setHeight(Integer.parseInt(height)); + } FrameRenderer renderer; if (type != null) { @@ -203,13 +211,6 @@ public class ReplayMod { type = parts[0].toUpperCase(); if ("NORMAL".equals(type) || "DEFAULT".equals(type)) { renderer = new DefaultFrameRenderer(options); - } else if ("TILED".equals(type)) { - if (parts.length < 3) { - throw new IllegalArgumentException("Tiled renderer requires width and height."); - } - int width = Integer.parseInt(parts[1]); - int height = Integer.parseInt(parts[2]); - renderer = new TilingFrameRenderer(options, width, height); } else if ("STEREO".equals(type) || "STEREOSCOPIC".equals(type)) { renderer = new StereoscopicFrameRenderer(options); } else if ("CUBIC".equals(type)) { diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/EntityRendererCT.java b/src/main/java/eu/crushedpixel/replaymod/coremod/EntityRendererCT.java new file mode 100644 index 00000000..41ea7eca --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/coremod/EntityRendererCT.java @@ -0,0 +1,127 @@ +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 EntityRenderer class: + * - Adds a new field 'CustomEntityRenderer hook' + * - Moves code of 'loadShader' into 'loadShader$Original' + * - Creates 'loadShader' method (see {@link #createLoadShader(ClassNode, MethodNode)} ()} + * + * Transforms the CustomEntityRenderer class: + * - Method 'original_loadShader' calls 'loadShader$Original' + */ +public class EntityRendererCT implements IClassTransformer { + + private static final String HOOK = "eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer"; + private static final String HOOK_JVM = HOOK.replace('.', '/'); + private static final String HOOK_TYPE = "L" + HOOK_JVM + ";"; + private static final String ENTITY_RENDERER = "net.minecraft.client.renderer.EntityRenderer"; + private static final String ENTITY_RENDERER_JVM = ENTITY_RENDERER.replace('.', '/'); + private static final String ENTITY_RENDERER_TYPE = "L" + ENTITY_RENDERER_JVM + ";"; + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if (ENTITY_RENDERER.equals(transformedName)) { + if (name.equals(transformedName)) { + return transformEntityRenderer(bytes, "loadShader", "(Lnet/minecraft/util/ResourceLocation;)V"); + } else { + return transformEntityRenderer(bytes, "a", "(Loa;)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 method + for (MethodNode m : classNode.methods) { + if ("original_loadShader".equals(m.name)) { + m.instructions.clear(); + InsnList iter = m.instructions; + iter.add(new VarInsnNode(ALOAD, 0)); // this + iter.add(new FieldInsnNode(GETFIELD, classNode.name, "proxied", ENTITY_RENDERER_TYPE)); + iter.add(new VarInsnNode(ALOAD, 1)); // resourceLocation + iter.add(new MethodInsnNode(INVOKEVIRTUAL, ENTITY_RENDERER_JVM, "loadShader$Original", m.desc, false)); + iter.add(new InsnNode(RETURN)); + } + } + + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(classWriter); + return classWriter.toByteArray(); + } + + private byte[] transformEntityRenderer(byte[] bytes, String name_loadShader, String desc_loadShader) { + 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 method + MethodNode setupTerrain = null; + for (MethodNode m : classNode.methods) { + if (name_loadShader.equals(m.name) && desc_loadShader.equals(m.desc)) { + setupTerrain = m; + } + } + if (setupTerrain == null) { + throw new NoSuchMethodError("loadShader"); + } + + // Generate new method + classNode.methods.add(createLoadShader(classNode, setupTerrain)); + + // Rename original method + setupTerrain.name = "loadShader$Original"; + + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(classWriter); + return classWriter.toByteArray(); + } + + /** + * public void loadShader(ResourceLocation resourceLocation) { + * if (this.hook == null) { + * loadShader$Original(resourceLocation); + * return; + * } + * hook.loadShader(resourceLocation); + * } + */ + private MethodNode createLoadShader(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)); // resourceLocation + list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "loadShader$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)); // resourceLocation + list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "loadShader", org.desc, false)); + list.add(new InsnNode(RETURN)); + + return method; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java index 492e883e..bdc3df25 100755 --- a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java +++ b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java @@ -11,6 +11,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin { return new String[]{ CameraRollCT.class.getName(), ForceChunkLoadingCT.class.getName(), + EntityRendererCT.class.getName(), SoundManagerCT.class.getName(), EnchantmentTimerCT.class.getName() }; diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java index 57ff8295..4cdf1591 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiRenderSettings.java @@ -59,15 +59,51 @@ public class GuiRenderSettings extends GuiScreen { fontRendererObj, 0, 0, 200, 5); rendererDropdown.addSelectionListener(new RendererDropdownListener()); + int i = 0; + for (RendererSettings r : RendererSettings.values()) { + rendererDropdown.addElement(r); + rendererDropdown.setHoverText(i, r.getDescription()); + i++; + } + renderButton = new GuiButton(GuiConstants.RENDER_SETTINGS_RENDER_BUTTON, 0, 0, I18n.format("replaymod.gui.render")); cancelButton = new GuiButton(GuiConstants.RENDER_SETTINGS_CANCEL_BUTTON, 0, 0, I18n.format("replaymod.gui.cancel")); advancedButton = new GuiButton(GuiConstants.RENDER_SETTINGS_ADVANCED_BUTTON, 0, 0, I18n.format("replaymod.gui.rendersettings.advanced")); customResolution = new GuiCheckBox(GuiConstants.RENDER_SETTINGS_RESOLUTION_CHECKBOX, 0, 0, I18n.format("replaymod.gui.rendersettings.customresolution"), false); - customResolution.enabled = false; - xRes = new GuiNumberInput(GuiConstants.RENDER_SETTINGS_RESOLUTION_X, fontRendererObj, 0, 0, 50, 1, 100000, mc.displayWidth, false); - yRes = new GuiNumberInput(GuiConstants.RENDER_SETTINGS_RESOLUTION_Y, fontRendererObj, 0, 0, 50, 1, 100000, mc.displayHeight, false); + xRes = new GuiNumberInput(GuiConstants.RENDER_SETTINGS_RESOLUTION_X, fontRendererObj, 0, 0, 50, 1, 100000, mc.displayWidth, false) { + @Override + public void setCursorPosition(int p_146190_1_) { + super.setCursorPosition(p_146190_1_); + 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 = new GuiNumberInput(GuiConstants.RENDER_SETTINGS_RESOLUTION_Y, fontRendererObj, 0, 0, 50, 1, 100000, mc.displayHeight, false) { + @Override + public void setCursorPosition(int p_146190_1_) { + super.setCursorPosition(p_146190_1_); + 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.setEnabled(false); yRes.setEnabled(false); @@ -101,13 +137,6 @@ public class GuiRenderSettings extends GuiScreen { youtubeExport = new GuiCheckBox(GuiConstants.RENDER_SETTINGS_YOUTUBE_READY, 0, 0, I18n.format("replaymod.gui.rendersettings.exportyoutube"), true); ignoreCamDir.enabled = youtubeExport.enabled = false; - int i = 0; - for(RendererSettings r : RendererSettings.values()) { - rendererDropdown.addElement(r); - rendererDropdown.setHoverText(i, r.getDescription()); - i++; - } - permanentButtons.add(advancedButton); permanentButtons.add(renderButton); permanentButtons.add(cancelButton); @@ -320,7 +349,6 @@ public class GuiRenderSettings extends GuiScreen { private enum RendererSettings { DEFAULT("default"), - TILED("tiled"), STEREOSCOPIC("stereoscopic"), CUBIC("cubic"), EQUIRECTANGULAR("equirectangular"); @@ -363,10 +391,11 @@ public class GuiRenderSettings extends GuiScreen { options.setSkyColor(colorPicker.getPickedColor()); } + options.setWidth(getWidthSetting()); + options.setHeight(getHeightSetting()); + if(r == RendererSettings.DEFAULT) { renderer = new DefaultFrameRenderer(options); - } else if(r == RendererSettings.TILED) { - renderer = new TilingFrameRenderer(options, getWidthSetting(), getHeightSetting()); } else if(r == RendererSettings.STEREOSCOPIC) { renderer = new StereoscopicFrameRenderer(options); } else if(r == RendererSettings.CUBIC) { @@ -393,32 +422,18 @@ public class GuiRenderSettings extends GuiScreen { RendererSettings s = rendererDropdown.getElement(selectionIndex); if(s == RendererSettings.DEFAULT) { - customResolution.enabled = false; - xRes.setEnabled(false); - yRes.setEnabled(false); - youtubeExport.enabled = ignoreCamDir.enabled = false; - } else if(s == RendererSettings.TILED) { - customResolution.enabled = true; - xRes.setEnabled(customResolution.isChecked()); - yRes.setEnabled(customResolution.isChecked()); youtubeExport.enabled = ignoreCamDir.enabled = false; } else if(s == RendererSettings.STEREOSCOPIC) { - customResolution.enabled = false; - xRes.setEnabled(false); - yRes.setEnabled(false); youtubeExport.enabled = ignoreCamDir.enabled = false; } else if(s == RendererSettings.CUBIC) { - customResolution.enabled = false; - xRes.setEnabled(false); - yRes.setEnabled(false); youtubeExport.enabled = true; ignoreCamDir.enabled = false; } else if(s == RendererSettings.EQUIRECTANGULAR) { - customResolution.enabled = false; - xRes.setEnabled(false); - yRes.setEnabled(false); youtubeExport.enabled = ignoreCamDir.enabled = true; } + + xRes.setCursorPositionEnd(); + yRes.setCursorPositionEnd(); } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java b/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java index e447cab6..5d19defc 100644 --- a/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/renderer/SpectatorRenderer.java @@ -60,7 +60,7 @@ public class SpectatorRenderer { float f1 = 0.07F; - if(this.mc.gameSettings.anaglyph) { + if (renderPass != 2) { GlStateManager.translate((float)(-(renderPass * 2 - 1)) * f1, 0.0F, 0.0F); } @@ -70,7 +70,7 @@ public class SpectatorRenderer { GlStateManager.matrixMode(GL11.GL_MODELVIEW); GlStateManager.loadIdentity(); - if(this.mc.gameSettings.anaglyph) { + if (renderPass != 2) { GlStateManager.translate((float)(renderPass * 2 - 1) * 0.1F, 0.0F, 0.0F); } @@ -540,4 +540,9 @@ public class SpectatorRenderer { GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); GlStateManager.disableBlend(); } + + public void cleanup() { + MinecraftForge.EVENT_BUS.unregister(this); + FMLCommonHandler.instance().bus().unregister(this); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java b/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java index 410888ba..720c154f 100644 --- a/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java +++ b/src/main/java/eu/crushedpixel/replaymod/settings/RenderOptions.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.settings; import eu.crushedpixel.replaymod.video.frame.FrameRenderer; +import net.minecraft.client.Minecraft; import static org.apache.commons.lang3.Validate.*; @@ -8,9 +9,16 @@ public final class RenderOptions { private FrameRenderer renderer; private float quality = 0.5f; private int fps = 30; + + // Advanced private boolean waitForChunks = true; private boolean isLinearMovement = false; private int skyColor = -1; + private int width = Minecraft.getMinecraft().displayWidth; + private int height = Minecraft.getMinecraft().displayHeight; + + // Highly advanced + private int writerQueueSize = Integer.parseInt(System.getProperty("replaymod.render.writerQueueSize", "1")); public FrameRenderer getRenderer() { return renderer; @@ -66,6 +74,30 @@ public final class RenderOptions { this.skyColor = skyColor; } + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getWriterQueueSize() { + return writerQueueSize; + } + + public void setWriterQueueSize(int writerQueueSize) { + this.writerQueueSize = writerQueueSize; + } + public RenderOptions copy() { RenderOptions copy = new RenderOptions(); copy.renderer = this.renderer; @@ -74,6 +106,9 @@ public final class RenderOptions { copy.waitForChunks = this.waitForChunks; copy.isLinearMovement = this.isLinearMovement; copy.skyColor = this.skyColor; + copy.width = this.width; + copy.height = this.height; + copy.writerQueueSize = this.writerQueueSize; return copy; } } diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java new file mode 100644 index 00000000..6dc50254 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/utils/OpenGLUtils.java @@ -0,0 +1,41 @@ +package eu.crushedpixel.replaymod.utils; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +public class OpenGLUtils { + public static final int VIEWPORT_MAX_WIDTH; + public static final int VIEWPORT_MAX_HEIGHT; + + static { + IntBuffer buffer = BufferUtils.createIntBuffer(16); + GL11.glGetInteger(GL11.GL_MAX_VIEWPORT_DIMS, buffer); + VIEWPORT_MAX_WIDTH = buffer.get(); + VIEWPORT_MAX_HEIGHT = buffer.get(); + } + + public static void openGlBytesToBufferedImage(ByteBuffer buffer, int bufferWidth, BufferedImage image, int offsetX, int offsetY) { + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + int imageWidth = image.getWidth(); + int bufferSize = buffer.remaining() / 3; + // Read the OpenGL image row by row from right to left (flipped horizontally) + for (int i = bufferSize - 1; i >= 0; i--) { + // Coordinates in the final image + int x = offsetX + bufferWidth - i % bufferWidth - 1; // X coord of OpenGL image has to be flipped first + int y = offsetY + i / bufferWidth; + if (x >= imageWidth || y * imageWidth >= pixels.length) { + buffer.position(buffer.position() + 3); // Pixel would end up outside of image + continue; + } + // Write to image (row by row, left to right) + pixels[y * imageWidth + x] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | buffer.get() & 0xff; + } + buffer.rewind(); + } + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java index 029cf53f..b0cb258c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java @@ -50,14 +50,12 @@ public class VideoRenderer { private int totalFrames; private final GuiVideoRenderer gui; - private boolean frameRendererUpdatesGui; private boolean paused; private boolean cancelled; public VideoRenderer(RenderOptions options) throws IOException { this.frameRenderer = options.getRenderer(); - this.videoWriter = new VideoWriter(frameRenderer.getVideoWidth(), frameRenderer.getVideoHeight(), - options.getFps(), options.getQuality(), 10); + this.videoWriter = new VideoWriter(options); this.gui = new GuiVideoRenderer(this); this.replaySender = ReplayMod.replaySender; this.options = options; @@ -100,9 +98,7 @@ public class VideoRenderer { frame(timer); framesDone++; - if (!frameRendererUpdatesGui) { - drawGui(); - } + drawGui(); System.gc(); } @@ -149,12 +145,6 @@ public class VideoRenderer { } time.prepare(); - frameRendererUpdatesGui = frameRenderer.setRenderPreviewCallback(new Runnable() { - @Override - public void run() { - drawGui(); - } - }); frameRenderer.setup(); ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java b/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java index 59ba0894..21ea6783 100755 --- a/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.video; import com.google.common.base.Preconditions; +import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.utils.ReplayFileIO; import org.monte.media.*; import org.monte.media.FormatKeys.MediaType; @@ -42,12 +43,8 @@ public class VideoWriter { private final Condition noLongerEmptyCondition = lock.newCondition(); private final Condition noLongerFullCondition = lock.newCondition(); - public VideoWriter(int width, int height, int fps, float quality) throws IOException { - this(width, height, fps, quality, Integer.MAX_VALUE); - } - - public VideoWriter(int width, int height, int fps, float quality, int queueLimit) throws IOException { - this.queueLimit = queueLimit; + public VideoWriter(RenderOptions options) throws IOException { + this.queueLimit = options.getWriterQueueSize(); this.toWrite = new LinkedList(); File folder = ReplayFileIO.getRenderFolder(); @@ -58,11 +55,11 @@ public class VideoWriter { out = Registry.getInstance().getWriter(file); Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO, FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG, - FormatKeys.FrameRateKey, new Rational(fps, 1), - VideoFormatKeys.WidthKey, width, - VideoFormatKeys.HeightKey, height, + FormatKeys.FrameRateKey, new Rational(options.getFps(), 1), + VideoFormatKeys.WidthKey, options.getWidth(), + VideoFormatKeys.HeightKey, options.getHeight(), VideoFormatKeys.DepthKey, 24, - VideoFormatKeys.QualityKey, quality); + VideoFormatKeys.QualityKey, options.getQuality()); track = out.addTrack(format); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java index 61cce7bd..02b47c06 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java @@ -33,8 +33,8 @@ public class CubicEntityRenderer extends CustomEntityRenderer { private final boolean stable; private Direction direction; - public CubicEntityRenderer(RenderOptions options, boolean stable) { - super(options); + public CubicEntityRenderer(RenderOptions options, int frameSize, boolean stable) { + super(options, frameSize, frameSize); this.stable = stable; } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/CustomEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/CustomEntityRenderer.java index 637f0ea4..88fe53e1 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/entity/CustomEntityRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/CustomEntityRenderer.java @@ -3,6 +3,8 @@ package eu.crushedpixel.replaymod.video.entity; import eu.crushedpixel.replaymod.renderer.SpectatorRenderer; import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.settings.RenderOptions; +import eu.crushedpixel.replaymod.utils.OpenGLUtils; +import eu.crushedpixel.replaymod.video.entity.strategy.*; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.renderer.*; @@ -14,53 +16,103 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.EnumWorldBlockLayer; import net.minecraft.util.MathHelper; +import net.minecraft.util.ResourceLocation; import org.lwjgl.util.glu.Project; +import java.awt.image.BufferedImage; + import static net.minecraft.client.renderer.GlStateManager.*; import static org.lwjgl.opengl.GL11.*; public abstract class CustomEntityRenderer { - protected final Minecraft mc = Minecraft.getMinecraft(); - protected final EntityRenderer proxied = mc.entityRenderer; + public final Minecraft mc = Minecraft.getMinecraft(); + public final EntityRenderer proxied = mc.entityRenderer; protected final SpectatorRenderer spectatorRenderer = new SpectatorRenderer(){ @Override protected void gluPerspective(float fovY, float aspect, float zNear, float zFar) { CustomEntityRenderer.this.gluPerspective(fovY, aspect, zNear, zFar); } }; + protected final FrameRenderingStrategy renderingStrategy; + public GluPerspectiveHook gluPerspectiveHook; + public LoadShaderHook loadShaderHook; protected int frameCount; protected final RenderOptions options; + public final int resultWidth; + public final int resultHeight; + public CustomEntityRenderer(RenderOptions options) { + this(options, options.getWidth(), options.getHeight()); + } + + public CustomEntityRenderer(RenderOptions options, int resultWidth, int resultHeight) { this.options = options; - } + this.resultWidth = resultWidth; + this.resultHeight = resultHeight; - protected void gluPerspective(float fovY, float aspect, float zNear, float zFar) { - Project.gluPerspective(fovY, aspect, zNear, zFar); - } - - public void updateCameraAndRender(float renderPartialTicks) { - if (mc.theWorld != null) { - renderWorld(renderPartialTicks, 0); - - if (OpenGlHelper.shadersSupported) { - mc.renderGlobal.renderEntityOutlineFramebuffer(); - - if (proxied.theShaderGroup != null && proxied.useShader) { - matrixMode(GL_TEXTURE); - pushMatrix(); - loadIdentity(); - proxied.theShaderGroup.loadShaderGroup(renderPartialTicks); - popMatrix(); - } - - mc.getFramebuffer().bindFramebuffer(true); + if (OpenGlHelper.isFramebufferEnabled()) { + if (resultWidth > OpenGLUtils.VIEWPORT_MAX_WIDTH || resultHeight > OpenGLUtils.VIEWPORT_MAX_HEIGHT) { + // Video resolution only limited by available RAM and disk size + renderingStrategy = new TiledFrameBufferRenderingStrategy(this); + } else { + // This strategy supports shader + renderingStrategy = new VanillaFrameBufferRenderingStrategy(this); } + } else { + if (resultWidth > mc.displayHeight || resultHeight > mc.displayHeight) { + // Video resolution only limited by available RAM and disk size + renderingStrategy = new TiledReadPixelsRenderingStrategy(this); + } else { + // Simplest and fastest strategy if frame buffers aren't supported + renderingStrategy = new VanillaReadPixelsRenderingStrategy(this); + } + } + System.out.println("CustomEntityRenderer using " + renderingStrategy); + } + + @SuppressWarnings("unused") // Method called by ASM hook + protected void loadShader(ResourceLocation resourceLocation) { + if (loadShaderHook != null) { + loadShaderHook.loadShader(resourceLocation); + } else { + original_loadShader(resourceLocation); } } - protected void renderWorld(float partialTicks, long finishTimeNano) { + @SuppressWarnings("unused") + protected final void original_loadShader(ResourceLocation resourceLocation) { + // Method body generated by ASM + } + + protected void gluPerspective(float fovY, float aspect, float zNear, float zFar) { + if (gluPerspectiveHook == null) { + Project.gluPerspective(fovY, aspect, zNear, zFar); + } else { + gluPerspectiveHook.gluPerspective(fovY, aspect, zNear, zFar); + } + } + + public void renderFrame(float partialTicks, BufferedImage into, int x, int y) { + if (mc.theWorld != null) { + renderingStrategy.renderFrame(partialTicks, into, x, y); + } + } + + public void withDisplaySize(int displayWidth, int displayHeight, Runnable runnable) { + final int prevWidth = mc.displayWidth; + final int prevHeight = mc.displayHeight; + mc.displayWidth = displayWidth; + mc.displayHeight = displayHeight; + + runnable.run(); + + mc.displayWidth = prevWidth; + mc.displayHeight = prevHeight; + } + + public void renderWorld(float partialTicks, long finishTimeNano) { proxied.updateLightmap(partialTicks); enableDepth(); @@ -247,6 +299,11 @@ public abstract class CustomEntityRenderer { } } + public void cleanup() { + renderingStrategy.cleanup(); + spectatorRenderer.cleanup(); + } + public static final class NoCullingCamera implements ICamera { @Override @@ -259,4 +316,12 @@ public abstract class CustomEntityRenderer { } } + + public static interface GluPerspectiveHook { + void gluPerspective(float fovY, float aspect, float zNear, float zFar); + } + + public static interface LoadShaderHook { + void loadShader(ResourceLocation resourceLocation); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/StereoscopicEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/StereoscopicEntityRenderer.java index dcff00c2..5c9597ab 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/entity/StereoscopicEntityRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/StereoscopicEntityRenderer.java @@ -4,6 +4,8 @@ import eu.crushedpixel.replaymod.settings.RenderOptions; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.MathHelper; +import java.awt.image.BufferedImage; + import static net.minecraft.client.renderer.GlStateManager.loadIdentity; import static net.minecraft.client.renderer.GlStateManager.matrixMode; import static org.lwjgl.opengl.GL11.GL_MODELVIEW; @@ -14,7 +16,7 @@ public class StereoscopicEntityRenderer extends CustomEntityRenderer { private boolean leftEye; public StereoscopicEntityRenderer(RenderOptions options) { - super(options); + super(options, options.getWidth() / 2, options.getHeight()); } public void setEye(boolean leftEye) { @@ -22,11 +24,8 @@ public class StereoscopicEntityRenderer extends CustomEntityRenderer { } @Override - public void updateCameraAndRender(float renderPartialTicks) { - boolean wasAnaglyph = mc.gameSettings.anaglyph; - mc.gameSettings.anaglyph = true; - super.updateCameraAndRender(renderPartialTicks); - mc.gameSettings.anaglyph = wasAnaglyph; + public void renderFrame(float partialTicks, BufferedImage into, int x, int y) { + super.renderFrame(partialTicks, into, x, y); } protected void translateStereoscopic() { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/TilingEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/TilingEntityRenderer.java deleted file mode 100644 index f0816dfd..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/entity/TilingEntityRenderer.java +++ /dev/null @@ -1,43 +0,0 @@ -package eu.crushedpixel.replaymod.video.entity; - -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.video.frame.TilingFrameRenderer; - -import static net.minecraft.client.renderer.GlStateManager.scale; -import static net.minecraft.client.renderer.GlStateManager.translate; -import static org.lwjgl.opengl.GL11.glFrustum; - -public class TilingEntityRenderer extends CustomEntityRenderer { - private final TilingFrameRenderer renderer; - private int tileX, tileY; - - public TilingEntityRenderer(RenderOptions options, TilingFrameRenderer renderer) { - super(options); - this.renderer = renderer; - } - - public void setTile(int tileX, int tileY) { - this.tileX = tileX; - this.tileY = tileY; - } - - @Override - public void gluPerspective(float fovY, float aspect, float zNear, float zFar) { - double height = Math.tan(fovY / 360 * Math.PI) * zNear; - double width = height * aspect; - - int videoWidth = renderer.getVideoWidth(); - int videoHeight = renderer.getVideoHeight(); - int tileWidth = renderer.getTileWidth(); - int tileHeight = renderer.getTileHeight(); - double tilesX = (double) videoWidth / tileWidth; - double tilesY = (double) videoHeight / tileHeight; - double tilesMin = Math.min(tilesX, tilesY); - scale(tilesMin, tilesMin, 1); - double xOffset = (2 * tileX - tilesX + 1) / tilesMin; - double yOffset = (2 * tileY - tilesY + 1) / tilesMin; - translate(-xOffset, yOffset, 0); - - glFrustum(-width, width, -height, height, zNear, zFar); - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/FrameRenderingStrategy.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/FrameRenderingStrategy.java new file mode 100644 index 00000000..4bcf8d5e --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/FrameRenderingStrategy.java @@ -0,0 +1,8 @@ +package eu.crushedpixel.replaymod.video.entity.strategy; + +import java.awt.image.BufferedImage; + +public interface FrameRenderingStrategy { + void renderFrame(float partialTicks, BufferedImage into, int x, int y); + void cleanup(); +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledFrameBufferRenderingStrategy.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledFrameBufferRenderingStrategy.java new file mode 100644 index 00000000..40fea14c --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledFrameBufferRenderingStrategy.java @@ -0,0 +1,127 @@ +package eu.crushedpixel.replaymod.video.entity.strategy; + +import eu.crushedpixel.replaymod.utils.OpenGLUtils; +import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; + +import static net.minecraft.client.renderer.GlStateManager.*; +import static org.lwjgl.opengl.GL11.*; + +public class TiledFrameBufferRenderingStrategy implements FrameRenderingStrategy, CustomEntityRenderer.GluPerspectiveHook { + + private final CustomEntityRenderer renderer; + private final int tileWidth; + private final int tileHeight; + private final ByteBuffer buffer; + private Framebuffer frameBuffer; + + private int tileX; + private int tileY; + + public TiledFrameBufferRenderingStrategy(CustomEntityRenderer renderer) { + this.renderer = renderer; + this.tileWidth = OpenGLUtils.VIEWPORT_MAX_WIDTH; + this.tileHeight = OpenGLUtils.VIEWPORT_MAX_HEIGHT; + this.buffer = BufferUtils.createByteBuffer(tileWidth * tileHeight * 3); + renderer.gluPerspectiveHook = this; + } + + @Override + public void gluPerspective(float fovY, float aspect, float zNear, float zFar) { + double height = Math.tan(fovY / 360 * Math.PI) * zNear; + double width = height * aspect; + + double tilesX = (double) renderer.resultWidth / tileWidth; + double tilesY = (double) renderer.resultHeight / tileHeight; + double tilesMin = Math.min(tilesX, tilesY); + scale(tilesMin, tilesMin, 1); + double xOffset = (2 * tileX - tilesX + 1) / tilesMin; + double yOffset = (2 * tileY - tilesY + 1) / tilesMin; + translate(-xOffset, yOffset, 0); + + glFrustum(-width, width, -height, height, zNear, zFar); + } + + private Framebuffer frameBuffer() { + if (frameBuffer == null) { + frameBuffer = new Framebuffer(tileWidth, tileHeight, true); + } + return frameBuffer; + } + + @Override + public void renderFrame(float partialTicks, BufferedImage into, int x, int y) { + int tilesX = getTilesX(); + int tilesY = getTilesY(); + for (tileX = 0; tileX < tilesX; tileX++) { + for (tileY = 0; tileY < tilesY; tileY++) { + try { + renderTile(partialTicks, into, x + tileWidth * tileX, y + tileHeight * tileY); + } catch (Throwable t) { + CrashReport crash = CrashReport.makeCrashReport(t, "Rendering tile " + x + "/" + y); + throw new ReportedException(crash); + } + } + } + } + + public void renderTile(final float partialTicks, BufferedImage into, int x, int y) { + pushMatrix(); + frameBuffer().bindFramebuffer(true); + + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + + renderer.withDisplaySize(tileWidth, tileHeight, new Runnable() { + @Override + public void run() { + renderer.renderWorld(partialTicks, 0); + } + }); + + frameBuffer().unbindFramebuffer(); + popMatrix(); + + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + buffer.clear(); + + frameBuffer().bindFramebufferTexture(); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + frameBuffer().unbindFramebufferTexture(); + + OpenGLUtils.openGlBytesToBufferedImage(buffer, tileWidth, into, x, y); + } + + public int getTilesX() { + int videoWidth = renderer.resultWidth; + if (videoWidth % tileWidth == 0) { + return videoWidth / tileWidth; + } else { + return videoWidth / tileWidth + 1; + } + } + + public int getTilesY() { + int videoHeight = renderer.resultHeight; + if (videoHeight % tileHeight == 0) { + return videoHeight / tileHeight; + } else { + return videoHeight / tileHeight + 1; + } + } + + @Override + public void cleanup() { + if (frameBuffer != null) { + frameBuffer.deleteFramebuffer(); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledReadPixelsRenderingStrategy.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledReadPixelsRenderingStrategy.java new file mode 100644 index 00000000..f02b8e82 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/TiledReadPixelsRenderingStrategy.java @@ -0,0 +1,112 @@ +package eu.crushedpixel.replaymod.video.entity.strategy; + +import eu.crushedpixel.replaymod.utils.OpenGLUtils; +import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; + +import static net.minecraft.client.renderer.GlStateManager.*; +import static org.lwjgl.opengl.GL11.*; + +public class TiledReadPixelsRenderingStrategy implements FrameRenderingStrategy, CustomEntityRenderer.GluPerspectiveHook { + + private final CustomEntityRenderer renderer; + private final int tileWidth; + private final int tileHeight; + private final ByteBuffer buffer; + + private int tileX; + private int tileY; + + public TiledReadPixelsRenderingStrategy(CustomEntityRenderer renderer) { + this.renderer = renderer; + this.tileWidth = renderer.mc.displayWidth; + this.tileHeight = renderer.mc.displayHeight; + this.buffer = BufferUtils.createByteBuffer(tileWidth * tileHeight * 3); + renderer.gluPerspectiveHook = this; + } + + @Override + public void gluPerspective(float fovY, float aspect, float zNear, float zFar) { + double height = Math.tan(fovY / 360 * Math.PI) * zNear; + double width = height * aspect; + + double tilesX = (double) renderer.resultWidth / tileWidth; + double tilesY = (double) renderer.resultHeight / tileHeight; + double tilesMin = Math.min(tilesX, tilesY); + scale(tilesMin, tilesMin, 1); + double xOffset = (2 * tileX - tilesX + 1) / tilesMin; + double yOffset = (2 * tileY - tilesY + 1) / tilesMin; + translate(-xOffset, yOffset, 0); + + glFrustum(-width, width, -height, height, zNear, zFar); + } + + @Override + public void renderFrame(float partialTicks, BufferedImage into, int x, int y) { + int tilesX = getTilesX(); + int tilesY = getTilesY(); + for (tileX = 0; tileX < tilesX; tileX++) { + for (tileY = 0; tileY < tilesY; tileY++) { + try { + renderTile(partialTicks, into, x + tileWidth * tileX, y + tileHeight * tileY); + } catch (Throwable t) { + CrashReport crash = CrashReport.makeCrashReport(t, "Rendering tile " + x + "/" + y); + throw new ReportedException(crash); + } + } + } + } + + public void renderTile(final float partialTicks, BufferedImage into, int x, int y) { + pushMatrix(); + + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + + renderer.withDisplaySize(tileWidth, tileHeight, new Runnable() { + @Override + public void run() { + renderer.renderWorld(partialTicks, 0); + } + }); + + popMatrix(); + + buffer.clear(); + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + GL11.glReadPixels(0, 0, tileWidth, tileHeight, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + buffer.rewind(); + + OpenGLUtils.openGlBytesToBufferedImage(buffer, tileWidth, into, x, y); + } + + public int getTilesX() { + int videoWidth = renderer.resultWidth; + if (videoWidth % tileWidth == 0) { + return videoWidth / tileWidth; + } else { + return videoWidth / tileWidth + 1; + } + } + + public int getTilesY() { + int videoHeight = renderer.resultHeight; + if (videoHeight % tileHeight == 0) { + return videoHeight / tileHeight; + } else { + return videoHeight / tileHeight + 1; + } + } + + @Override + public void cleanup() { + + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaFrameBufferRenderingStrategy.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaFrameBufferRenderingStrategy.java new file mode 100644 index 00000000..0089ee2b --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaFrameBufferRenderingStrategy.java @@ -0,0 +1,115 @@ +package eu.crushedpixel.replaymod.video.entity.strategy; + +import com.google.gson.JsonSyntaxException; +import eu.crushedpixel.replaymod.utils.OpenGLUtils; +import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.client.shader.ShaderGroup; +import net.minecraft.util.ResourceLocation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static net.minecraft.client.renderer.GlStateManager.*; +import static org.lwjgl.opengl.GL11.*; + +public class VanillaFrameBufferRenderingStrategy implements FrameRenderingStrategy, CustomEntityRenderer.LoadShaderHook { + + private static final Logger logger = LogManager.getLogger(); + + private final CustomEntityRenderer renderer; + private final ByteBuffer buffer; + private Framebuffer frameBuffer; + + public VanillaFrameBufferRenderingStrategy(CustomEntityRenderer renderer) { + this.renderer = renderer; + this.buffer = BufferUtils.createByteBuffer(renderer.resultWidth * renderer.resultHeight * 3); + } + + @Override + public void loadShader(ResourceLocation resourceLocation) { + EntityRenderer proxied = renderer.proxied; + try { + ShaderGroup theShaderGroup = new ShaderGroup(renderer.mc.getTextureManager(), proxied.resourceManager, frameBuffer(), resourceLocation); + theShaderGroup.createBindFramebuffers(renderer.resultWidth, renderer.resultHeight); + proxied.theShaderGroup = theShaderGroup; + proxied.useShader = true; + } catch (IOException e) { + logger.warn("Failed to load shader: " + resourceLocation, e); + proxied.useShader = false; + } catch (JsonSyntaxException e) { + logger.warn("Failed to load shader: " + resourceLocation, e); + proxied.useShader = false; + } + } + + private Framebuffer frameBuffer() { + if (frameBuffer == null) { + frameBuffer = new Framebuffer(renderer.resultWidth, renderer.resultHeight, true); + + ShaderGroup theShaderGroup = renderer.proxied.theShaderGroup; + if (theShaderGroup != null) { + theShaderGroup.deleteShaderGroup(); + renderer.proxied.theShaderGroup = null; + } + } + return frameBuffer; + } + + @Override + public void renderFrame(final float partialTicks, BufferedImage into, int x, int y) { + pushMatrix(); + frameBuffer().bindFramebuffer(true); + + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + + renderer.withDisplaySize(renderer.resultWidth, renderer.resultHeight, new Runnable() { + @Override + public void run() { + renderer.renderWorld(partialTicks, 0); + if (OpenGlHelper.shadersSupported) { + EntityRenderer proxied = renderer.proxied; + if (proxied.theShaderGroup != null && proxied.useShader) { + matrixMode(GL_TEXTURE); + + pushMatrix(); + loadIdentity(); + proxied.theShaderGroup.loadShaderGroup(partialTicks); + popMatrix(); + } + } + } + }); + + frameBuffer().unbindFramebuffer(); + popMatrix(); + + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + buffer.clear(); + + frameBuffer().bindFramebufferTexture(); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + frameBuffer().unbindFramebufferTexture(); + + OpenGLUtils.openGlBytesToBufferedImage(buffer, renderer.resultWidth, into, x, y); + } + + @Override + public void cleanup() { + if (frameBuffer != null) { + frameBuffer.deleteFramebuffer(); + } + if (renderer.proxied.theShaderGroup != null) { + renderer.proxied.theShaderGroup.createBindFramebuffers(renderer.mc.displayWidth, renderer.mc.displayHeight); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaReadPixelsRenderingStrategy.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaReadPixelsRenderingStrategy.java new file mode 100644 index 00000000..e9da9cae --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/strategy/VanillaReadPixelsRenderingStrategy.java @@ -0,0 +1,54 @@ +package eu.crushedpixel.replaymod.video.entity.strategy; + +import eu.crushedpixel.replaymod.utils.OpenGLUtils; +import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; + +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 VanillaReadPixelsRenderingStrategy implements FrameRenderingStrategy { + + private final CustomEntityRenderer renderer; + private final ByteBuffer buffer; + + public VanillaReadPixelsRenderingStrategy(CustomEntityRenderer renderer) { + this.renderer = renderer; + this.buffer = BufferUtils.createByteBuffer(renderer.resultWidth * renderer.resultHeight * 3); + } + + @Override + public void renderFrame(final float partialTicks, BufferedImage into, int x, int y) { + pushMatrix(); + + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + + renderer.withDisplaySize(renderer.resultWidth, renderer.resultHeight, new Runnable() { + @Override + public void run() { + renderer.renderWorld(partialTicks, 0); + } + }); + + popMatrix(); + + buffer.clear(); + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + GL11.glReadPixels(0, 0, renderer.resultWidth, renderer.resultHeight, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + buffer.rewind(); + + OpenGLUtils.openGlBytesToBufferedImage(buffer, renderer.resultWidth, into, x, y); + } + + @Override + public void cleanup() { + + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicFrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicFrameRenderer.java index 55a4ea0d..54eeddc3 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicFrameRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/frame/CubicFrameRenderer.java @@ -2,7 +2,6 @@ package eu.crushedpixel.replaymod.video.frame; import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.video.entity.CubicEntityRenderer; -import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; @@ -10,23 +9,19 @@ import net.minecraft.util.Timer; import java.awt.image.BufferedImage; public class CubicFrameRenderer extends FrameRenderer { - protected static int getDisplaySize() { - Minecraft mc = Minecraft.getMinecraft(); - return Math.min(mc.displayWidth, mc.displayHeight); - } + protected final int frameSize; protected final CubicEntityRenderer entityRenderer; - protected final int displaySize; - protected CubicFrameRenderer(RenderOptions options, int videoWidth, int videoHeight, boolean stable) { - super(options, videoWidth, videoHeight, getDisplaySize() * getDisplaySize() * 3); - this.displaySize = getDisplaySize(); - this.entityRenderer = new CubicEntityRenderer(options, stable); + protected CubicFrameRenderer(RenderOptions options, int frameSize, boolean stable) { + super(options); + this.frameSize = frameSize; + this.entityRenderer = new CubicEntityRenderer(options, frameSize, stable); setCustomEntityRenderer(entityRenderer); } public CubicFrameRenderer(RenderOptions options, boolean stable) { - this(options, getDisplaySize() * 4, getDisplaySize() * 3, stable); + this(options, options.getWidth() / 4, stable); } @Override @@ -34,7 +29,12 @@ public class CubicFrameRenderer extends FrameRenderer { BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB); for (CubicEntityRenderer.Direction direction : CubicEntityRenderer.Direction.values()) { try { - captureFrame(timer, image, direction); + entityRenderer.setDirection(direction); + + int frame = direction.getCubicFrame(); + int xVideo = frame % 4 * frameSize; + int yVideo = frame / 4 * frameSize; + renderFrame(timer, image, xVideo, yVideo); } catch (Throwable t) { CrashReport crash = CrashReport.makeCrashReport(t, "Rendering frame " + direction + "."); throw new ReportedException(crash); @@ -43,38 +43,4 @@ public class CubicFrameRenderer extends FrameRenderer { updateDefaultPreview(image); return image; } - - protected void captureFrame(Timer timer, BufferedImage into, CubicEntityRenderer.Direction direction) { - renderFrameToBuffer(timer, direction); - - // Copy to image - int frame = direction.getCubicFrame(); - int xVideo = frame % 4 * displaySize; - int yVideo = frame / 4 * displaySize; - copyPixelsToImage(buffer, displaySize, into, xVideo, yVideo); - } - - protected void renderFrameToBuffer(Timer timer, CubicEntityRenderer.Direction direction) { - // Setup aspect ratio - int originalWidth = mc.displayWidth; - int originalHeight = mc.displayHeight; - mc.displayWidth = mc.displayHeight = displaySize; - updateFrameBufferSize(); - - // Render frame - entityRenderer.setDirection(direction); - renderFrame(timer); - - // Read pixels - readPixels(buffer); - - mc.displayWidth = originalWidth; - mc.displayHeight = originalHeight; - updateFrameBufferSize(); - } - - private void updateFrameBufferSize() { - mc.getFramebuffer().createBindFramebuffer(mc.displayWidth, mc.displayHeight); - mc.entityRenderer.updateShaderGroupSize(mc.displayWidth, mc.displayHeight); - } } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/DefaultFrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/DefaultFrameRenderer.java index 8659977e..766ae8cd 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/DefaultFrameRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/frame/DefaultFrameRenderer.java @@ -1,7 +1,6 @@ package eu.crushedpixel.replaymod.video.frame; import eu.crushedpixel.replaymod.settings.RenderOptions; -import net.minecraft.client.Minecraft; import net.minecraft.util.Timer; import java.awt.image.BufferedImage; @@ -9,20 +8,16 @@ import java.awt.image.BufferedImage; public class DefaultFrameRenderer extends FrameRenderer { public DefaultFrameRenderer(RenderOptions options) { - super(options, Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight); + super(options); } @Override public BufferedImage captureFrame(Timer timer) { - renderFrame(timer); - int width = getVideoWidth(); int height = getVideoHeight(); - readPixels(buffer); - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - copyPixelsToImage(buffer, width, image, 0, 0); + renderFrame(timer, image, 0, 0); updateDefaultPreview(image); return image; } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/EquirectangularFrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/EquirectangularFrameRenderer.java index 37f17055..f8ab4908 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/EquirectangularFrameRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/frame/EquirectangularFrameRenderer.java @@ -11,10 +11,16 @@ import java.awt.image.DataBufferInt; import static java.lang.Math.PI; -public class EquirectangularFrameRenderer extends CubicFrameRenderer { +public class EquirectangularFrameRenderer extends FrameRenderer { + + private final int frameSize; + protected final CubicEntityRenderer entityRenderer; public EquirectangularFrameRenderer(RenderOptions options, boolean stable) { - super(options, getDisplaySize() * 4, getDisplaySize() * 2, stable); + super(options); + this.frameSize = options.getWidth() / 4; + this.entityRenderer = new CubicEntityRenderer(options, frameSize, stable); + setCustomEntityRenderer(entityRenderer); } @Override @@ -77,8 +83,8 @@ public class EquirectangularFrameRenderer extends CubicFrameRenderer { pt = top; } - int cX = Math.min(displaySize - 1, (int) (cXN * displaySize)); - int cY = Math.min(displaySize - 1, (int) (cYN * displaySize)); + int cX = Math.min(frameSize - 1, (int) (cXN * frameSize)); + int cY = Math.min(frameSize - 1, (int) (cYN * frameSize)); resultPixels[i + j * rWidth] = 0xff000000 | pt.getRGB(cX, cY); // Make sure we got alpha value as well } } @@ -88,11 +94,9 @@ public class EquirectangularFrameRenderer extends CubicFrameRenderer { protected BufferedImage captureFrame(Timer timer, CubicEntityRenderer.Direction direction) { try { - renderFrameToBuffer(timer, direction); - - // Copy to image - BufferedImage image = new BufferedImage(displaySize, displaySize, BufferedImage.TYPE_INT_RGB); - copyPixelsToImage(buffer, displaySize, image, displaySize, 0, 0); + BufferedImage image = new BufferedImage(frameSize, frameSize, BufferedImage.TYPE_INT_RGB); + entityRenderer.setDirection(direction); + renderFrame(timer, image, 0, 0); return image; } catch (Throwable t) { CrashReport crash = CrashReport.makeCrashReport(t, "Rendering frame " + direction); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/FrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/FrameRenderer.java index 64ed968d..2d9c0faa 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/FrameRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/frame/FrameRenderer.java @@ -1,7 +1,5 @@ package eu.crushedpixel.replaymod.video.frame; -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.Runnables; import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer; import net.minecraft.client.Minecraft; @@ -11,7 +9,6 @@ import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.util.ResourceLocation; import net.minecraft.util.Timer; import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; @@ -19,51 +16,38 @@ import java.nio.ByteBuffer; import java.util.Arrays; import static com.google.common.base.Preconditions.checkNotNull; -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; +import static net.minecraft.client.renderer.GlStateManager.bindTexture; +import static net.minecraft.client.renderer.GlStateManager.color; public abstract class FrameRenderer { private static final ResourceLocation noPreviewTexture = new ResourceLocation("replaymod", "logo.jpg"); protected final Minecraft mc = Minecraft.getMinecraft(); protected final RenderOptions options; - private final int videoWidth; - private final int videoHeight; private CustomEntityRenderer customEntityRenderer; private DynamicTexture previewTexture; - private Runnable renderPreviewCallback; private boolean previewActive = false; protected final ByteBuffer buffer; - public FrameRenderer(RenderOptions options, int videoWidth, int videoHeight) { + public FrameRenderer(RenderOptions options) { this.options = options; - this.videoWidth = videoWidth; - this.videoHeight = videoHeight; - this.buffer = BufferUtils.createByteBuffer(mc.displayWidth * mc.displayHeight * 3); - this.customEntityRenderer = new CustomEntityRenderer(options) {}; - } - - public FrameRenderer(RenderOptions options, int videoWidth, int videoHeight, int bufferSize) { - this.options = options; - this.videoWidth = videoWidth; - this.videoHeight = videoHeight; - this.buffer = BufferUtils.createByteBuffer(bufferSize); - this.customEntityRenderer = new CustomEntityRenderer(options) {}; + this.buffer = BufferUtils.createByteBuffer(options.getWidth() * options.getHeight() * 3); } public final int getVideoWidth() { - return videoWidth; + return options.getWidth(); } public final int getVideoHeight() { - return videoHeight; + return options.getHeight(); } public void setPreviewActive(boolean previewActive) { this.previewActive = previewActive; if (previewActive) { - Arrays.fill(previewTexture.getTextureData(), 0xff000000); - previewTexture.updateDynamicTexture(); + if (previewTexture != null) { + Arrays.fill(previewTexture.getTextureData(), 0xff000000); + previewTexture.updateDynamicTexture(); + } } } @@ -71,30 +55,12 @@ public abstract class FrameRenderer { return previewActive; } - public boolean setRenderPreviewCallback(Runnable renderPreviewCallback) { - Preconditions.checkState(this.renderPreviewCallback == null, "Render preview callback already set."); - this.renderPreviewCallback = renderPreviewCallback; - return false; - } - - public Runnable getRenderPreviewCallback() { - return renderPreviewCallback != null ? renderPreviewCallback : Runnables.doNothing(); - } - public void setCustomEntityRenderer(CustomEntityRenderer customEntityRenderer) { this.customEntityRenderer = checkNotNull(customEntityRenderer); } public abstract BufferedImage captureFrame(Timer timer); - protected void readPixels(ByteBuffer buffer) { - buffer.clear(); - GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); - GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); - GL11.glReadPixels(0, 0, mc.displayWidth, mc.displayHeight, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); - buffer.rewind(); - } - protected void updateDefaultPreview(BufferedImage image) { if (isPreviewActive()) { int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); @@ -103,62 +69,22 @@ public abstract class FrameRenderer { } protected void updateDefaultPreview(int[] pixels) { - if (isPreviewActive()) { - System.arraycopy(pixels, 0, getPreviewTexture().getTextureData(), 0, pixels.length); - getPreviewTexture().updateDynamicTexture(); + if (isPreviewActive() && previewTexture != null) { + System.arraycopy(pixels, 0, previewTexture.getTextureData(), 0, pixels.length); + previewTexture.updateDynamicTexture(); } } - protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, BufferedImage image, int offsetX, int offsetY) { - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - copyPixelsToImage(buffer, bufferWidth, pixels, offsetX, offsetY); - } - - protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, BufferedImage image, int imageWidth, int offsetX, int offsetY) { - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - copyPixelsToImage(buffer, bufferWidth, pixels, imageWidth, offsetX, offsetY); - } - - protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, int[] image, int offsetX, int offsetY) { - copyPixelsToImage(buffer, bufferWidth, image, videoWidth, offsetX, offsetY); - } - - protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, int[] image, int imageWidth, int offsetX, int offsetY) { - int bufferSize = buffer.remaining() / 3; - // Read the OpenGL image row by row from right to left (flipped horizontally) - for (int i = bufferSize - 1; i >= 0; i--) { - // Coordinates in the final image - int x = offsetX + bufferWidth - i % bufferWidth - 1; // X coord of OpenGL image has to be flipped first - int y = offsetY + i / bufferWidth; - if (x >= imageWidth || y * imageWidth >= image.length) { - buffer.position(buffer.position() + 3); // Pixel would end up outside of image - continue; - } - // Write to image (row by row, left to right) - image[y * imageWidth + x] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | buffer.get() & 0xff; - } - buffer.rewind(); - } - - protected void renderFrame(Timer timer) { - pushMatrix(); - clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - mc.getFramebuffer().bindFramebuffer(true); - - enableTexture2D(); - - customEntityRenderer.updateCameraAndRender(timer.renderPartialTicks); - - mc.getFramebuffer().unbindFramebuffer(); - popMatrix(); - - pushMatrix(); - mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); - popMatrix(); + protected void renderFrame(Timer timer, BufferedImage into, int x, int y) { + customEntityRenderer.renderFrame(timer.renderPartialTicks, into, x, y); } public void setup() { - this.previewTexture = new DynamicTexture(videoWidth, videoHeight) { + if (customEntityRenderer == null) { + customEntityRenderer = new CustomEntityRenderer(options) {}; + } + + previewTexture = new DynamicTexture(getVideoWidth(), getVideoHeight()) { @Override public void updateDynamicTexture() { bindTexture(getGlTextureId()); @@ -168,11 +94,22 @@ public abstract class FrameRenderer { } public void tearDown() { - this.previewTexture.deleteGlTexture(); + if (previewTexture != null) { + previewTexture.deleteGlTexture(); + } + if (customEntityRenderer != null) { + customEntityRenderer.cleanup(); + } } - public DynamicTexture getPreviewTexture() { - return previewTexture; + public void drawPreviewTexture(int x, int y, int width, int height) { + int videoWidth = getVideoWidth(); + int videoHeight = getVideoHeight(); + color(1, 1, 1, 1); + if (previewTexture != null) { + bindTexture(previewTexture.getGlTextureId()); + Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, videoWidth, videoHeight, width, height, videoWidth, videoHeight); + } } /** @@ -194,9 +131,7 @@ public abstract class FrameRenderer { x += (width - actualWidth) / 2; y += (height - actualHeight) / 2; - bindTexture(previewTexture.getGlTextureId()); - color(1, 1, 1, 1); - Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, videoWidth, videoHeight, actualWidth, actualHeight, videoWidth, videoHeight); + drawPreviewTexture(x, y, actualWidth, actualHeight); } public static void renderNoPreview(int x, int y, int width, int height) { diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicFrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicFrameRenderer.java index c15aaeb5..206cb14d 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicFrameRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/frame/StereoscopicFrameRenderer.java @@ -2,7 +2,6 @@ package eu.crushedpixel.replaymod.video.frame; import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.video.entity.StereoscopicEntityRenderer; -import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; @@ -13,7 +12,7 @@ public class StereoscopicFrameRenderer extends FrameRenderer { private final StereoscopicEntityRenderer entityRenderer; public StereoscopicFrameRenderer(RenderOptions options) { - super(options, Minecraft.getMinecraft().displayWidth * 2, Minecraft.getMinecraft().displayHeight); + super(options); entityRenderer = new StereoscopicEntityRenderer(options); setCustomEntityRenderer(entityRenderer); } @@ -22,32 +21,20 @@ public class StereoscopicFrameRenderer extends FrameRenderer { public BufferedImage captureFrame(Timer timer) { BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB); try { - captureFrame(timer, image, true); + entityRenderer.setEye(true); + renderFrame(timer, image, 0, 0); } catch (Throwable t) { CrashReport crash = CrashReport.makeCrashReport(t, "Rendering left eye."); throw new ReportedException(crash); } try { - captureFrame(timer, image, false); + entityRenderer.setEye(false); + renderFrame(timer, image, getVideoWidth() / 2, 0); } catch (Throwable t) { CrashReport crash = CrashReport.makeCrashReport(t, "Rendering right eye."); throw new ReportedException(crash); } + updateDefaultPreview(image); return image; } - - protected void captureFrame(Timer timer, BufferedImage into, boolean leftEye) { - int displayWidth = getVideoWidth() / 2; - - // Render frame - entityRenderer.setEye(leftEye); - renderFrame(timer); - - // Read pixels - readPixels(buffer); - - // Copy to image - copyPixelsToImage(buffer, displayWidth, into, leftEye ? 0 : displayWidth, 0); - updateDefaultPreview(into); - } } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/frame/TilingFrameRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/frame/TilingFrameRenderer.java deleted file mode 100644 index e507077f..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/video/frame/TilingFrameRenderer.java +++ /dev/null @@ -1,132 +0,0 @@ -package eu.crushedpixel.replaymod.video.frame; - -import eu.crushedpixel.replaymod.settings.RenderOptions; -import eu.crushedpixel.replaymod.video.entity.TilingEntityRenderer; -import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; -import net.minecraft.util.Timer; - -import java.awt.image.BufferedImage; -import java.util.Arrays; - -public class TilingFrameRenderer extends FrameRenderer { - private final TilingEntityRenderer entityRenderer; - private final int tileWidth; - private final int tileHeight; - - public TilingFrameRenderer(RenderOptions options, int videoWidth, int videoHeight) { - super(options, videoWidth, videoHeight); - this.entityRenderer = new TilingEntityRenderer(options, this); - this.tileWidth = mc.displayWidth; - this.tileHeight = mc.displayHeight; - setCustomEntityRenderer(entityRenderer); - } - - @Override - public boolean setRenderPreviewCallback(Runnable renderPreviewCallback) { - super.setRenderPreviewCallback(renderPreviewCallback); - return true; - } - - @Override - public BufferedImage captureFrame(Timer timer) { - BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB); - int tilesX = getTilesX(); - int tilesY = getTilesY(); - for (int x = 0; x < tilesX; x++) { - for (int y = 0; y < tilesY; y++) { - try { - captureTile(timer, x, y, image); - } catch (Throwable t) { - CrashReport crash = CrashReport.makeCrashReport(t, "Rendering tile " + x + "/" + y); - throw new ReportedException(crash); - } - getRenderPreviewCallback().run(); - } - } - return image; - } - - protected void captureTile(Timer timer, int tileX, int tileY, BufferedImage into) { - // Render frame - entityRenderer.setTile(tileX, tileY); - renderFrame(timer); - - // Read pixels - readPixels(buffer); - - // Copy to image - copyPixelsToImage(buffer, tileWidth, into, tileX * tileWidth, tileY * tileHeight); - if (isPreviewActive()) { - int[] pixels = getPreviewTexture().getTextureData(); - copyPixelsToImage(buffer, tileWidth, pixels, tileX * tileWidth, tileY * tileHeight); - - // Draw red rectangle around next tile - tileY = (tileY + 1) % getTilesY(); - if (tileY == 0) { - tileX = (tileX + 1) % getTilesX(); - } - drawPreviewRectangle(pixels, tileX, tileY); - - getPreviewTexture().updateDynamicTexture(); - } - } - - private void drawPreviewRectangle(int[] pixels, int tileX, int tileY) { - final int RED = 0xffff0000; - - int videoWidth = getVideoWidth(); - int videoHeight = getVideoHeight(); - int border = videoWidth / tileWidth * 5; - int left = Math.min(tileX * tileWidth, videoWidth - border - 1); - int right = Math.min((left + tileWidth), videoWidth) - 1; - int top = Math.min(tileY * tileHeight, videoHeight - border - 1); - int bottom = Math.min((top + tileHeight), videoHeight - border) - 1; - - for (int i = 0; i < border; i++) { - // Top border - int offset = (top + i) * videoWidth; - Arrays.fill(pixels, offset + left, offset + right + 1, RED); - - // Bottom border - offset = (bottom - i) * videoWidth; - Arrays.fill(pixels, offset + left, offset + right + 1, RED); - - // Left border - for (int j = top; j <= bottom; j++) { - pixels[j * videoWidth + left + i] = RED; - } - - // Right border - for (int j = top; j <= bottom; j++) { - pixels[j * videoWidth + right - i] = RED; - } - } - } - - public int getTilesX() { - int videoWidth = getVideoWidth(); - if (videoWidth % tileWidth == 0) { - return videoWidth / tileWidth; - } else { - return videoWidth / tileWidth + 1; - } - } - - public int getTilesY() { - int videoHeight = getVideoHeight(); - if (videoHeight % tileHeight == 0) { - return videoHeight / tileHeight; - } else { - return videoHeight / tileHeight + 1; - } - } - - public int getTileWidth() { - return tileWidth; - } - - public int getTileHeight() { - return tileHeight; - } -} diff --git a/src/main/resources/assets/replaymod/lang/en_US.lang b/src/main/resources/assets/replaymod/lang/en_US.lang index d65ba062..4953ffac 100644 --- a/src/main/resources/assets/replaymod/lang/en_US.lang +++ b/src/main/resources/assets/replaymod/lang/en_US.lang @@ -254,9 +254,8 @@ replaymod.gui.rendersettings.renderer.stereoscopic=Stereoscopic Rendering replaymod.gui.rendersettings.renderer.cubic=Cubic Rendering replaymod.gui.rendersettings.renderer.equirectangular=Equirectangular Rendering -replaymod.gui.rendersettings.renderer.default.description=Renders the video with your current resolution. Fastest Rendering Option. -replaymod.gui.rendersettings.renderer.tiled.description=Renders the video with a custom resolution, even if it's larger than your screen. -replaymod.gui.rendersettings.renderer.stereoscopic.description=Renders the video as a steroscopic (side-by-side) 3D movie, useable by different 3D technologies. +replaymod.gui.rendersettings.renderer.default.description=Renders the video in the specified resolution. Fastest Rendering Option. +replaymod.gui.rendersettings.renderer.stereoscopic.description=Renders the video as a steroscopic (side-by-side) 3D movie, useable by different 3D technologies. The image for one eye is half the width of the video replaymod.gui.rendersettings.renderer.cubic.description=Renders the video with a 360 degree panoramic view, using Cubic Projection. This is useable by several 360 degree video players (and the Oculus Rift), for example VR Player. replaymod.gui.rendersettings.renderer.equirectangular.description=Renders the video with a 360 degree panoramic view, using Equirectangular Projection. This is useable by YouTube's new 360 degree video function, and several video players (and the Oculus Rift), for example VR Player.