Skip to content

Commit fb8feaf

Browse files
committed
sync with java mode
1 parent 8101d02 commit fb8feaf

File tree

5 files changed

+742
-161
lines changed

5 files changed

+742
-161
lines changed

core/src/processing/core/PConstants.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,13 @@ public interface PConstants {
422422
static final int ENABLE_STROKE_PURE = 9;
423423
static final int DISABLE_STROKE_PURE = -9;
424424

425-
static final int ENABLE_BUFFER_READING = 10;
426-
static final int DISABLE_BUFFER_READING = -10;
425+
static final int ENABLE_BUFFER_READING = 10;
426+
static final int DISABLE_BUFFER_READING = -10;
427427

428-
static final int HINT_COUNT = 11;
428+
static final int DISABLE_ASYNC_SAVEFRAME = 11;
429+
static final int ENABLE_ASYNC_SAVEFRAME = -11;
430+
431+
static final int HINT_COUNT = 12;
429432

430433

431434
// error messages

core/src/processing/core/PGraphics.java

+160
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525

2626
import java.util.HashMap;
2727
import java.util.WeakHashMap;
28+
import java.util.concurrent.ArrayBlockingQueue;
29+
import java.util.concurrent.BlockingQueue;
30+
import java.util.concurrent.ExecutorService;
31+
import java.util.concurrent.Executors;
32+
import java.util.concurrent.RejectedExecutionException;
33+
import java.util.concurrent.TimeUnit;
2834

2935
import processing.opengl.PGL;
3036
import processing.opengl.PShader;
@@ -5664,4 +5670,158 @@ public boolean is3D() {
56645670
public boolean isGL() {
56655671
return false;
56665672
}
5673+
5674+
5675+
//////////////////////////////////////////////////////////////
5676+
5677+
// ASYNC IMAGE SAVING
5678+
5679+
5680+
@Override
5681+
public boolean save(String filename) { // ignore
5682+
5683+
if (hints[DISABLE_ASYNC_SAVEFRAME]) {
5684+
return super.save(filename);
5685+
}
5686+
5687+
if (asyncImageSaver == null) {
5688+
asyncImageSaver = new AsyncImageSaver();
5689+
}
5690+
5691+
if (!loaded) loadPixels();
5692+
PImage target = asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight,
5693+
format);
5694+
if (target == null) return false;
5695+
int count = PApplet.min(pixels.length, target.pixels.length);
5696+
System.arraycopy(pixels, 0, target.pixels, 0, count);
5697+
asyncImageSaver.saveTargetAsync(this, target, filename);
5698+
5699+
return true;
5700+
}
5701+
5702+
protected void processImageBeforeAsyncSave(PImage image) { }
5703+
5704+
5705+
protected static AsyncImageSaver asyncImageSaver;
5706+
5707+
protected static class AsyncImageSaver {
5708+
5709+
static final int TARGET_COUNT =
5710+
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
5711+
5712+
BlockingQueue<PImage> targetPool = new ArrayBlockingQueue<>(TARGET_COUNT);
5713+
ExecutorService saveExecutor = Executors.newFixedThreadPool(TARGET_COUNT);
5714+
5715+
int targetsCreated = 0;
5716+
5717+
5718+
static final int TIME_AVG_FACTOR = 32;
5719+
5720+
volatile long avgNanos = 0;
5721+
long lastTime = 0;
5722+
int lastFrameCount = 0;
5723+
5724+
5725+
public AsyncImageSaver() { } // ignore
5726+
5727+
5728+
public void dispose() { // ignore
5729+
saveExecutor.shutdown();
5730+
try {
5731+
saveExecutor.awaitTermination(5000, TimeUnit.SECONDS);
5732+
} catch (InterruptedException e) { }
5733+
}
5734+
5735+
5736+
public boolean hasAvailableTarget() { // ignore
5737+
return targetsCreated < TARGET_COUNT || targetPool.isEmpty();
5738+
}
5739+
5740+
5741+
/**
5742+
* After taking a target, you must call saveTargetAsync() or
5743+
* returnUnusedTarget(), otherwise one thread won't be able to run
5744+
*/
5745+
public PImage getAvailableTarget(int requestedWidth, int requestedHeight, // ignore
5746+
int format) {
5747+
try {
5748+
PImage target;
5749+
if (targetsCreated < TARGET_COUNT && targetPool.isEmpty()) {
5750+
target = new PImage(requestedWidth, requestedHeight);
5751+
targetsCreated++;
5752+
} else {
5753+
target = targetPool.take();
5754+
if (target.width != requestedWidth ||
5755+
target.height != requestedHeight) {
5756+
target.width = requestedWidth;
5757+
target.height = requestedHeight;
5758+
// TODO: this kills performance when saving different sizes
5759+
target.pixels = new int[requestedWidth * requestedHeight];
5760+
}
5761+
}
5762+
target.format = format;
5763+
return target;
5764+
} catch (InterruptedException e) {
5765+
return null;
5766+
}
5767+
}
5768+
5769+
5770+
public void returnUnusedTarget(PImage target) { // ignore
5771+
targetPool.offer(target);
5772+
}
5773+
5774+
5775+
public void saveTargetAsync(final PGraphics renderer, final PImage target, // ignore
5776+
final String filename) {
5777+
target.parent = renderer.parent;
5778+
5779+
// if running every frame, smooth the framerate
5780+
if (target.parent.frameCount - 1 == lastFrameCount && TARGET_COUNT > 1) {
5781+
5782+
// count with one less thread to reduce jitter
5783+
// 2 cores - 1 save thread - no wait
5784+
// 4 cores - 3 save threads - wait 1/2 of save time
5785+
// 8 cores - 7 save threads - wait 1/6 of save time
5786+
long avgTimePerFrame = avgNanos / (Math.max(1, TARGET_COUNT - 1));
5787+
long now = System.nanoTime();
5788+
long delay = PApplet.round((lastTime + avgTimePerFrame - now) / 1e6f);
5789+
try {
5790+
if (delay > 0) Thread.sleep(delay);
5791+
} catch (InterruptedException e) { }
5792+
}
5793+
5794+
lastFrameCount = target.parent.frameCount;
5795+
lastTime = System.nanoTime();
5796+
5797+
try {
5798+
saveExecutor.submit(new Runnable() {
5799+
@Override
5800+
public void run() { // ignore
5801+
try {
5802+
long startTime = System.nanoTime();
5803+
renderer.processImageBeforeAsyncSave(target);
5804+
target.save(filename);
5805+
long saveNanos = System.nanoTime() - startTime;
5806+
synchronized (AsyncImageSaver.this) {
5807+
if (avgNanos == 0) {
5808+
avgNanos = saveNanos;
5809+
} else if (saveNanos < avgNanos) {
5810+
avgNanos = (avgNanos * (TIME_AVG_FACTOR - 1) + saveNanos) /
5811+
(TIME_AVG_FACTOR);
5812+
} else {
5813+
avgNanos = saveNanos;
5814+
}
5815+
}
5816+
} finally {
5817+
targetPool.offer(target);
5818+
}
5819+
}
5820+
});
5821+
} catch (RejectedExecutionException e) {
5822+
// the executor service was probably shut down, no more saving for us
5823+
}
5824+
}
5825+
}
5826+
56675827
}

0 commit comments

Comments
 (0)