diff --git a/src/main/java/bdv/img/cache/Cache.java b/src/main/java/bdv/img/cache/Cache.java
index e2edb3e9c47155d3939adb7cf9e1b40a655265ed..488e0ab9617886a20da5f9d08cbcee8701f89ecc 100644
--- a/src/main/java/bdv/img/cache/Cache.java
+++ b/src/main/java/bdv/img/cache/Cache.java
@@ -26,5 +26,5 @@ public interface Cache
/**
* (Re-)initialize the IO time budget.
*/
- public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize );
+ public void initIoTimeBudget( final long[] partialBudget );
}
diff --git a/src/main/java/bdv/img/cache/CacheIoTiming.java b/src/main/java/bdv/img/cache/CacheIoTiming.java
index d8ff08b32dcfeccdfe9548c970b878ed3095aa4a..b2ff0cf83297e5ff5658527b993490bb540e2472 100644
--- a/src/main/java/bdv/img/cache/CacheIoTiming.java
+++ b/src/main/java/bdv/img/cache/CacheIoTiming.java
@@ -20,22 +20,31 @@ public class CacheIoTiming
*/
public static class IoTimeBudget
{
- private final long[] initialBudget;
-
private final long[] budget;
- public IoTimeBudget( final long[] initBudget )
+ public IoTimeBudget( final int numLevels )
+ {
+ budget = new long[ numLevels ];
+ }
+
+ public synchronized void reset( final long[] partialBudget )
{
- initialBudget = initBudget.clone();
- for ( int l = 1; l < initialBudget.length; ++l )
- if ( initialBudget[ l ] > initialBudget[ l - 1 ] )
- initialBudget[ l ] = initialBudget[ l - 1 ];
- budget = initialBudget.clone();
+ if ( partialBudget == null )
+ clear();
+ else
+ {
+ for ( int i = 0; i < budget.length; ++i )
+ budget[ i ] = partialBudget.length > i ? partialBudget[ i ] : partialBudget[ partialBudget.length - 1 ];
+ for ( int i = 1; i < budget.length; ++i )
+ if ( budget[ i ] > budget[ i - 1 ] )
+ budget[ i ] = budget[ i - 1 ];
+ }
}
- public void reset()
+ public synchronized void clear()
{
- System.arraycopy( initialBudget, 0, budget, 0, budget.length );
+ for ( int i = 0; i < budget.length; ++i )
+ budget[ i ] = 0;
}
public synchronized long timeLeft( final int level )
diff --git a/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java b/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
index 5745b990f35e5c8074684d4674f2b4f29b979307..e94e6b353eb5795535fae3da6e58b2ffd54d376c 100644
--- a/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
+++ b/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
@@ -4,6 +4,8 @@ import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess;
@@ -99,6 +101,12 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
protected final ConcurrentHashMap< Key, Reference< Entry > > softReferenceCache = new ConcurrentHashMap< Key, Reference< Entry > >();
+ /**
+ * Keeps references to the {@link Entry entries} accessed in the current
+ * frame, such that they cannot be cleared from the cache prematurely.
+ */
+ protected final List< Entry > currentFrameEntries = Collections.synchronizedList( new ArrayList< Entry >() );
+
protected final BlockingFetchQueues< Key > queue;
protected volatile long currentQueueFrame = 0;
@@ -123,7 +131,10 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
{
try
{
- Thread.sleep( 1 );
+ synchronized ( lock )
+ {
+ lock.wait( waitMillis );
+ }
}
catch ( final InterruptedException e )
{}
@@ -139,6 +150,8 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
}
}
+ private final Object lock = new Object();
+
private volatile long pauseUntilTimeMillis = 0;
public void pauseUntil( final long timeMillis )
@@ -150,25 +163,36 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
public void wakeUp()
{
pauseUntilTimeMillis = 0;
+ synchronized ( lock )
+ {
+ lock.notify();
+ }
}
}
- public void pauseFetcherThreads()
- {
- pauseFetcherThreadsUntil( System.currentTimeMillis() + 5 );
- }
-
- public void pauseFetcherThreads( final long ms )
+ /**
+ * pause all {@link Fetcher} threads for the specified number of milliseconds.
+ */
+ public void pauseFetcherThreadsFor( final long ms )
{
pauseFetcherThreadsUntil( System.currentTimeMillis() + ms );
}
+ /**
+ * pause all {@link Fetcher} threads until the given time (see
+ * {@link System#currentTimeMillis()}).
+ */
public void pauseFetcherThreadsUntil( final long timeMillis )
{
for ( final Fetcher f : fetchers )
f.pauseUntil( timeMillis );
}
+ /**
+ * Wake up all Fetcher threads immediately. This ends any
+ * {@link #pauseFetcherThreadsFor(long)} and
+ * {@link #pauseFetcherThreadsUntil(long)} set earlier.
+ */
public void wakeFetcherThreads()
{
for ( final Fetcher f : fetchers )
@@ -179,7 +203,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
private final CacheArrayLoader< A > loader;
- public VolatileGlobalCellCache( final CacheArrayLoader< A > loader, final int numTimepoints, final int numSetups, final int maxNumLevels, final int[] maxLevels )
+ public VolatileGlobalCellCache( final CacheArrayLoader< A > loader, final int numTimepoints, final int numSetups, final int maxNumLevels, final int[] maxLevels, final int numFetcherThreads )
{
this.loader = loader;
this.numTimepoints = numTimepoints;
@@ -189,7 +213,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
queue = new BlockingFetchQueues< Key >( maxNumLevels );
fetchers = new ArrayList< Fetcher >();
- for ( int i = 0; i < 1; ++i ) // TODO: add numFetcherThreads parameter
+ for ( int i = 0; i < numFetcherThreads; ++i )
{
final Fetcher f = new Fetcher();
f.setDaemon( true );
@@ -240,7 +264,9 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
{
final VolatileCell< A > cell = new VolatileCell< A >( cellDims, cellMin, loader.loadArray( timepoint, setup, level, cellDims, cellMin ) );
entry.data = cell;
+ entry.enqueueFrame = Long.MAX_VALUE;
softReferenceCache.put( entry.key, new SoftReference< Entry >( entry ) );
+ entry.notifyAll();
}
}
}
@@ -258,6 +284,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
final Key k = entry.key;
final int priority = maxLevels[ k.setup ] - k.level;
queue.put( k, priority );
+ currentFrameEntries.add( entry );
}
}
@@ -272,26 +299,26 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
final IoTimeBudget budget = stats.getIoTimeBudget();
final Key k = entry.key;
final int priority = maxLevels[ k.setup ] - k.level;
- if ( budget.timeLeft( priority ) > 0 )
+ final long timeLeft = budget.timeLeft( priority );
+ if ( timeLeft > 0 )
{
- pauseFetcherThreads( 1000 );
- final long t0 = stats.getIoNanoTime();
- stats.start();
- while( true )
+ synchronized ( entry )
+ {
+ if ( entry.data.getData().isValid() )
+ return;
+ enqueueEntry( entry );
+ final long t0 = stats.getIoNanoTime();
+ stats.start();
try
{
- loadEntryIfNotValid( entry );
- break;
+ entry.wait( timeLeft / 1000000l, 1 );
}
catch ( final InterruptedException e )
{}
- stats.stop();
- final long t = stats.getIoNanoTime() - t0;
- budget.use( t, priority );
- if ( budget.timeLeft( 0 ) <= 0 )
- wakeFetcherThreads();
- else
- pauseFetcherThreads( 3 );
+ stats.stop();
+ final long t = stats.getIoNanoTime() - t0;
+ budget.use( t, priority );
+ }
}
else
enqueueEntry( entry );
@@ -432,6 +459,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
public void prepareNextFrame()
{
queue.clear();
+ currentFrameEntries.clear();
++currentQueueFrame;
}
@@ -445,23 +473,14 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
* smaller-equal the budget for level <em>j</em>. If <em>n</em>
* is smaller than the maximum number of mipmap levels, the
* remaining priority levels are filled up with budget[n].
- * @param reinitialize
- * If true, the IO time budget is initialized to the given
- * partial budget. If false, the IO time budget is reset to the
- * previous initial values.
*/
@Override
- public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize )
+ public void initIoTimeBudget( final long[] partialBudget )
{
final IoStatistics stats = CacheIoTiming.getThreadGroupIoStatistics();
- if ( reinitialize || stats.getIoTimeBudget() == null )
- {
- final long[] budget = new long[ maxNumLevels ];
- for ( int i = 0; i < budget.length; ++i )
- budget[ i ] = partialBudget.length > i ? partialBudget[ i ] : partialBudget[ partialBudget.length - 1 ];
- stats.setIoTimeBudget( new IoTimeBudget( budget ) );
- }
- stats.getIoTimeBudget().reset();
+ if ( stats.getIoTimeBudget() == null )
+ stats.setIoTimeBudget( new IoTimeBudget( maxNumLevels ) );
+ stats.getIoTimeBudget().reset( partialBudget );
}
public class Hdf5CellCache implements CellCache< A >
diff --git a/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java b/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
index c61450cb0d52b7f4e9cb0b807981e4f35681440d..35b338a4868af60eceae2b00fb2d4d28a3981558 100644
--- a/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
+++ b/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
@@ -141,7 +141,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader
cachedDimensions = new long[ numTimepoints * numSetups * maxNumLevels ][];
cachedExistence = new Boolean[ numTimepoints * numSetups * maxNumLevels ];
- cache = new VolatileGlobalCellCache< VolatileShortArray >( new Hdf5VolatileShortArrayLoader( hdf5Reader ), numTimepoints, numSetups, maxNumLevels, maxLevels );
+ cache = new VolatileGlobalCellCache< VolatileShortArray >( new Hdf5VolatileShortArrayLoader( hdf5Reader ), numTimepoints, numSetups, maxNumLevels, maxLevels, 1 );
}
@Override
@@ -255,8 +255,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader
System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found. Partition file missing?" );
return getMissingDataImage( view, level, new VolatileUnsignedShortType() );
}
- final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.VOLATILE );
-// final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
+ final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
final VolatileUnsignedShortType linkedType = new VolatileUnsignedShortType( img );
img.setLinkedType( linkedType );
return img;
@@ -325,7 +324,11 @@ public class Hdf5ImageLoader implements ViewerImgLoader
final String cellsPath = Util.getCellsPath( timepoint, setup, level );
HDF5DataSetInformation info = null;
boolean exists = false;
- cache.pauseFetcherThreads();
+ // pause Fetcher threads for 5 ms. There will be more calls to
+ // getImageDimension() because this happens when a timepoint is
+ // loaded, and all setups for the timepoint are loaded then. We
+ // don't want to interleave this with block loading operations.
+ cache.pauseFetcherThreadsFor( 5 );
synchronized ( hdf5Reader )
{
try {
diff --git a/src/main/java/bdv/img/hdf5/Hdf5VolatileShortArrayLoader.java b/src/main/java/bdv/img/hdf5/Hdf5VolatileShortArrayLoader.java
index 07eee2dc17566734dae5b92d45faad48df57d4fc..69da6ed268f0193cb8b8627102c74afa59ebab38 100644
--- a/src/main/java/bdv/img/hdf5/Hdf5VolatileShortArrayLoader.java
+++ b/src/main/java/bdv/img/hdf5/Hdf5VolatileShortArrayLoader.java
@@ -2,9 +2,6 @@ package bdv.img.hdf5;
import static bdv.img.hdf5.Util.getCellsPath;
import static bdv.img.hdf5.Util.reorder;
-
-import java.io.PrintStream;
-
import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
import bdv.img.cache.CacheArrayLoader;
import ch.systemsx.cisd.base.mdarray.MDShortArray;
@@ -16,74 +13,27 @@ public class Hdf5VolatileShortArrayLoader implements CacheArrayLoader< VolatileS
private VolatileShortArray theEmptyArray;
- PrintStream log = System.out;
-
public Hdf5VolatileShortArrayLoader( final IHDF5Reader hdf5Reader )
{
this.hdf5Reader = hdf5Reader;
theEmptyArray = new VolatileShortArray( 32 * 32 * 32, false );
}
-// @Override
-// public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
-// {
-// final MDShortArray array;
-// synchronized ( hdf5Reader )
-// {
-// if ( Thread.interrupted() )
-// throw new InterruptedException();
-// array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorder( dimensions ), reorder( min ) );
-// }
-// return new VolatileShortArray( array.getAsFlatArray(), true );
-// }
-
- public static volatile long pStart = System.currentTimeMillis();
-
- public static volatile long pEnd = System.currentTimeMillis();
-
- public static volatile long tLoad = 0;
-
- public static volatile long sLoad = 0;
-
private final int[] reorderedDimensions = new int[ 3 ];
private final long[] reorderedMin = new long[ 3 ];
@Override
- public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min )
+ public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
{
final MDShortArray array;
synchronized ( hdf5Reader )
{
- pStart = System.currentTimeMillis();
-// log.println( String.format( "%3d %d %d { %2d, %2d, %2d } { %4d, %4d, %4d }", timepoint, setup, level,
-// dimensions[0], dimensions[1], dimensions[2],
-// min[0], min[1], min[2] ) );
- final long msBetweenLoads = pStart - pEnd;
- if ( msBetweenLoads > 2 )
- {
- log.println( msBetweenLoads + " ms pause before this load." );
-// final StringWriter sw = new StringWriter();
-// final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
-// for ( final StackTraceElement elem : trace )
-// sw.write( elem.getClassName() + "." + elem.getMethodName() + "\n" );
-// log.println( sw.toString() );
- }
- final long t0 = System.currentTimeMillis();
+ if ( Thread.interrupted() )
+ throw new InterruptedException();
reorder( dimensions, reorderedDimensions );
reorder( min, reorderedMin );
array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
- pEnd = System.currentTimeMillis();
- final long t = pEnd - t0;
- final long size = array.size();
- tLoad += t;
- sLoad += size;
- if ( sLoad > 1000000 )
- {
- log.println( String.format( "%.0f k shorts/sec ", ( ( double ) sLoad / tLoad ) ) );
- tLoad = 1;
- sLoad = 1;
- }
}
return new VolatileShortArray( array.getAsFlatArray(), true );
}
@@ -103,4 +53,46 @@ public class Hdf5VolatileShortArrayLoader implements CacheArrayLoader< VolatileS
public int getBytesPerElement() {
return 2;
}
+
+// PrintStream log = System.out;
+// public static volatile long pStart = System.currentTimeMillis();
+// public static volatile long pEnd = System.currentTimeMillis();
+// public static volatile long tLoad = 0;
+// public static volatile long sLoad = 0;
+//
+// @Override
+// public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min )
+// {
+// final MDShortArray array;
+// synchronized ( hdf5Reader )
+// {
+// pStart = System.currentTimeMillis();
+// final long msBetweenLoads = pStart - pEnd;
+// if ( msBetweenLoads > 2 )
+// {
+// log.println( msBetweenLoads + " ms pause before this load." );
+// final StringWriter sw = new StringWriter();
+// final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+// for ( final StackTraceElement elem : trace )
+// sw.write( elem.getClassName() + "." + elem.getMethodName() + "\n" );
+// log.println( sw.toString() );
+// }
+// final long t0 = System.currentTimeMillis();
+// reorder( dimensions, reorderedDimensions );
+// reorder( min, reorderedMin );
+// array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
+// pEnd = System.currentTimeMillis();
+// final long t = System.currentTimeMillis() - t0;
+// final long size = array.size();
+// tLoad += t;
+// sLoad += size;
+// if ( sLoad > 1000000 )
+// {
+// log.println( String.format( "%.0f k shorts/sec ", ( ( double ) sLoad / tLoad ) ) );
+// tLoad = 1;
+// sLoad = 1;
+// }
+// }
+// return new VolatileShortArray( array.getAsFlatArray(), true );
+// }
}
diff --git a/src/main/java/bdv/tools/RecordMovieDialog.java b/src/main/java/bdv/tools/RecordMovieDialog.java
index cb5518a97e5dd636f2acb7d465a8768bf7ca39e4..f5cc64a3037e299bd94447b944f3f2cb529d9c3c 100644
--- a/src/main/java/bdv/tools/RecordMovieDialog.java
+++ b/src/main/java/bdv/tools/RecordMovieDialog.java
@@ -262,7 +262,7 @@ public class RecordMovieDialog extends JDialog implements OverlayRenderer
final MultiResolutionRenderer renderer = new MultiResolutionRenderer( target, new PainterThread( null ), new double[] { 1 }, 0, false, 1, null, false, new Cache()
{
@Override
- public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize )
+ public void initIoTimeBudget( final long[] partialBudget )
{}
@Override
diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
index eb95b33ceeabb6752526d77f37ed096c13e3a467..3fa0227dec204a594c76a768722caa98faf044b8 100644
--- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
+++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
@@ -237,7 +237,10 @@ public class MultiResolutionRenderer
protected int previousTimepoint;
// TODO: should be settable
- protected long[] iobudget = new long[] { 500l * 1000000l, 0l * 1000000l };
+ protected long[] iobudget = new long[] { 100l * 1000000l, 10l * 1000000l };
+
+ // TODO: should be settable
+ protected boolean prefetchCells = true;
/**
* @param display
@@ -453,19 +456,6 @@ public class MultiResolutionRenderer
}
// try rendering
-// if ( clearQueue )
-// cache.prepareNextFrame();
-
-// if ( clearQueue )
-// try
-// {
-// Thread.sleep( 100 );
-// }
-// catch ( final InterruptedException e1 )
-// {
-// // TODO Auto-generated catch block
-// e1.printStackTrace();
-// }
final boolean success = p.map( createProjector );
final long rendertime = p.getLastFrameRenderNanoTime();
@@ -497,11 +487,9 @@ public class MultiResolutionRenderer
if ( rendertime < targetRenderNanos && maxScreenScaleIndex > 0 )
maxScreenScaleIndex--;
}
-// System.out.println( "created projector" );
// System.out.println( String.format( "rendering:%4d ms", rendertime / 1000000 ) );
// System.out.println( "scale = " + currentScreenScaleIndex );
// System.out.println( "maxScreenScaleIndex = " + maxScreenScaleIndex + " (" + screenImages[ maxScreenScaleIndex ][ 0 ].dimension( 0 ) + " x " + screenImages[ maxScreenScaleIndex ][ 0 ].dimension( 1 ) + ")" );
-// System.out.println();
}
if ( currentScreenScaleIndex > 0 )
@@ -517,8 +505,6 @@ public class MultiResolutionRenderer
requestRepaint( currentScreenScaleIndex );
}
}
-// else
-// System.out.println("! success");
}
return success;
@@ -530,7 +516,6 @@ public class MultiResolutionRenderer
*/
public synchronized void requestRepaint()
{
-// System.out.println("requestRepaint()");
newFrameRequest = true;
requestRepaint( maxScreenScaleIndex );
}
@@ -556,7 +541,7 @@ public class MultiResolutionRenderer
{
synchronized ( viewerState )
{
- cache.initIoTimeBudget( iobudget, false );
+ cache.initIoTimeBudget( null ); // clear time budget such that prefetching doesn't wait for loading blocks.
final List< SourceState< ? > > sources = viewerState.getSources();
final List< Integer > visibleSourceIndices = viewerState.getVisibleSourceIndices();
VolatileProjector projector;
@@ -586,6 +571,7 @@ public class MultiResolutionRenderer
projector = new AccumulateProjectorARGB( sourceProjectors, sourceImages, screenImage, numRenderingThreads );
}
previousTimepoint = viewerState.getCurrentTimepoint();
+ cache.initIoTimeBudget( iobudget );
return projector;
}
}
@@ -667,12 +653,14 @@ public class MultiResolutionRenderer
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, bestLevel ) );
if ( nLevels - 1 != bestLevel )
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, nLevels - 1 ) );
- final long t0 = System.currentTimeMillis();
- if ( nLevels - 1 != bestLevel )
- prefetch( viewerState, spimSource, screenScaleTransform, nLevels - 1, screenImage );
- prefetch( viewerState, spimSource, screenScaleTransform, bestLevel, screenImage );
- final long t1 = System.currentTimeMillis() - t0;
- System.out.println( "prefetch: " + t1 + " ms" );
+
+ if ( prefetchCells )
+ {
+ if ( nLevels - 1 != bestLevel )
+ prefetch( viewerState, spimSource, screenScaleTransform, nLevels - 1, screenImage );
+ prefetch( viewerState, spimSource, screenScaleTransform, bestLevel, screenImage );
+ }
+
// slight abuse of newFrameRequest: we only want this two-pass
// rendering to happen once then switch to normal multi-pass
// rendering if we remain longer on this frame.
@@ -682,11 +670,10 @@ public class MultiResolutionRenderer
{
for ( int i = bestLevel; i < nLevels; ++i )
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, i ) );
- final long t0 = System.currentTimeMillis();
- for ( int i = nLevels - 1; i >= bestLevel; --i )
- prefetch( viewerState, spimSource, screenScaleTransform, i, screenImage );
- final long t1 = System.currentTimeMillis() - t0;
- System.out.println( "prefetch: " + t1 + " ms" );
+
+ if ( prefetchCells )
+ for ( int i = nLevels - 1; i >= bestLevel; --i )
+ prefetch( viewerState, spimSource, screenScaleTransform, i, screenImage );
}
// for ( int i = bestLevel - 1; i >= 0; --i )
// levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, i ) );
@@ -714,24 +701,17 @@ public class MultiResolutionRenderer
final int mipmapIndex,
final Dimensions screenInterval )
{
-// if ( mipmapIndex != source.getNumMipmapLevels() - 1 )
-// return;
final int timepoint = viewerState.getCurrentTimepoint();
final RandomAccessibleInterval< T > img = source.getSource( timepoint, mipmapIndex );
if ( CellImg.class.isInstance( img ) )
{
-// System.out.println( "is CellImg" );
- final CellImg< ?, ?, ? > cellImg = ( CellImg ) img;
+ final CellImg< ?, ?, ? > cellImg = ( CellImg< ?, ?, ? > ) img;
final int[] cellDimensions = new int[ 3 ];
cellImg.getCells().cellDimensions( cellDimensions );
final long[] dimensions = new long[ 3 ];
cellImg.dimensions( dimensions );
final RandomAccess< ? > cellsRandomAccess = cellImg.getCells().randomAccess();
-// System.out.println( net.imglib2.util.Util.printCoordinates( cellDimensions ) );
-// System.out.println( net.imglib2.util.Util.printCoordinates( dimensions ) );
-// System.out.println();
-
final Interpolation interpolation = viewerState.getInterpolation();
final AffineTransform3D sourceToScreen = new AffineTransform3D();
@@ -739,7 +719,7 @@ public class MultiResolutionRenderer
sourceToScreen.concatenate( source.getSourceTransform( timepoint, mipmapIndex ) );
sourceToScreen.preConcatenate( screenScaleTransform );
- PlayWithGeometry.scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
+ Prefetcher.fetchCells( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
}
}
}
diff --git a/src/main/java/bdv/viewer/render/PlayWithGeometry.java b/src/main/java/bdv/viewer/render/PlayWithGeometry.java
deleted file mode 100644
index 05a76ab823f7f6dffeeffdc714c3161b5cf917c6..0000000000000000000000000000000000000000
--- a/src/main/java/bdv/viewer/render/PlayWithGeometry.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package bdv.viewer.render;
-
-import net.imglib2.Dimensions;
-import net.imglib2.RandomAccess;
-import net.imglib2.RealPoint;
-import net.imglib2.realtransform.AffineTransform3D;
-import bdv.viewer.Interpolation;
-
-public class PlayWithGeometry
-{
- public static void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
- {
- new Prefetcher().scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
- }
-
- static class Prefetcher
- {
- private final double[] xStep = new double[ 3 ];
-
- private final double[] offsetNeg = new double[ 3 ];
-
- private final double[] offsetPos = new double[ 3 ];
-
- private static final double eps = 0.0000001;
-
- public void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
- {
- final RealPoint pSource = new RealPoint( 3 );
- final RealPoint pScreen = new RealPoint( 3 );
- final int[] minCell = new int[ 3 ];
- final int[] maxCell = new int[ 3 ];
- final int w = ( int ) screenInterval.dimension( 0 );
- final int h = ( int ) screenInterval.dimension( 1 );
-
- for ( int d = 0; d < 3; ++d )
- maxCell[ d ] = ( int ) ( ( dimensions[ d ] - 1 ) / cellDimensions[ d ] );
-
- // compute bounding box
- final RealPoint[] screenCorners = new RealPoint[ 4 ];
- screenCorners[ 0 ] = new RealPoint( 0, 0, 0 );
- screenCorners[ 1 ] = new RealPoint( w, 0, 0 );
- screenCorners[ 2 ] = new RealPoint( w, h, 0 );
- screenCorners[ 3 ] = new RealPoint( 0, h, 0 );
- final RealPoint sourceCorner = new RealPoint( 3 );
- final double[] bbMin = new double[] { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
- final double[] bbMax = new double[] { Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
- for ( int i = 0; i < 4; ++i )
- {
- sourceToScreen.applyInverse( sourceCorner, screenCorners[ i ] );
- for ( int d = 0; d < 3; ++d )
- {
- final double p = sourceCorner.getDoublePosition( d );
- if ( p < bbMin[ d ] )
- bbMin[ d ] = p;
- if ( p > bbMax[ d ] )
- bbMax[ d ] = p;
- }
- }
- for ( int d = 0; d < 3; ++d )
- {
- minCell[ d ] = Math.min( maxCell[ d ], Math.max( ( int ) bbMin[ d ] / cellDimensions[ d ] - 1, 0 ) );
- maxCell[ d ] = Math.max( 0, Math.min( ( int ) bbMax[ d ] / cellDimensions[ d ] + 1, maxCell[ d ] ) );
- }
-
- checkProtoCell( cellDimensions, sourceToScreen, interpolation );
- getStep( cellDimensions, sourceToScreen );
-
- pSource.setPosition( ( minCell[ 2 ] - 1 ) * cellDimensions[ 2 ], 2 );
- for ( cellsRandomAccess.setPosition( minCell[ 2 ], 2 ); cellsRandomAccess.getIntPosition( 2 ) <= maxCell[ 2 ]; cellsRandomAccess.fwd( 2 ) )
- {
- pSource.move( cellDimensions[ 2 ], 2 );
- pSource.setPosition( ( minCell[ 1 ] - 1 ) * cellDimensions[ 1 ], 1 );
- for ( cellsRandomAccess.setPosition( minCell[ 1 ], 1 ); cellsRandomAccess.getIntPosition( 1 ) <= maxCell[ 1 ]; cellsRandomAccess.fwd( 1 ) )
- {
- pSource.move( cellDimensions[ 1 ], 1 );
-
- // find first and last cell that hits z
- pSource.setPosition( minCell[ 0 ] * cellDimensions[ 0 ], 0 );
- sourceToScreen.apply( pSource, pScreen );
- final double z0 = pScreen.getDoublePosition( 2 );
- int nStart = 0;
- int nStop = 0;
- if ( xStep[ 2 ] > eps )
- {
- nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
- if ( nStart > maxCell[ 0 ] )
- continue;
- nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
- if ( nStop < minCell[ 0 ] )
- continue;
- }
- else if ( xStep[ 2 ] < - eps )
- {
- nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
- if ( nStart > maxCell[ 0 ] )
- continue;
- nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
- if ( nStop < minCell[ 0 ] )
- continue;
- }
- else
- {
- if ( z0 + offsetNeg[ 2 ] > 0 || z0 + offsetPos[ 2 ] < 0 )
- continue;
- nStart = minCell[ 0 ];
- nStop = maxCell[ 0 ];
- }
-
- pSource.setPosition( nStart * cellDimensions[ 0 ], 0 );
- for ( cellsRandomAccess.setPosition( nStart, 0 ); cellsRandomAccess.getIntPosition( 0 ) <= nStop; cellsRandomAccess.fwd( 0 ) )
- {
- sourceToScreen.apply( pSource, pScreen );
- final double x = pScreen.getDoublePosition( 0 );
- final double y = pScreen.getDoublePosition( 1 );
- if ( ( x + offsetPos[ 0 ] >= 0 ) &&
- ( x + offsetNeg[ 0 ] < w ) &&
- ( y + offsetPos[ 1 ] >= 0 ) &&
- ( y + offsetNeg[ 1 ] < h ) )
- {
- cellsRandomAccess.get();
- }
- pSource.move( cellDimensions[ 0 ], 0 );
- }
- }
- }
- }
-
- void getStep( final int[] cellStep, final AffineTransform3D sourceToScreen )
- {
- final RealPoint p0 = new RealPoint( 3 );
- final RealPoint p1 = new RealPoint( 3 );
- p1.setPosition( cellStep[ 0 ], 0 );
- final RealPoint s0 = new RealPoint( 3 );
- final RealPoint s1 = new RealPoint( 3 );
- sourceToScreen.apply( p0, s0 );
- sourceToScreen.apply( p1, s1 );
- for ( int d = 0; d < 3; ++d )
- xStep[ d ] = s1.getDoublePosition( d ) - s0.getDoublePosition( d );
- }
-
- void checkProtoCell( final int[] cellDims, final AffineTransform3D sourceToScreen, final Interpolation interpolation )
- {
- final RealPoint pSource = new RealPoint( 3 );
- final RealPoint pScreenAnchor = new RealPoint( 3 );
- sourceToScreen.apply( pSource, pScreenAnchor );
-
- final RealPoint[] pScreen = new RealPoint[ 8 ];
- final double[] cellMin = new double[ 3 ];
- final double[] cellSize = new double[] { cellDims[ 0 ], cellDims[ 1 ], cellDims[ 2 ] };
- if ( interpolation == Interpolation.NEARESTNEIGHBOR )
- {
- for ( int d = 0; d < 3; ++d )
- {
- cellMin[ d ] -= 0.5;
- cellSize[ d ] -= 0.5;
- }
- }
- else // Interpolation.NLINEAR
- {
- for ( int d = 0; d < 3; ++d )
- cellMin[ d ] -= 1;
- }
- int i = 0;
- for ( int z = 0; z < 2; ++z )
- {
- pSource.setPosition( ( z == 0 ) ? cellMin[ 2 ] : cellSize[ 2 ], 2 );
- for ( int y = 0; y < 2; ++y )
- {
- pSource.setPosition( ( y == 0 ) ? cellMin[ 1 ] : cellSize[ 1 ], 1 );
- for ( int x = 0; x < 2; ++x )
- {
- pSource.setPosition( ( x == 0 ) ? cellMin[ 0 ] : cellSize[ 0 ], 0 );
- pScreen[ i ] = new RealPoint( 3 );
- sourceToScreen.apply( pSource, pScreen[ i++ ] );
- }
- }
- }
-
- for ( int d = 0; d < 3; ++d )
- {
- double min = pScreen[ 0 ].getDoublePosition( d );
- double max = pScreen[ 0 ].getDoublePosition( d );
- for ( i = 1; i < 8; ++i )
- {
- final double p = pScreen[ i ].getDoublePosition( d );
- if ( p < min )
- min = p;
- if ( p > max )
- max = p;
- }
- offsetNeg[ d ] = min - pScreenAnchor.getDoublePosition( d );
- offsetPos[ d ] = max - pScreenAnchor.getDoublePosition( d );
- }
- }
- }
-}
diff --git a/src/main/java/bdv/viewer/render/Prefetcher.java b/src/main/java/bdv/viewer/render/Prefetcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..527f45617d0db73dd345fad97fca96a4ea926733
--- /dev/null
+++ b/src/main/java/bdv/viewer/render/Prefetcher.java
@@ -0,0 +1,250 @@
+package bdv.viewer.render;
+
+import net.imglib2.Dimensions;
+import net.imglib2.RandomAccess;
+import net.imglib2.RealPoint;
+import net.imglib2.img.cell.CellImg;
+import net.imglib2.realtransform.AffineTransform3D;
+import bdv.viewer.Interpolation;
+
+public class Prefetcher
+{
+ /**
+ * Access cells that will be needed for rendering to the screen.
+ *
+ * @param sourceToScreen
+ * source-to-screen transform
+ * @param cellDimensions
+ * standard size of a source cell
+ * @param dimensions
+ * dimensions of the source {@link CellImg}
+ * @param screenInterval
+ * the interval of the screen that will be rendered
+ * @param interpolation
+ * the interpolation method
+ * @param cellsRandomAccess
+ * access to the source cells
+ */
+ public static void fetchCells( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
+ {
+ new Prefetcher().scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
+ }
+
+ private Prefetcher()
+ {}
+
+ /**
+ * The transformed vector in screen coordinate when moving by by one cell in
+ * X direction.
+ */
+ private final double[] xStep = new double[ 3 ];
+
+ /**
+ * The min of the bounding box of a cell under the current transform and
+ * interpolation method, as seen from the min corner of the cell.
+ */
+ private final double[] offsetNeg = new double[ 3 ];
+
+ /**
+ * The max of the bounding box of a cell under the current transform and
+ * interpolation method, as seen from the min corner of the cell.
+ */
+ private final double[] offsetPos = new double[ 3 ];
+
+ private static final double eps = 0.0000001;
+
+ /**
+ * Access cells that will be needed for rendering to the screen.
+ *
+ * @param sourceToScreen
+ * source-to-screen transform
+ * @param cellDimensions
+ * standard size of a source cell
+ * @param dimensions
+ * dimensions of the source {@link CellImg}
+ * @param screenInterval
+ * the interval of the screen that will be rendered
+ * @param interpolation
+ * the interpolation method
+ * @param cellsRandomAccess
+ * access to the source cells
+ */
+ private void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
+ {
+ final RealPoint pSource = new RealPoint( 3 );
+ final RealPoint pScreen = new RealPoint( 3 );
+ final int[] minCell = new int[ 3 ];
+ final int[] maxCell = new int[ 3 ];
+ final int w = ( int ) screenInterval.dimension( 0 );
+ final int h = ( int ) screenInterval.dimension( 1 );
+
+ for ( int d = 0; d < 3; ++d )
+ maxCell[ d ] = ( int ) ( ( dimensions[ d ] - 1 ) / cellDimensions[ d ] );
+
+ // compute bounding box
+ final RealPoint[] screenCorners = new RealPoint[ 4 ];
+ screenCorners[ 0 ] = new RealPoint( 0, 0, 0 );
+ screenCorners[ 1 ] = new RealPoint( w, 0, 0 );
+ screenCorners[ 2 ] = new RealPoint( w, h, 0 );
+ screenCorners[ 3 ] = new RealPoint( 0, h, 0 );
+ final RealPoint sourceCorner = new RealPoint( 3 );
+ final double[] bbMin = new double[] { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
+ final double[] bbMax = new double[] { Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
+ for ( int i = 0; i < 4; ++i )
+ {
+ sourceToScreen.applyInverse( sourceCorner, screenCorners[ i ] );
+ for ( int d = 0; d < 3; ++d )
+ {
+ final double p = sourceCorner.getDoublePosition( d );
+ if ( p < bbMin[ d ] )
+ bbMin[ d ] = p;
+ if ( p > bbMax[ d ] )
+ bbMax[ d ] = p;
+ }
+ }
+ for ( int d = 0; d < 3; ++d )
+ {
+ minCell[ d ] = Math.min( maxCell[ d ], Math.max( ( int ) bbMin[ d ] / cellDimensions[ d ] - 1, 0 ) );
+ maxCell[ d ] = Math.max( 0, Math.min( ( int ) bbMax[ d ] / cellDimensions[ d ] + 1, maxCell[ d ] ) );
+ }
+
+ checkProtoCell( cellDimensions, sourceToScreen, interpolation );
+ getXStep( cellDimensions, sourceToScreen );
+
+ pSource.setPosition( ( minCell[ 2 ] - 1 ) * cellDimensions[ 2 ], 2 );
+ for ( cellsRandomAccess.setPosition( minCell[ 2 ], 2 ); cellsRandomAccess.getIntPosition( 2 ) <= maxCell[ 2 ]; cellsRandomAccess.fwd( 2 ) )
+ {
+ pSource.move( cellDimensions[ 2 ], 2 );
+ pSource.setPosition( ( minCell[ 1 ] - 1 ) * cellDimensions[ 1 ], 1 );
+ for ( cellsRandomAccess.setPosition( minCell[ 1 ], 1 ); cellsRandomAccess.getIntPosition( 1 ) <= maxCell[ 1 ]; cellsRandomAccess.fwd( 1 ) )
+ {
+ pSource.move( cellDimensions[ 1 ], 1 );
+
+ // find first and last cell that hits z
+ pSource.setPosition( minCell[ 0 ] * cellDimensions[ 0 ], 0 );
+ sourceToScreen.apply( pSource, pScreen );
+ final double z0 = pScreen.getDoublePosition( 2 );
+ int nStart = 0;
+ int nStop = 0;
+ if ( xStep[ 2 ] > eps )
+ {
+ nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
+ if ( nStart > maxCell[ 0 ] )
+ continue;
+ nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
+ if ( nStop < minCell[ 0 ] )
+ continue;
+ }
+ else if ( xStep[ 2 ] < - eps )
+ {
+ nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
+ if ( nStart > maxCell[ 0 ] )
+ continue;
+ nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
+ if ( nStop < minCell[ 0 ] )
+ continue;
+ }
+ else
+ {
+ if ( z0 + offsetNeg[ 2 ] > 0 || z0 + offsetPos[ 2 ] < 0 )
+ continue;
+ nStart = minCell[ 0 ];
+ nStop = maxCell[ 0 ];
+ }
+
+ pSource.setPosition( nStart * cellDimensions[ 0 ], 0 );
+ for ( cellsRandomAccess.setPosition( nStart, 0 ); cellsRandomAccess.getIntPosition( 0 ) <= nStop; cellsRandomAccess.fwd( 0 ) )
+ {
+ sourceToScreen.apply( pSource, pScreen );
+ final double x = pScreen.getDoublePosition( 0 );
+ final double y = pScreen.getDoublePosition( 1 );
+ if ( ( x + offsetPos[ 0 ] >= 0 ) &&
+ ( x + offsetNeg[ 0 ] < w ) &&
+ ( y + offsetPos[ 1 ] >= 0 ) &&
+ ( y + offsetNeg[ 1 ] < h ) )
+ {
+ cellsRandomAccess.get();
+ }
+ pSource.move( cellDimensions[ 0 ], 0 );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the transformed vector in screen coordinate when moving by
+ * cellStep[0] in X direction.
+ */
+ private void getXStep( final int[] cellStep, final AffineTransform3D sourceToScreen )
+ {
+ final RealPoint p0 = new RealPoint( 3 );
+ final RealPoint p1 = new RealPoint( 3 );
+ p1.setPosition( cellStep[ 0 ], 0 );
+ final RealPoint s0 = new RealPoint( 3 );
+ final RealPoint s1 = new RealPoint( 3 );
+ sourceToScreen.apply( p0, s0 );
+ sourceToScreen.apply( p1, s1 );
+ for ( int d = 0; d < 3; ++d )
+ xStep[ d ] = s1.getDoublePosition( d ) - s0.getDoublePosition( d );
+ }
+
+ /**
+ * Get the bounding box of a cell under the current transform and
+ * interpolation method. Set {@link #offsetNeg} and {@link #offsetPos} as
+ * seen from the min corner of the cell.
+ */
+ private void checkProtoCell( final int[] cellDims, final AffineTransform3D sourceToScreen, final Interpolation interpolation )
+ {
+ final RealPoint pSource = new RealPoint( 3 );
+ final RealPoint pScreenAnchor = new RealPoint( 3 );
+ sourceToScreen.apply( pSource, pScreenAnchor );
+
+ final RealPoint[] pScreen = new RealPoint[ 8 ];
+ final double[] cellMin = new double[ 3 ];
+ final double[] cellSize = new double[] { cellDims[ 0 ], cellDims[ 1 ], cellDims[ 2 ] };
+ if ( interpolation == Interpolation.NEARESTNEIGHBOR )
+ {
+ for ( int d = 0; d < 3; ++d )
+ {
+ cellMin[ d ] -= 0.5;
+ cellSize[ d ] -= 0.5;
+ }
+ }
+ else // Interpolation.NLINEAR
+ {
+ for ( int d = 0; d < 3; ++d )
+ cellMin[ d ] -= 1;
+ }
+ int i = 0;
+ for ( int z = 0; z < 2; ++z )
+ {
+ pSource.setPosition( ( z == 0 ) ? cellMin[ 2 ] : cellSize[ 2 ], 2 );
+ for ( int y = 0; y < 2; ++y )
+ {
+ pSource.setPosition( ( y == 0 ) ? cellMin[ 1 ] : cellSize[ 1 ], 1 );
+ for ( int x = 0; x < 2; ++x )
+ {
+ pSource.setPosition( ( x == 0 ) ? cellMin[ 0 ] : cellSize[ 0 ], 0 );
+ pScreen[ i ] = new RealPoint( 3 );
+ sourceToScreen.apply( pSource, pScreen[ i++ ] );
+ }
+ }
+ }
+
+ for ( int d = 0; d < 3; ++d )
+ {
+ double min = pScreen[ 0 ].getDoublePosition( d );
+ double max = pScreen[ 0 ].getDoublePosition( d );
+ for ( i = 1; i < 8; ++i )
+ {
+ final double p = pScreen[ i ].getDoublePosition( d );
+ if ( p < min )
+ min = p;
+ if ( p > max )
+ max = p;
+ }
+ offsetNeg[ d ] = min - pScreenAnchor.getDoublePosition( d );
+ offsetPos[ d ] = max - pScreenAnchor.getDoublePosition( d );
+ }
+ }
+}