From 454c0384f21127b933140568bac82441b37acbfe Mon Sep 17 00:00:00 2001
From: tpietzsch <tobias.pietzsch@gmail.com>
Date: Tue, 7 Jul 2020 12:43:32 +0200
Subject: [PATCH] Refactoring and javadoc.

---
 .../render/MultiResolutionRenderer.java       | 261 +++++++++++-------
 1 file changed, 156 insertions(+), 105 deletions(-)

diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
index 9f8b3d87..46cf5ed3 100644
--- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
+++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
@@ -116,7 +116,8 @@ public class MultiResolutionRenderer
 	private final long[] iobudget = new long[] { 100l * 1000000l,  10l * 1000000l };
 
 	/**
-	 * TODO javadoc
+	 * Maintains current sizes and transforms at every screen scale level.
+	 * Records interval rendering requests.
 	 */
 	private final ScreenScales screenScales;
 
@@ -133,22 +134,11 @@ public class MultiResolutionRenderer
 
 	/**
 	 * Currently active projector, used to re-paint the display. It maps the
-	 * source data to {@code ©screenImages}.
+	 * source data to {@code ©screenImages}. {@code projector.cancel()} can be
+	 * used to cancel the ongoing rendering operation.
 	 */
 	private VolatileProjector projector;
 
-	/**
-	 * Screen scale of the last successful (not cancelled) rendering pass in
-	 * full-frame mode.
-	 */
-	private int currentScreenScaleIndex;
-
-	/**
-	 * The index of the screen scale which should be rendered next in full-frame
-	 * mode.
-	 */
-	private int requestedScreenScaleIndex;
-
 	/**
 	 * Whether the current rendering operation may be cancelled (to start a new
 	 * one). Rendering may be cancelled unless we are rendering at the
@@ -158,6 +148,7 @@ public class MultiResolutionRenderer
 
 	/**
 	 * Snapshot of the ViewerState that is currently being rendered.
+	 * A new snapshot is taken in the first {@code paint()} pass after a (full frame) {@link #requestRepaint()}
 	 */
 	private ViewerState currentViewerState;
 
@@ -167,10 +158,10 @@ public class MultiResolutionRenderer
 	private int currentNumVisibleSources;
 
 	/**
-	 * Whether a full repaint was {@link #requestRepaint() requested}. This will
-	 * cause {@link CacheControl#prepareNextFrame()}.
+	 * The last successfully rendered (not cancelled) full frame result.
+	 * This result is by full frame refinement passes and/or interval rendering passes.
 	 */
-	private boolean newFrameRequest;
+	private RenderResult currentRenderResult;
 
 	/**
 	 * If {@code true}, then we are painting intervals currently.
@@ -178,26 +169,70 @@ public class MultiResolutionRenderer
 	 */
 	private boolean intervalMode;
 
+	/*
+	 *
+	 * === FULL FRAME RENDERING ===
+	 *
+	 */
+
+	/**
+	 * Screen scale of the last successful (not cancelled) rendering pass in
+	 * full-frame mode.
+	 */
+	private int currentScreenScaleIndex;
+
+	/**
+	 * The index of the screen scale which should be rendered next in full-frame
+	 * mode.
+	 */
+	private int requestedScreenScaleIndex;
+
+	/**
+	 * Whether a full frame repaint was {@link #requestRepaint() requested}.
+	 * Supersedes {@link #newIntervalRequest}.
+	 */
+	private boolean newFrameRequest;
+
+	/*
+	 *
+	 * === INTERVAL RENDERING ===
+	 *
+	 */
+
 	/**
 	 * Screen scale of the last successful (not cancelled) rendering pass in
 	 * interval mode.
 	 */
 	private int currentIntervalScaleIndex;
 
