From 517591d72a060a01aa01e9786992091481a4cac8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 26 Jun 2022 13:32:58 +0200 Subject: [PATCH] Encode OpenEXR and PNG frames in parallel --- .../advancedscreenshots/ScreenshotWriter.java | 5 ++ .../java/com/replaymod/render/EXRWriter.java | 5 ++ .../com/replaymod/render/FFmpegWriter.java | 5 ++ .../java/com/replaymod/render/PNGWriter.java | 5 ++ .../render/rendering/FrameConsumer.java | 2 + .../replaymod/render/rendering/Pipeline.java | 65 +++++++++++++------ .../replaymod/render/rendering/Pipelines.java | 5 ++ .../render/rendering/VideoRenderer.java | 15 ++++- 8 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java index 70522b22..987e7839 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java @@ -63,4 +63,9 @@ public class ScreenshotWriter implements FrameConsumer { public void close() throws IOException { } + + @Override + public boolean isParallelCapable() { + return false; + } } diff --git a/src/main/java/com/replaymod/render/EXRWriter.java b/src/main/java/com/replaymod/render/EXRWriter.java index 17124ccc..f2d9a921 100644 --- a/src/main/java/com/replaymod/render/EXRWriter.java +++ b/src/main/java/com/replaymod/render/EXRWriter.java @@ -135,4 +135,9 @@ public class EXRWriter implements FrameConsumer { @Override public void close() { } + + @Override + public boolean isParallelCapable() { + return true; + } } diff --git a/src/main/java/com/replaymod/render/FFmpegWriter.java b/src/main/java/com/replaymod/render/FFmpegWriter.java index f4e1381c..332f346c 100644 --- a/src/main/java/com/replaymod/render/FFmpegWriter.java +++ b/src/main/java/com/replaymod/render/FFmpegWriter.java @@ -134,6 +134,11 @@ public class FFmpegWriter implements FrameConsumer { } } + @Override + public boolean isParallelCapable() { + return false; + } + private void checkSize(ReadableDimension size) { checkSize(size.getWidth(), size.getHeight()); } diff --git a/src/main/java/com/replaymod/render/PNGWriter.java b/src/main/java/com/replaymod/render/PNGWriter.java index 525d154d..8912dcb0 100644 --- a/src/main/java/com/replaymod/render/PNGWriter.java +++ b/src/main/java/com/replaymod/render/PNGWriter.java @@ -71,4 +71,9 @@ public class PNGWriter implements FrameConsumer { @Override public void close() { } + + @Override + public boolean isParallelCapable() { + return true; + } } diff --git a/src/main/java/com/replaymod/render/rendering/FrameConsumer.java b/src/main/java/com/replaymod/render/rendering/FrameConsumer.java index fd74586c..ae654ca0 100644 --- a/src/main/java/com/replaymod/render/rendering/FrameConsumer.java +++ b/src/main/java/com/replaymod/render/rendering/FrameConsumer.java @@ -7,4 +7,6 @@ public interface FrameConsumer

