Rework rendering pipeline to make better use of multithreading

Move OpenGL frame to ARGB conversion to processing threads
Move video exporting to processing threads
Skip creation of BufferedImage and instead use ByteBuffer with ARGB content directly
This commit is contained in:
johni0702
2015-07-15 14:21:18 +02:00
parent 3a2c35cb22
commit 7fd429e25f
44 changed files with 1142 additions and 1244 deletions

View File

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

View File

@@ -0,0 +1,37 @@
package eu.crushedpixel.replaymod.video.capturer;
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
public enum Data implements CaptureData {
LEFT, RIGHT, FRONT, BACK, TOP, BOTTOM
}
private final int frameSize;
public CubicOpenGlFrameCapturer(WorldRenderer<Data> worldRenderer, RenderInfo renderInfo, int frameSize) {
super(worldRenderer, renderInfo);
this.frameSize = frameSize;
}
@Override
protected int getFrameWidth() {
return frameSize;
}
@Override
protected int getFrameHeight() {
return frameSize;
}
@Override
public CubicOpenGlFrame process() {
float partialTicks = renderInfo.updateForNextFrame();
int frameId = framesDone++;
return new CubicOpenGlFrame(renderFrame(frameId, partialTicks, Data.LEFT),
renderFrame(frameId, partialTicks, Data.RIGHT),
renderFrame(frameId, partialTicks, Data.FRONT),
renderFrame(frameId, partialTicks, Data.BACK),
renderFrame(frameId, partialTicks, Data.TOP),
renderFrame(frameId, partialTicks, Data.BOTTOM));
}
}

View File

@@ -0,0 +1,108 @@
package eu.crushedpixel.replaymod.video.capturer;
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
import eu.crushedpixel.replaymod.video.rendering.Frame;
import eu.crushedpixel.replaymod.video.rendering.FrameCapturer;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.shader.Framebuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import org.lwjgl.util.WritableDimension;
import java.io.IOException;
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 abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData> implements FrameCapturer<F> {
protected final WorldRenderer<D> worldRenderer;
protected final RenderInfo renderInfo;
protected int framesDone;
private Framebuffer frameBuffer;
public OpenGlFrameCapturer(WorldRenderer<D> worldRenderer, RenderInfo renderInfo) {
this.worldRenderer = worldRenderer;
this.renderInfo = renderInfo;
}
protected final ReadableDimension frameSize = new ReadableDimension() {
@Override
public int getWidth() {
return getFrameWidth();
}
@Override
public int getHeight() {
return getFrameHeight();
}
@Override
public void getSize(WritableDimension dest) {
dest.setSize(getWidth(), getHeight());
}
};
protected int getFrameWidth() {
return renderInfo.getFrameSize().getWidth();
}
protected int getFrameHeight() {
return renderInfo.getFrameSize().getHeight();
}
private Framebuffer frameBuffer() {
if (frameBuffer == null) {
frameBuffer = new Framebuffer(getFrameWidth(), getFrameHeight(), true);
}
return frameBuffer;
}
@Override
public boolean isDone() {
return framesDone >= renderInfo.getTotalFrames();
}
protected OpenGlFrame renderFrame(int frameId, float partialTicks) {
return renderFrame(frameId, partialTicks, null);
}
protected OpenGlFrame renderFrame(int frameId, float partialTicks, D captureData) {
pushMatrix();
frameBuffer().bindFramebuffer(true);
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D();
worldRenderer.renderWorld(frameSize, partialTicks, captureData);
frameBuffer().unbindFramebuffer();
popMatrix();
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
ByteBuffer buffer = ByteBufferPool.allocate(getFrameWidth() * getFrameHeight() * 3);
if (OpenGlHelper.isFramebufferEnabled()) {
frameBuffer().bindFramebufferTexture();
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
frameBuffer().unbindFramebufferTexture();
} else {
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
}
buffer.rewind();
return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), buffer);
}
@Override
public void close() throws IOException {
worldRenderer.close();
if (frameBuffer != null) {
frameBuffer.deleteFramebuffer();
}
}
}

View File

@@ -0,0 +1,14 @@
package eu.crushedpixel.replaymod.video.capturer;
import eu.crushedpixel.replaymod.settings.RenderOptions;
import org.lwjgl.util.ReadableDimension;
public interface RenderInfo {
ReadableDimension getFrameSize();
int getTotalFrames();
float updateForNextFrame();
RenderOptions getRenderOptions();
}

View File

@@ -0,0 +1,16 @@
package eu.crushedpixel.replaymod.video.capturer;
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
public SimpleOpenGlFrameCapturer(WorldRenderer<CaptureData> worldRenderer, RenderInfo renderInfo) {
super(worldRenderer, renderInfo);
}
@Override
public OpenGlFrame process() {
float partialTicks = renderInfo.updateForNextFrame();
return renderFrame(framesDone++, partialTicks);
}
}

View File

@@ -0,0 +1,29 @@
package eu.crushedpixel.replaymod.video.capturer;
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
public class StereoscopicOpenGlFrameCapturer
extends OpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
public enum Data implements CaptureData {
LEFT_EYE, RIGHT_EYE
}
public StereoscopicOpenGlFrameCapturer(WorldRenderer<Data> worldRenderer, RenderInfo renderInfo) {
super(worldRenderer, renderInfo);
}
@Override
protected int getFrameWidth() {
return super.getFrameWidth() / 2;
}
@Override
public StereoscopicOpenGlFrame process() {
float partialTicks = renderInfo.updateForNextFrame();
int frameId = framesDone++;
OpenGlFrame left = renderFrame(frameId, partialTicks, Data.LEFT_EYE);
OpenGlFrame right = renderFrame(frameId, partialTicks, Data.RIGHT_EYE);
return new StereoscopicOpenGlFrame(left, right);
}
}

View File

@@ -0,0 +1,9 @@
package eu.crushedpixel.replaymod.video.capturer;
import org.lwjgl.util.ReadableDimension;
import java.io.Closeable;
public interface WorldRenderer<D extends CaptureData> extends Closeable {
void renderWorld(ReadableDimension displaySize, float partialTicks, D data);
}