+	/**
+	 * The index of the screen scale which should be rendered next in interval
+	 * mode.
+	 */
 	private int requestedIntervalScaleIndex;
 
 	/**
 	 * Whether repainting of an interval was {@link #requestRepaint(Interval)
-	 * requested}. This will cause {@link CacheControl#prepareNextFrame()}.
-	 * Pending interval requests are obsoleted by full repaint requests.
+	 * requested}. The union of all pending intervals are recorded in
+	 * {@link #screenScales}. Pending interval requests are obsoleted by full
+	 * frame repaints requests ({@link #newFrameRequest}).
 	 */
 	private boolean newIntervalRequest;
 
-	// =========== intervals ==========================================================================================
-	private RenderResult currentRenderResult;
+	/**
+	 * Re-used for all interval rendering.
+	 */
 	private final RenderResult intervalResult;
+
+	/**
+	 * Currently rendering interval. This is pulled from {@link #screenScales}
+	 * at the start of {@code paint()}, which clears requested intervals at
+	 * {@link #currentIntervalScaleIndex} or coarser. (This ensures that new
+	 * interval requests arriving during rendering are not missed. If the
+	 * requested intervals would be cleared after rendering, this might happen.
+	 * Instead we re-request the pulled intervals, if rendering fails.)
+	 */
 	private IntervalRenderData intervalRenderData;
