diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java index e5f5e45854b5d214503e84d68844fddc2d3b929a..9f8b3d87948e977c13dad1d5a1124dfc27f10f32 100644 --- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java +++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java @@ -28,12 +28,8 @@ */ package bdv.viewer.render; -import bdv.cache.CacheControl; -import bdv.util.MovingAverage; -import bdv.viewer.ViewerState; -import bdv.viewer.render.ScreenScales.IntervalRenderData; -import bdv.viewer.render.ScreenScales.ScreenScale; import java.util.concurrent.ExecutorService; + import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.Volatile; @@ -44,6 +40,12 @@ import net.imglib2.ui.PainterThread; import net.imglib2.ui.RenderTarget; import net.imglib2.util.Intervals; +import bdv.cache.CacheControl; +import bdv.util.MovingAverage; +import bdv.viewer.ViewerState; +import bdv.viewer.render.ScreenScales.IntervalRenderData; +import bdv.viewer.render.ScreenScales.ScreenScale; + /** * A renderer that uses a coarse-to-fine rendering scheme. First, a small target * image at a fraction of the canvas resolution is rendered. Then, increasingly @@ -191,6 +193,12 @@ public class MultiResolutionRenderer */ private boolean newIntervalRequest; + // =========== intervals ========================================================================================== + private RenderResult currentRenderResult; + private final RenderResult intervalResult; + private IntervalRenderData intervalRenderData; + // =========== intervals ========================================================================================== + /** * @param display * The canvas that will display the images we render. @@ -256,6 +264,59 @@ public class MultiResolutionRenderer accumulateProjectorFactory ); } + /** + * Request a repaint of the display from the painter thread. The painter + * thread will trigger a {@link #paint} as soon as possible (that is, + * immediately or after the currently running {@link #paint} has completed). + */ + public synchronized void requestRepaint() + { + if ( renderingMayBeCancelled && projector != null ) + projector.cancel(); + newFrameRequest = true; + painterThread.requestRepaint(); + } + + /** + * Request a repaint of the given {@code interval} of the display from the + * painter thread. The painter thread will trigger a {@link #paint} as soon + * as possible (that is, immediately or after the currently running + * {@link #paint} has completed). + */ + public synchronized void requestRepaint( final Interval interval ) + { + if ( !intervalMode && !renderingMayBeCancelled ) + { + /* + * We are currently rendering a full frame at the coarsest + * resolution. There is no point in painting an interval now. Just + * request a new full frame. + */ + newFrameRequest = true; + } + else + { + if ( renderingMayBeCancelled && projector != null ) + projector.cancel(); + screenScales.requestInterval( interval ); + newIntervalRequest = true; + } + painterThread.requestRepaint(); + } + + /** + * DON'T USE THIS. + * <p> + * This is a work around for JDK bug + * https://bugs.openjdk.java.net/browse/JDK-8029147 which leads to + * ViewerPanel not being garbage-collected when ViewerFrame is closed. So + * instead we need to manually let go of resources... + */ + public void kill() + { + projector = null; + renderStorage.clear(); + } /** * Render image at the {@link #requestedScreenScaleIndex requested screen @@ -268,15 +329,14 @@ public class MultiResolutionRenderer if ( screenW <= 0 || screenH <= 0 ) return false; - final boolean resized; final boolean newFrame; final boolean newInterval; final boolean paintInterval; final boolean prepareNextFrame; - boolean createProjector = false; + final boolean createProjector; synchronized ( this ) { - resized = screenScales.checkResize( screenW, screenH ); + final boolean resized = screenScales.checkResize( screenW, screenH ); newFrame = newFrameRequest || resized; if ( newFrame ) @@ -302,6 +362,11 @@ public class MultiResolutionRenderer if ( createProjector ) intervalRenderData = screenScales.pullIntervalRenderData( requestedIntervalScaleIndex, currentScreenScaleIndex ); } + else + { + createProjector = newFrame || ( requestedScreenScaleIndex != currentScreenScaleIndex ); + } + newFrameRequest = false; newIntervalRequest = false; @@ -327,26 +392,18 @@ public class MultiResolutionRenderer // whether to request a newFrame, in case that a new projector is created in full frame mode boolean requestNewFrameIfIncomplete = false; - if ( paintInterval ) + synchronized ( this ) { - intervalResult.init( intervalRenderData.width(), intervalRenderData.height() ); - intervalResult.setScaleFactor( intervalRenderData.scale() ); - synchronized ( this ) + if ( createProjector ) { - if ( createProjector ) + if ( paintInterval ) { + intervalResult.init( intervalRenderData.width(), intervalRenderData.height() ); + intervalResult.setScaleFactor( intervalRenderData.scale() ); projector = createProjector( currentViewerState, requestedIntervalScaleIndex, intervalResult.getScreenImage(), intervalRenderData.offsetX(), intervalRenderData.offsetY() ); renderingMayBeCancelled = !newInterval; } - p = projector; - } - } - else - { - createProjector = newFrame || ( requestedScreenScaleIndex != currentScreenScaleIndex ); - synchronized ( this ) - { - if ( createProjector ) + else { final ScreenScale screenScale = screenScales.get( requestedScreenScaleIndex ); @@ -360,8 +417,8 @@ public class MultiResolutionRenderer requestNewFrameIfIncomplete = projectorFactory.requestNewFrameIfIncomplete(); renderingMayBeCancelled = !newFrame; } - p = projector; } + p = projector; } @@ -377,37 +434,31 @@ public class MultiResolutionRenderer { if ( paintInterval ) { - if ( createProjector ) - currentIntervalScaleIndex = requestedIntervalScaleIndex; - + currentIntervalScaleIndex = requestedIntervalScaleIndex; currentRenderResult.patch( intervalResult, intervalRenderData.targetInterval(), intervalRenderData.tx(), intervalRenderData.ty() ); if ( currentIntervalScaleIndex > currentScreenScaleIndex ) iterateRepaintInterval( currentIntervalScaleIndex - 1 ); else if ( p.isValid() ) { + // if full frame rendering was not yet complete if ( requestedScreenScaleIndex >= 0 ) { - // go back to "normal" rendering + // go back to full frame rendering intervalMode = false; - renderingMayBeCancelled = false; if ( requestedScreenScaleIndex == currentScreenScaleIndex ) ++currentScreenScaleIndex; painterThread.requestRepaint(); } } else - { - usleep(); - intervalRenderData.reRequest(); iterateRepaintInterval( currentIntervalScaleIndex ); - } } else { + currentScreenScaleIndex = requestedScreenScaleIndex; if ( createProjector ) { - currentScreenScaleIndex = requestedScreenScaleIndex; renderResult.setUpdated(); ( ( RenderTarget ) display ).setRenderResult( renderResult ); currentRenderResult = renderResult; @@ -423,18 +474,11 @@ public class MultiResolutionRenderer if ( !p.isValid() && requestNewFrameIfIncomplete ) requestRepaint(); - else if ( currentScreenScaleIndex > 0 ) - iterateRepaint( currentScreenScaleIndex - 1 ); - else if ( p.isValid() ) - { + else if ( p.isValid() && currentScreenScaleIndex == 0 ) // indicate that rendering is complete requestedScreenScaleIndex = -1; - } else - { - usleep(); - iterateRepaint( currentScreenScaleIndex ); - } + iterateRepaint( Math.max( 0, currentScreenScaleIndex - 1 ) ); } } // if rendering was cancelled... @@ -458,49 +502,37 @@ public class MultiResolutionRenderer */ private void iterateRepaint( final int screenScaleIndex ) { + if ( screenScaleIndex == currentScreenScaleIndex ) + usleep(); requestedScreenScaleIndex = screenScaleIndex; painterThread.requestRepaint(); } - /** - * Request a repaint of the display from the painter thread. The painter - * thread will trigger a {@link #paint} as soon as possible (that is, - * immediately or after the currently running {@link #paint} has completed). - */ - public synchronized void requestRepaint() - { - if ( renderingMayBeCancelled && projector != null ) - projector.cancel(); - newFrameRequest = true; - painterThread.requestRepaint(); - } - - public synchronized void requestRepaint( final Interval screenInterval ) + private void iterateRepaintInterval( final int intervalScaleIndex ) { - if ( renderingMayBeCancelled || intervalMode ) + if ( intervalScaleIndex == currentIntervalScaleIndex ) { - if ( projector != null ) - projector.cancel(); - screenScales.requestInterval( screenInterval ); - newIntervalRequest = true; + intervalRenderData.reRequest(); + usleep(); } - else - newFrameRequest = true; + requestedIntervalScaleIndex = intervalScaleIndex; painterThread.requestRepaint(); } /** - * DON'T USE THIS. - * <p> - * This is a work around for JDK bug - * https://bugs.openjdk.java.net/browse/JDK-8029147 which leads to - * ViewerPanel not being garbage-collected when ViewerFrame is closed. So - * instead we need to manually let go of resources... + * Wait for 1ms so that fetcher threads get a chance to do work. */ - public void kill() + private void usleep() { - projector = null; - renderStorage.clear(); + try + { + Thread.sleep( 1 ); + } + catch ( final InterruptedException e ) + { + // restore interrupted state + Thread.currentThread().interrupt(); + } } private VolatileProjector createProjector( @@ -529,30 +561,4 @@ public class MultiResolutionRenderer CacheIoTiming.getIoTimeBudget().reset( iobudget ); return projector; } - - // =========== intervals ========================================================================================== - - private RenderResult currentRenderResult; - - private final RenderResult intervalResult; - private IntervalRenderData intervalRenderData; - - private void iterateRepaintInterval( final int intervalScaleIndex ) - { - requestedIntervalScaleIndex = intervalScaleIndex; - painterThread.requestRepaint(); - } - - private void usleep() - { - try - { - Thread.sleep( 1 ); - } - catch ( final InterruptedException e ) - { - // restore interrupted state - Thread.currentThread().interrupt(); - } - } }