Allow custom resolution for all video rendering modes
Remove tiled rendering Choose different rendering strategy based on video size and framebuffer support Make size of video writer queue configurable via system properties
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<BufferedImage>();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user