-	// =========== intervals ==========================================================================================
 
 	/**
 	 * @param display
@@ -315,6 +350,7 @@ public class MultiResolutionRenderer
 	public void kill()
 	{
 		projector = null;
+		currentRenderResult = null;
 		renderStorage.clear();
 	}
 
@@ -331,7 +367,6 @@ public class MultiResolutionRenderer
 
 		final boolean newFrame;
 		final boolean newInterval;
-		final boolean paintInterval;
 		final boolean prepareNextFrame;
 		final boolean createProjector;
 		synchronized ( this )
@@ -354,19 +389,16 @@ public class MultiResolutionRenderer
 			}
 
 			prepareNextFrame = newFrame || newInterval;
-			paintInterval = intervalMode;
+			renderingMayBeCancelled = !prepareNextFrame;
 
-			if ( paintInterval )
+			if ( intervalMode )
 			{
 				createProjector = newInterval || ( requestedIntervalScaleIndex != currentIntervalScaleIndex );
 				if ( createProjector )
 					intervalRenderData = screenScales.pullIntervalRenderData( requestedIntervalScaleIndex, currentScreenScaleIndex );
 			}
 			else
-			{
 				createProjector = newFrame || ( requestedScreenScaleIndex != currentScreenScaleIndex );
-			}
-
 
 			newFrameRequest = false;
 			newIntervalRequest = false;
@@ -383,6 +415,13 @@ public class MultiResolutionRenderer
 			requestedScreenScaleIndex = screenScales.suggestScreenScale( renderNanosPerPixel );
 		}
 
+		return intervalMode
+				? paintInterval( createProjector )
+				: paintFullFrame( createProjector );
+	}
+
+	private boolean paintFullFrame( final boolean createProjector )
+	{
 		// the projector that paints to the screenImage.
 		final VolatileProjector p;
 
@@ -396,109 +435,121 @@ public class MultiResolutionRenderer
 		{
 			if ( createProjector )
 			{
-				if ( paintInterval )
+				final ScreenScale screenScale = screenScales.get( requestedScreenScaleIndex );
+
+				renderResult = display.getReusableRenderResult();
+				renderResult.init( screenScale.width(), screenScale.height() );
+				renderResult.setScaleFactor( screenScale.scale() );
+				currentViewerState.getViewerTransform( renderResult.getViewerTransform() );
+
+				renderStorage.checkRenewData( screenScales.get( 0 ).width(), screenScales.get( 0 ).height(), currentNumVisibleSources );
+				projector = createProjector( currentViewerState, requestedScreenScaleIndex, renderResult.getScreenImage(), 0, 0 );
+				requestNewFrameIfIncomplete = projectorFactory.requestNewFrameIfIncomplete();
+			}
+			p = projector;
+		}
+
+		// try rendering
+		final boolean success = p.map( createProjector );
+		final long rendertime = p.getLastFrameRenderNanoTime();
+
+		synchronized ( this )
+		{
+			// if rendering was not cancelled...
+			if ( success )
+			{
+				currentScreenScaleIndex = requestedScreenScaleIndex;
+				if ( createProjector )
 				{
-					intervalResult.init( intervalRenderData.width(), intervalRenderData.height() );
-					intervalResult.setScaleFactor( intervalRenderData.scale() );
-					projector = createProjector( currentViewerState, requestedIntervalScaleIndex, intervalResult.getScreenImage(), intervalRenderData.offsetX(), intervalRenderData.offsetY() );
-					renderingMayBeCancelled = !newInterval;
+					renderResult.setUpdated();
+					( ( RenderTarget ) display ).setRenderResult( renderResult );
+					currentRenderResult = renderResult;
+					recordRenderTime( renderResult, rendertime );
 				}
 				else
-				{
-					final ScreenScale screenScale = screenScales.get( requestedScreenScaleIndex );
+					currentRenderResult.setUpdated();
+
+				if ( !p.isValid() && requestNewFrameIfIncomplete )
+					requestRepaint();
+				else if ( p.isValid() && currentScreenScaleIndex == 0 )
+					// indicate that rendering is complete
+					requestedScreenScaleIndex = -1;
+				else
+					iterateRepaint( Math.max( 0, currentScreenScaleIndex - 1 ) );
+			}
+		}
 
-					renderResult = display.getReusableRenderResult();
-					renderResult.init( screenScale.width(), screenScale.height() );
-					renderResult.setScaleFactor( screenScale.scale() );
-					currentViewerState.getViewerTransform( renderResult.getViewerTransform() );
+		return success;
+	}
 
-					renderStorage.checkRenewData( screenScales.get( 0 ).width(), screenScales.get( 0 ).height(), currentNumVisibleSources );
-					projector = createProjector( currentViewerState, requestedScreenScaleIndex, renderResult.getScreenImage(), 0, 0 );
-					requestNewFrameIfIncomplete = projectorFactory.requestNewFrameIfIncomplete();
-					renderingMayBeCancelled = !newFrame;
-				}
+	private boolean paintInterval( final boolean createProjector )
+	{
+		// the projector that paints to the screenImage.
+		final VolatileProjector p;
+
+		synchronized ( this )
+		{
+			if ( createProjector )
+			{
+				intervalResult.init( intervalRenderData.width(), intervalRenderData.height() );
+				intervalResult.setScaleFactor( intervalRenderData.scale() );
+				projector = createProjector( currentViewerState, requestedIntervalScaleIndex, intervalResult.getScreenImage(), intervalRenderData.offsetX(), intervalRenderData.offsetY() );
 			}
 			p = projector;
 		}
 
-
 		// try rendering
 		final boolean success = p.map( createProjector );
 		final long rendertime = p.getLastFrameRenderNanoTime();
 
-
 		synchronized ( this )
 		{
 			// if rendering was not cancelled...
 			if ( success )
 			{
-				if ( paintInterval )
-				{
-					currentIntervalScaleIndex = requestedIntervalScaleIndex;
-					currentRenderResult.patch( intervalResult, intervalRenderData.targetInterval(), intervalRenderData.tx(), intervalRenderData.ty() );
+				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 full frame rendering
-							intervalMode = false;
-							if ( requestedScreenScaleIndex == currentScreenScaleIndex )
-								++currentScreenScaleIndex;
-							painterThread.requestRepaint();
-						}
-					}
-					else
-						iterateRepaintInterval( currentIntervalScaleIndex );
-				}
-				else
+				if ( createProjector )
+					recordRenderTime( intervalResult, rendertime );
+
+				if ( currentIntervalScaleIndex > currentScreenScaleIndex )
+					iterateRepaintInterval( currentIntervalScaleIndex - 1 );
+				else if ( p.isValid() )
 				{
-					currentScreenScaleIndex = requestedScreenScaleIndex;
-					if ( createProjector )
+					// if full frame rendering was not yet complete
+					if ( requestedScreenScaleIndex >= 0 )
 					{
-						renderResult.setUpdated();
-						( ( RenderTarget ) display ).setRenderResult( renderResult );
-						currentRenderResult = renderResult;
-
-						if ( currentNumVisibleSources > 0 )
-						{
-							final int numRenderPixels = ( int ) Intervals.numElements( renderResult.getScreenImage() ) * currentNumVisibleSources;
-							renderNanosPerPixelAndSource.add( rendertime / ( double ) numRenderPixels );
-						}
+						// go back to full frame rendering
+						intervalMode = false;
+						if ( requestedScreenScaleIndex == currentScreenScaleIndex )
+							++currentScreenScaleIndex;
+						painterThread.requestRepaint();
 					}
-					else
-						currentRenderResult.setUpdated();
-
-					if ( !p.isValid() && requestNewFrameIfIncomplete )
-						requestRepaint();
-					else if ( p.isValid() && currentScreenScaleIndex == 0 )
-						// indicate that rendering is complete
-						requestedScreenScaleIndex = -1;
-					else
-						iterateRepaint( Math.max( 0, currentScreenScaleIndex - 1 ) );
 				}
+				else
+					iterateRepaintInterval( currentIntervalScaleIndex );
 			}
 			// if rendering was cancelled...
 			else
-			{
-				if ( paintInterval )
-					intervalRenderData.reRequest();
-			}
+				intervalRenderData.reRequest();
 		}
 
 		return success;
 	}
 
+	private void recordRenderTime( final RenderResult result, final long renderNanos )
+	{
+		final int numRenderPixels = ( int ) Intervals.numElements( result.getScreenImage() ) * currentNumVisibleSources;
+		if ( numRenderPixels >= 4096 )
+			renderNanosPerPixelAndSource.add( renderNanos / ( double ) numRenderPixels );
+	}
+
 	/**
-	 * Request a repaint of the display from the painter thread, setting
-	 * {@code requestedScreenScaleIndex} to the specified
-	 * {@code screenScaleIndex}. This is used to repaint the
-	 * {@code currentViewerState} in a loop, until everything is painted at
-	 * highest resolution from valid data (or painting is interrupted by a new
-	 * "real" {@link #requestRepaint()}.
+	 * Request iterated repaint at the specified {@code screenScaleIndex}. This
+	 * is used to repaint the {@code currentViewerState} in a loop, until
+	 * everything is painted at highest resolution from valid data (or until
+	 * painting is interrupted by a new request}.
 	 */
 	private void iterateRepaint( final int screenScaleIndex )
 	{
@@ -508,6 +559,12 @@ public class MultiResolutionRenderer
 		painterThread.requestRepaint();
 	}
 
+	/**
+	 * Request iterated repaint at the specified {@code intervalScaleIndex}.
+	 * This is used to repaint the current interval in a loop, until everything
+	 * is painted at highest resolution from valid data (or until painting is
+	 * interrupted by a new request}.
+	 */
 	private void iterateRepaintInterval( final int intervalScaleIndex )
 	{
 		if ( intervalScaleIndex == currentIntervalScaleIndex )
@@ -542,12 +599,6 @@ public class MultiResolutionRenderer
 			final int offsetX,
 			final int offsetY )
 	{
-		/*
-		 * This shouldn't be necessary, with
-		 * CacheHints.LoadingStrategy==VOLATILE
-		 */
-//		CacheIoTiming.getIoTimeBudget().clear(); // clear time budget such that prefetching doesn't wait for loading blocks.
-
 		final AffineTransform3D screenScaleTransform = screenScales.get( screenScaleIndex ).scaleTransform();
 		final AffineTransform3D screenTransform = viewerState.getViewerTransform();
 		screenTransform.preConcatenate( screenScaleTransform );
-- 
GitLab