Encode OpenEXR and PNG frames in parallel

This commit is contained in:
Jonas Herzig
2022-06-26 13:32:58 +02:00
parent a3f4049322
commit 517591d72a
8 changed files with 87 additions and 20 deletions

View File

@@ -63,4 +63,9 @@ public class ScreenshotWriter implements FrameConsumer<BitmapFrame> {
public void close() throws IOException {
}
@Override
public boolean isParallelCapable() {
return false;
}
}

View File

@@ -135,4 +135,9 @@ public class EXRWriter implements FrameConsumer<BitmapFrame> {
@Override
public void close() {
}
@Override
public boolean isParallelCapable() {
return true;
}
}

View File

@@ -134,6 +134,11 @@ public class FFmpegWriter implements FrameConsumer<BitmapFrame> {
}
}
@Override
public boolean isParallelCapable() {
return false;
}
private void checkSize(ReadableDimension size) {
checkSize(size.getWidth(), size.getHeight());
}

View File

@@ -71,4 +71,9 @@ public class PNGWriter implements FrameConsumer<BitmapFrame> {
@Override
public void close() {
}
@Override
public boolean isParallelCapable() {
return true;
}
}

View File

@@ -7,4 +7,6 @@ public interface FrameConsumer<P extends Frame> extends Closeable {
void consume(Map<Channel, P> channels);
boolean isParallelCapable();
}

View File

@@ -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<R extends Frame, P extends Frame> implements Runnable {
private final FrameCapturer<R> capturer;
private final FrameProcessor<R, P> processor;
private final GlToAbsoluteDepthProcessor depthProcessor;
private int consumerNextFrame;
private final Object consumerLock = new Object();
private final FrameConsumer<P> consumer;
private volatile boolean abort;
@@ -35,7 +34,7 @@ public class Pipeline<R extends Frame, P extends Frame> 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<R extends Frame, P extends Frame> 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<R extends Frame, P extends Frame> implements Runnable {
@Override
public void run() {
try {
Integer frameId = null;
Map<Channel, P> processedChannels = new HashMap<>();
for (Map.Entry<Channel, R> entry : rawChannels.entrySet()) {
P processedFrame = processor.process(entry.getValue());
@@ -114,27 +111,57 @@ public class Pipeline<R extends Frame, P extends Frame> 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<P extends Frame> implements FrameConsumer<P> {
private final FrameConsumer<P> inner;
private int nextFrame;
private final Object lock = new Object();
private ParallelSafeConsumer(FrameConsumer<P> inner) {
this.inner = inner;
}
@Override
public void consume(Map<Channel, P> 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();
}
}
}

View File

@@ -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);
}

View File

@@ -137,11 +137,19 @@ public class VideoRenderer implements RenderInfo {
}
ffmpegWriter = frameConsumer instanceof FFmpegWriter ? (FFmpegWriter) frameConsumer : null;
FrameConsumer<BitmapFrame> previewingFrameConsumer = new FrameConsumer<BitmapFrame>() {
private int lastFrameId = -1;
@Override
public void consume(Map<Channel, BitmapFrame> 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);
}