extends Closeable { void consume(Map channels); + boolean isParallelCapable(); + } diff --git a/src/main/java/com/replaymod/render/rendering/Pipeline.java b/src/main/java/com/replaymod/render/rendering/Pipeline.java index cf7f057d..8696fe29 100644 --- a/src/main/java/com/replaymod/render/rendering/Pipeline.java +++ b/src/main/java/com/replaymod/render/rendering/Pipeline.java @@ -10,6 +10,7 @@ import net.minecraft.util.crash.CrashException; import net.minecraft.util.crash.CrashReport; import org.lwjgl.glfw.GLFW; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; @@ -25,8 +26,6 @@ public class Pipeline implements Runnable { private final FrameCapturer capturer; private final FrameProcessor processor; private final GlToAbsoluteDepthProcessor depthProcessor; - private int consumerNextFrame; - private final Object consumerLock = new Object(); private final FrameConsumer

consumer; private volatile boolean abort; @@ -35,7 +34,7 @@ public class Pipeline implements Runnable { this.worldRenderer = worldRenderer; this.capturer = capturer; this.processor = processor; - this.consumer = consumer; + this.consumer = new ParallelSafeConsumer<>(consumer); float near = 0.05f; float far = getMinecraft().options.viewDistance * 16 * 4; @@ -44,7 +43,6 @@ public class Pipeline implements Runnable { @Override public synchronized void run() { - consumerNextFrame = 0; int processors = Runtime.getRuntime().availableProcessors(); int processThreads = Math.max(1, processors - 2); // One processor for the main thread and one for ffmpeg, sorry OS :( ExecutorService processService = new ThreadPoolExecutor(processThreads, processThreads, @@ -106,7 +104,6 @@ public class Pipeline implements Runnable { @Override public void run() { try { - Integer frameId = null; Map processedChannels = new HashMap<>(); for (Map.Entry entry : rawChannels.entrySet()) { P processedFrame = processor.process(entry.getValue()); @@ -114,27 +111,57 @@ public class Pipeline implements Runnable { depthProcessor.process((BitmapFrame) processedFrame); } processedChannels.put(entry.getKey(), processedFrame); - frameId = processedFrame.getFrameId(); } - if (frameId == null) { + if (processedChannels.isEmpty()) { return; } - synchronized (consumerLock) { - while (consumerNextFrame != frameId) { - try { - consumerLock.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - consumer.consume(processedChannels); - consumerNextFrame++; - consumerLock.notifyAll(); - } + consumer.consume(processedChannels); } catch (Throwable t) { CrashReport crashReport = CrashReport.create(t, "Processing frame"); MCVer.getMinecraft().setCrashReport(crashReport); } } } + + private static class ParallelSafeConsumer

implements FrameConsumer

{ + private final FrameConsumer

inner; + + private int nextFrame; + private final Object lock = new Object(); + + private ParallelSafeConsumer(FrameConsumer

inner) { + this.inner = inner; + } + + @Override + public void consume(Map channels) { + if (inner.isParallelCapable()) { + inner.consume(channels); + } else { + int frameId = channels.values().iterator().next().getFrameId(); + synchronized (lock) { + while (nextFrame != frameId) { + try { + lock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + inner.consume(channels); + nextFrame++; + lock.notifyAll(); + } + } + } + + @Override + public boolean isParallelCapable() { + return true; + } + + @Override + public void close() throws IOException { + inner.close(); + } + } } diff --git a/src/main/java/com/replaymod/render/rendering/Pipelines.java b/src/main/java/com/replaymod/render/rendering/Pipelines.java index e012fb41..bc65fba2 100644 --- a/src/main/java/com/replaymod/render/rendering/Pipelines.java +++ b/src/main/java/com/replaymod/render/rendering/Pipelines.java @@ -128,6 +128,11 @@ public class Pipelines { @Override public void close() { } + + @Override + public boolean isParallelCapable() { + return true; + } }; return new Pipeline<>(worldRenderer, capturer, new DummyProcessor<>(), consumer); } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index ff8048d2..92021f8b 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -137,11 +137,19 @@ public class VideoRenderer implements RenderInfo { } ffmpegWriter = frameConsumer instanceof FFmpegWriter ? (FFmpegWriter) frameConsumer : null; FrameConsumer previewingFrameConsumer = new FrameConsumer() { + private int lastFrameId = -1; + @Override public void consume(Map channels) { BitmapFrame bgra = channels.get(Channel.BRGA); if (bgra != null) { - gui.updatePreview(bgra.getByteBuffer(), bgra.getSize()); + synchronized (this) { + int frameId = bgra.getFrameId(); + if (lastFrameId < frameId) { + lastFrameId = frameId; + gui.updatePreview(bgra.getByteBuffer(), bgra.getSize()); + } + } } frameConsumer.consume(channels); } @@ -150,6 +158,11 @@ public class VideoRenderer implements RenderInfo { public void close() throws IOException { frameConsumer.close(); } + + @Override + public boolean isParallelCapable() { + return frameConsumer.isParallelCapable(); + } }; this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this, previewingFrameConsumer); }