From 6a5b6fdac582e3a3985e88c661f02b2fa35052fa Mon Sep 17 00:00:00 2001 From: johni0702 Date: Thu, 16 Jul 2015 16:19:20 +0200 Subject: [PATCH] Add PBOs and PBO support for default rendering --- .../replaymod/opengl/PixelBufferObject.java | 160 ++++++++++++++++++ .../eu/crushedpixel/replaymod/utils/Api.java | 10 ++ .../video/capturer/OpenGlFrameCapturer.java | 6 +- .../SimplePboOpenGlFrameCapturer.java | 87 ++++++++++ .../replaymod/video/rendering/Pipelines.java | 13 +- 5 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/utils/Api.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/video/capturer/SimplePboOpenGlFrameCapturer.java diff --git a/src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java b/src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java new file mode 100644 index 00000000..1670f5b9 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/opengl/PixelBufferObject.java @@ -0,0 +1,160 @@ +package eu.crushedpixel.replaymod.opengl; + +import eu.crushedpixel.replaymod.utils.Api; +import eu.crushedpixel.replaymod.utils.Objects; +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Minecraft; +import org.apache.logging.log4j.LogManager; +import org.lwjgl.opengl.ARBBufferObject; +import org.lwjgl.opengl.GLContext; + +import java.nio.ByteBuffer; + +import static org.lwjgl.opengl.ARBPixelBufferObject.*; +import static org.lwjgl.opengl.GL15.*; +import static org.lwjgl.opengl.GL21.GL_PIXEL_PACK_BUFFER; + +@Api +public class PixelBufferObject { + @RequiredArgsConstructor + public enum Usage { + COPY(GL_STREAM_COPY_ARB, GL_STREAM_COPY), + DRAW(GL_STREAM_DRAW_ARB, GL_STREAM_DRAW), + READ(GL_STREAM_READ_ARB, GL_STREAM_READ); + + private final int arb, gl15; + } + + public static final boolean SUPPORTED = GLContext.getCapabilities().GL_ARB_pixel_buffer_object || GLContext.getCapabilities().OpenGL15; + private static final boolean arb = !GLContext.getCapabilities().OpenGL15; + + private static ThreadLocal bound = new ThreadLocal(); + private static ThreadLocal mapped = new ThreadLocal(); + + private final long size; + private long handle; + + public PixelBufferObject(long size, Usage usage) { + if (!SUPPORTED) { + throw new UnsupportedOperationException("PBOs not supported."); + } + + this.size = size; + this.handle = arb ? ARBBufferObject.glGenBuffersARB() : glGenBuffers(); + + bind(); + + if (arb) { + ARBBufferObject.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size, usage.arb); + } else { + glBufferData(GL_PIXEL_PACK_BUFFER, size, usage.gl15); + } + } + + @Api + private int getHandle() { + if (handle == -1) { + throw new IllegalStateException("PBO not allocated."); + } + return (int) handle; + } + + @Api + public void bind() { + if (arb) { + ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle()); + } else { + glBindBuffer(GL_PIXEL_PACK_BUFFER, getHandle()); + } + bound.set(getHandle()); + } + + @Api + public void unbind() { + checkBound(); + if (arb) { + ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + } else { + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + bound.set(0); + } + + private void checkBound() { + if (!Objects.equals(getHandle(), bound.get())) { + throw new IllegalStateException("Buffer not bound."); + } + } + + @Api + public ByteBuffer mapReadOnly() { + checkBound(); + mapped.set(getHandle()); + if (arb) { + return ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB, size, null); + } else { + return glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY, size, null); + } + } + + @Api + public ByteBuffer mapWriteOnly() { + checkBound(); + mapped.set(getHandle()); + if (arb) { + return ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_WRITE_ONLY_ARB, size, null); + } else { + return glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_WRITE_ONLY, size, null); + } + } + + @Api + public ByteBuffer mapReadWrite() { + checkBound(); + mapped.set(getHandle()); + if (arb) { + return ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_WRITE_ARB, size, null); + } else { + return glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_WRITE, size, null); + } + } + + @Api + public void unmap() { + checkBound(); + if (!Objects.equals(mapped.get(), getHandle())) { + throw new IllegalStateException("Buffer not mapped."); + } + if (arb) { + ARBBufferObject.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); + } else { + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + } + } + + @Api + public void delete() { + if (handle != -1) { + if (arb) { + ARBBufferObject.glDeleteBuffersARB(getHandle()); + } else { + glDeleteBuffers(getHandle()); + } + handle = -1; + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (handle != -1) { + LogManager.getLogger().warn("PBO garbage collected before deleted!"); + Minecraft.getMinecraft().addScheduledTask(new Runnable() { + @Override + public void run() { + delete(); + } + }); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/Api.java b/src/main/java/eu/crushedpixel/replaymod/utils/Api.java new file mode 100644 index 00000000..62a45fc4 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/utils/Api.java @@ -0,0 +1,10 @@ +package eu.crushedpixel.replaymod.utils; + +/** + * Members annotated with @Api are considered API and should not be removed even if their unused as they might + * be of use at a later point in time. + * The purpose of this annotation is to be added to the IntelliJ inspections exceptions. + */ +@Api +public @interface Api { +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java b/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java index 0028c829..0e8c9b2c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/capturer/OpenGlFrameCapturer.java @@ -54,7 +54,7 @@ public abstract class OpenGlFrameCapturer { + private final int bufferSize; + private PixelBufferObject pbo, otherPBO; + + public SimplePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) { + super(worldRenderer, renderInfo); + + ReadableDimension size = renderInfo.getFrameSize(); + bufferSize = size.getHeight() * size.getWidth() * 3; + pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ); + otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ); + } + + private void swapPBOs() { + PixelBufferObject old = pbo; + pbo = otherPBO; + otherPBO = old; + } + + @Override + public boolean isDone() { + return framesDone >= renderInfo.getTotalFrames() + 2; + } + + @Override + public OpenGlFrame process() { + OpenGlFrame frame = null; + + if (framesDone > 1) { + // Read pbo to memory + pbo.bind(); + ByteBuffer pboBuffer = pbo.mapReadOnly(); + ByteBuffer buffer = ByteBufferPool.allocate(bufferSize); + buffer.put(pboBuffer); + buffer.rewind(); + pbo.unmap(); + pbo.unbind(); + frame = new OpenGlFrame(framesDone - 2, frameSize, buffer); + } + + if (framesDone < renderInfo.getTotalFrames()) { + // Then fill it again + renderFrame(framesDone, renderInfo.updateForNextFrame()); + } + + framesDone++; + swapPBOs(); + return frame; + } + + @Override + protected OpenGlFrame captureFrame(int frameId) { + pbo.bind(); + + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + + if (OpenGlHelper.isFramebufferEnabled()) { + frameBuffer().bindFramebufferTexture(); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, 0); + frameBuffer().unbindFramebufferTexture(); + } else { + GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, 0); + } + + pbo.unbind(); + return null; + } + + @Override + public void close() throws IOException { + pbo.delete(); + otherPBO.delete(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java b/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java index 54e6530a..93ee5269 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/rendering/Pipelines.java @@ -1,5 +1,6 @@ package eu.crushedpixel.replaymod.video.rendering; +import eu.crushedpixel.replaymod.opengl.PixelBufferObject; import eu.crushedpixel.replaymod.settings.RenderOptions; import eu.crushedpixel.replaymod.video.capturer.*; import eu.crushedpixel.replaymod.video.entity.CubicEntityRenderer; @@ -37,11 +38,13 @@ public class Pipelines { public static Pipeline newDefaultPipeline(RenderInfo renderInfo, FrameConsumer consumer) { RenderOptions options = renderInfo.getRenderOptions(); - return new Pipeline( - new SimpleOpenGlFrameCapturer(new CustomEntityRenderer(options), renderInfo), - new OpenGlToARGBProcessor(), - consumer - ); + FrameCapturer capturer; + if (PixelBufferObject.SUPPORTED) { + capturer = new SimplePboOpenGlFrameCapturer(new CustomEntityRenderer(options), renderInfo); + } else { + capturer = new SimpleOpenGlFrameCapturer(new CustomEntityRenderer(options), renderInfo); + } + return new Pipeline(capturer, new OpenGlToARGBProcessor(), consumer); } public static Pipeline newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer consumer) {