From b6b39d6f895e102bb7d12ee2d2d4180024dcd8af Mon Sep 17 00:00:00 2001 From: Tobias Pietzsch <tobias.pietzsch@gmail.com> Date: Wed, 26 Feb 2014 01:37:56 +0100 Subject: [PATCH] finished prefetching strategy: * clean up Prefetcher (previously PlayWithGeometry) * wait/notify instead of letting Fetcher threads sleep * remove debug prints * clean up IoTimeBudget * keep references to entries accessed in current frame such that they cannot be cleared from the cache prematurely. --- src/main/java/bdv/img/cache/Cache.java | 2 +- .../java/bdv/img/cache/CacheIoTiming.java | 29 +- .../img/cache/VolatileGlobalCellCache.java | 91 ++++--- .../java/bdv/img/hdf5/Hdf5ImageLoader.java | 11 +- .../hdf5/Hdf5VolatileShortArrayLoader.java | 98 ++++--- .../java/bdv/tools/RecordMovieDialog.java | 2 +- .../render/MultiResolutionRenderer.java | 60 ++--- .../bdv/viewer/render/PlayWithGeometry.java | 196 -------------- .../java/bdv/viewer/render/Prefetcher.java | 250 ++++++++++++++++++ 9 files changed, 398 insertions(+), 341 deletions(-) delete mode 100644 src/main/java/bdv/viewer/render/PlayWithGeometry.java create mode 100644 src/main/java/bdv/viewer/render/Prefetcher.java diff --git a/src/main/java/bdv/img/cache/Cache.java b/src/main/java/bdv/img/cache/Cache.java index e2edb3e9..488e0ab9 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 d8ff08b3..b2ff0cf8 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 5745b990..e94e6b35 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 c61450cb..35b338a4 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 07eee2dc..69da6ed2 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 cb5518a9..f5cc64a3 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 eb95b33c..3fa0227d 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 05a76ab8..00000000 --- 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 00000000..527f4561 --- /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 ); + } + } +} -- GitLab