Skip to content

Commit f41c655

Browse files
committed
Merge pull request #3863 from JakubValtar/async-saveframe
Async saveFrame
2 parents 7077032 + 008bbc8 commit f41c655

File tree

6 files changed

+592
-18
lines changed

6 files changed

+592
-18
lines changed

core/src/processing/core/PConstants.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,5 +520,8 @@ public interface PConstants {
520520
static final int DISABLE_KEY_REPEAT = 11;
521521
static final int ENABLE_KEY_REPEAT = -11;
522522

523-
static final int HINT_COUNT = 12;
523+
static final int DISABLE_ASYNC_SAVEFRAME = 12;
524+
static final int ENABLE_ASYNC_SAVEFRAME = -12;
525+
526+
static final int HINT_COUNT = 13;
524527
}

core/src/processing/core/PGraphics.java

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
import java.util.Map;
3737
import java.util.HashMap;
3838
import java.util.WeakHashMap;
39+
import java.util.concurrent.ArrayBlockingQueue;
40+
import java.util.concurrent.BlockingQueue;
41+
import java.util.concurrent.ExecutorService;
42+
import java.util.concurrent.Executors;
43+
import java.util.concurrent.RejectedExecutionException;
44+
import java.util.concurrent.TimeUnit;
3945

4046
import processing.opengl.PGL;
4147
import processing.opengl.PShader;
@@ -796,6 +802,10 @@ public void setSize(int w, int h) { // ignore
796802
* endRaw(), in order to shut things off.
797803
*/
798804
public void dispose() { // ignore
805+
if (primaryGraphics && asyncImageSaver != null) {
806+
asyncImageSaver.dispose();
807+
asyncImageSaver = null;
808+
}
799809
}
800810

801811

@@ -1112,20 +1122,25 @@ protected void reapplySettings() {
11121122
* hint(DISABLE_OPENGL_ERROR_REPORT) - Speeds up the P3D renderer setting
11131123
* by not checking for errors while running. Undo with hint(ENABLE_OPENGL_ERROR_REPORT).
11141124
* <br/> <br/>
1115-
* hint(ENABLE_DEPTH_READING) - Depth and stencil buffers in P2D/P3D will be
1125+
* hint(ENABLE_BUFFER_READING) - Depth and stencil buffers in P2D/P3D will be
11161126
* downsampled to make PGL#readPixels work with multisampling. Enabling this
11171127
* introduces some overhead, so if you experience bad performance, disable
11181128
* multisampling with noSmooth() instead. This hint is not intended to be
11191129
* enabled and disabled repeatedely, so call this once in setup() or after
11201130
* creating your PGraphics2D/3D. You can restore the default with
1121-
* hint(DISABLE_DEPTH_READING) if you don't plan to read depth from
1131+
* hint(DISABLE_BUFFER_READING) if you don't plan to read depth from
11221132
* this PGraphics anymore.
11231133
* <br/> <br/>
1124-
* hint(ENABLE_KEY_AUTO_REPEAT) - Auto-repeating key events are discarded
1134+
* hint(ENABLE_KEY_REPEAT) - Auto-repeating key events are discarded
11251135
* by default (works only in P2D/P3D); use this hint to get all the key events
1126-
* (including auto-repeated). Call hint(DISABLE_KEY_AUTO_REPEAT) to get events
1136+
* (including auto-repeated). Call hint(DISABLE_KEY_REPEAT) to get events
11271137
* only when the key goes physically up or down.
11281138
* <br/> <br/>
1139+
* hint(DISABLE_ASYNC_SAVEFRAME) - P2D/P3D only - save() and saveFrame()
1140+
* will not use separate threads for saving and will block until the image
1141+
* is written to the drive. This was the default behavior in 3.0b7 and before.
1142+
* To enable, call hint(ENABLE_ASYNC_SAVEFRAME).
1143+
* <br/> <br/>
11291144
* As of release 0149, unhint() has been removed in favor of adding
11301145
* additional ENABLE/DISABLE constants to reset the default behavior. This
11311146
* prevents the double negatives, and also reinforces which hints can be
@@ -8226,4 +8241,158 @@ public boolean isGL() { // ignore
82268241
public boolean is2X() {
82278242
return pixelDensity == 2;
82288243
}
8244+
8245+
8246+
//////////////////////////////////////////////////////////////
8247+
8248+
// ASYNC IMAGE SAVING
8249+
8250+
8251+
@Override
8252+
public boolean save(String filename) {
8253+
8254+
if (hints[DISABLE_ASYNC_SAVEFRAME]) {
8255+
return super.save(filename);
8256+
}
8257+
8258+
if (asyncImageSaver == null) {
8259+
asyncImageSaver = new AsyncImageSaver();
8260+
}
8261+
8262+
if (!loaded) loadPixels();
8263+
PImage target = asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight,
8264+
format);
8265+
if (target == null) return false;
8266+
int count = PApplet.min(pixels.length, target.pixels.length);
8267+
System.arraycopy(pixels, 0, target.pixels, 0, count);
8268+
asyncImageSaver.saveTargetAsync(this, target, filename);
8269+
8270+
return true;
8271+
}
8272+
8273+
protected void processImageBeforeAsyncSave(PImage image) { }
8274+
8275+
8276+
protected static AsyncImageSaver asyncImageSaver;
8277+
8278+
protected static class AsyncImageSaver {
8279+
8280+
static final int TARGET_COUNT =
8281+
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
8282+
8283+
BlockingQueue<PImage> targetPool = new ArrayBlockingQueue<>(TARGET_COUNT);
8284+
ExecutorService saveExecutor = Executors.newFixedThreadPool(TARGET_COUNT);
8285+
8286+
int targetsCreated = 0;
8287+
8288+
8289+
static final int TIME_AVG_FACTOR = 32;
8290+
8291+
volatile long avgNanos = 0;
8292+
long lastTime = 0;
8293+
int lastFrameCount = 0;
8294+
8295+
8296+
public AsyncImageSaver() { }
8297+
8298+
8299+
public void dispose() {
8300+
saveExecutor.shutdown();
8301+
try {
8302+
saveExecutor.awaitTermination(5000, TimeUnit.SECONDS);
8303+
} catch (InterruptedException e) { }
8304+
}
8305+
8306+
8307+
public boolean hasAvailableTarget() {
8308+
return targetsCreated < TARGET_COUNT || targetPool.isEmpty();
8309+
}
8310+
8311+
8312+
/**
8313+
* After taking a target, you must call saveTargetAsync() or
8314+
* returnUnusedTarget(), otherwise one thread won't be able to run
8315+
*/
8316+
public PImage getAvailableTarget(int requestedWidth, int requestedHeight,
8317+
int format) {
8318+
try {
8319+
PImage target;
8320+
if (targetsCreated < TARGET_COUNT && targetPool.isEmpty()) {
8321+
target = new PImage(requestedWidth, requestedHeight);
8322+
targetsCreated++;
8323+
} else {
8324+
target = targetPool.take();
8325+
if (target.width != requestedWidth ||
8326+
target.height != requestedHeight) {
8327+
target.width = requestedWidth;
8328+
target.height = requestedHeight;
8329+
// TODO: this kills performance when saving different sizes
8330+
target.pixels = new int[requestedWidth * requestedHeight];
8331+
}
8332+
}
8333+
target.format = format;
8334+
return target;
8335+
} catch (InterruptedException e) {
8336+
return null;
8337+
}
8338+
}
8339+
8340+
8341+
public void returnUnusedTarget(PImage target) {
8342+
targetPool.offer(target);
8343+
}
8344+
8345+
8346+
public void saveTargetAsync(final PGraphics renderer, final PImage target,
8347+
final String filename) {
8348+
target.parent = renderer.parent;
8349+
8350+
// if running every frame, smooth the framerate
8351+
if (target.parent.frameCount - 1 == lastFrameCount && TARGET_COUNT > 1) {
8352+
8353+
// count with one less thread to reduce jitter
8354+
// 2 cores - 1 save thread - no wait
8355+
// 4 cores - 3 save threads - wait 1/2 of save time
8356+
// 8 cores - 7 save threads - wait 1/6 of save time
8357+
long avgTimePerFrame = avgNanos / (Math.max(1, TARGET_COUNT - 1));
8358+
long now = System.nanoTime();
8359+
long delay = PApplet.round((lastTime + avgTimePerFrame - now) / 1e6f);
8360+
try {
8361+
if (delay > 0) Thread.sleep(delay);
8362+
} catch (InterruptedException e) { }
8363+
}
8364+
8365+
lastFrameCount = target.parent.frameCount;
8366+
lastTime = System.nanoTime();
8367+
8368+
try {
8369+
saveExecutor.submit(new Runnable() {
8370+
@Override
8371+
public void run() {
8372+
try {
8373+
long startTime = System.nanoTime();
8374+
renderer.processImageBeforeAsyncSave(target);
8375+
target.save(filename);
8376+
long saveNanos = System.nanoTime() - startTime;
8377+
synchronized (AsyncImageSaver.this) {
8378+
if (avgNanos == 0) {
8379+
avgNanos = saveNanos;
8380+
} else if (saveNanos < avgNanos) {
8381+
avgNanos = (avgNanos * (TIME_AVG_FACTOR - 1) + saveNanos) /
8382+
(TIME_AVG_FACTOR);
8383+
} else {
8384+
avgNanos = saveNanos;
8385+
}
8386+
}
8387+
} finally {
8388+
targetPool.offer(target);
8389+
}
8390+
}
8391+
});
8392+
} catch (RejectedExecutionException e) {
8393+
// the executor service was probably shut down, no more saving for us
8394+
}
8395+
}
8396+
}
8397+
82298398
}

core/src/processing/opengl/PGL.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2017,9 +2017,19 @@ protected boolean validateFramebuffer() {
20172017
return false;
20182018
}
20192019

2020+
protected boolean isES() {
2021+
return getString(VERSION).trim().toLowerCase().contains("opengl es");
2022+
}
20202023

20212024
protected int[] getGLVersion() {
2022-
String version = getString(VERSION).trim();
2025+
String version = getString(VERSION).trim().toLowerCase();
2026+
2027+
String ES = "opengl es";
2028+
int esPosition = version.indexOf(ES);
2029+
if (esPosition >= 0) {
2030+
version = version.substring(esPosition + ES.length()).trim();
2031+
}
2032+
20232033
int[] res = {0, 0, 0};
20242034
String[] parts = version.split(" ");
20252035
for (int i = 0; i < parts.length; i++) {
@@ -2132,6 +2142,24 @@ protected boolean hasAnisoSamplingSupport() {
21322142
}
21332143

21342144

2145+
protected boolean hasSynchronization() {
2146+
int[] version = getGLVersion();
2147+
if (isES()) {
2148+
return version[0] >= 3;
2149+
}
2150+
return (version[0] > 3) || (version[0] == 3 && version[1] >= 2);
2151+
}
2152+
2153+
2154+
protected boolean hasPBOs() {
2155+
int[] version = getGLVersion();
2156+
if (isES()) {
2157+
return version[0] >= 3;
2158+
}
2159+
return (version[0] > 2) || (version[0] == 2 && version[1] >= 1);
2160+
}
2161+
2162+
21352163
protected int maxSamples() {
21362164
intBuffer.rewind();
21372165
getIntegerv(MAX_SAMPLES, intBuffer);
@@ -2681,12 +2709,14 @@ protected interface FontOutline {
26812709

26822710
public static int ARRAY_BUFFER;
26832711
public static int ELEMENT_ARRAY_BUFFER;
2712+
public static int PIXEL_PACK_BUFFER;
26842713

26852714
public static int MAX_VERTEX_ATTRIBS;
26862715

26872716
public static int STATIC_DRAW;
26882717
public static int DYNAMIC_DRAW;
26892718
public static int STREAM_DRAW;
2719+
public static int STREAM_READ;
26902720

26912721
public static int BUFFER_SIZE;
26922722
public static int BUFFER_USAGE;
@@ -2901,6 +2931,10 @@ protected interface FontOutline {
29012931
public static int LINE_SMOOTH;
29022932
public static int POLYGON_SMOOTH;
29032933

2934+
public static int SYNC_GPU_COMMANDS_COMPLETE;
2935+
public static int ALREADY_SIGNALED;
2936+
public static int CONDITION_SATISFIED;
2937+
29042938
///////////////////////////////////////////////////////////
29052939

29062940
// Special Functions
@@ -2945,6 +2979,14 @@ protected interface FontOutline {
29452979

29462980
//////////////////////////////////////////////////////////////////////////////
29472981

2982+
// Synchronization
2983+
2984+
public abstract long fenceSync(int condition, int flags);
2985+
public abstract void deleteSync(long sync);
2986+
public abstract int clientWaitSync(long sync, int flags, long timeout);
2987+
2988+
//////////////////////////////////////////////////////////////////////////////
2989+
29482990
// Viewport and Clipping
29492991

29502992
public abstract void depthRangef(float n, float f);
@@ -2977,7 +3019,23 @@ public void readPixels(int x, int y, int width, int height, int format, int type
29773019
g.endReadPixels();
29783020
}
29793021

3022+
public void readPixels(int x, int y, int width, int height, int format, int type, long offset){
3023+
boolean multisampled = isMultisampled() || graphics.offscreenMultisample;
3024+
boolean depthReadingEnabled = graphics.getHint(PConstants.ENABLE_BUFFER_READING);
3025+
boolean depthRequested = format == STENCIL_INDEX || format == DEPTH_COMPONENT || format == DEPTH_STENCIL;
3026+
3027+
if (multisampled && depthRequested && !depthReadingEnabled) {
3028+
PGraphics.showWarning(DEPTH_READING_NOT_ENABLED_ERROR);
3029+
return;
3030+
}
3031+
3032+
graphics.beginReadPixels();
3033+
readPixelsImpl(x, y, width, height, format, type, offset);
3034+
graphics.endReadPixels();
3035+
}
3036+
29803037
protected abstract void readPixelsImpl(int x, int y, int width, int height, int format, int type, Buffer buffer);
3038+
protected abstract void readPixelsImpl(int x, int y, int width, int height, int format, int type, long offset);
29813039

29823040
//////////////////////////////////////////////////////////////////////////////
29833041

0 commit comments

Comments
 (0)