sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Provide a RenderingMode.DIRECT which avoid creation of a double buffer as the cost of executing `paint(Graphics2D)` in the JavaFX thread. It is okay if that method does only a `Graphics2D.drawRenderedImage(…)` call without scale or rotation, in which case we are not blocking the JavaFX thread longer than in the RenderingMode.DOUBLE_BUFFERED case.
Date Thu, 06 Aug 2020 14:33:28 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 0e402ff  Provide a RenderingMode.DIRECT which avoid creation of a double buffer as
the cost of executing `paint(Graphics2D)` in the JavaFX thread. It is okay if that method
does only a `Graphics2D.drawRenderedImage(…)` call without scale or rotation, in which case
we are not blocking the JavaFX thread longer than in the RenderingMode.DOUBLE_BUFFERED case.
0e402ff is described below

commit 0e402ff336c6224e55351b4681fc7644fc4ee272
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Aug 6 16:12:15 2020 +0200

    Provide a RenderingMode.DIRECT which avoid creation of a double buffer as the cost of
executing `paint(Graphics2D)` in the JavaFX thread.
    It is okay if that method does only a `Graphics2D.drawRenderedImage(…)` call without
scale or rotation, in which case we are not blocking
    the JavaFX thread longer than in the RenderingMode.DOUBLE_BUFFERED case.
---
 .../apache/sis/gui/coverage/CoverageCanvas.java    |   8 +-
 .../java/org/apache/sis/gui/map/MapCanvas.java     |  10 +-
 .../java/org/apache/sis/gui/map/MapCanvasAWT.java  | 216 +++++++++++++++++----
 .../java/org/apache/sis/gui/map/RenderingMode.java |  51 +++++
 4 files changed, 242 insertions(+), 43 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 1cd0ad1..cfafb5d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Locale;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
+import java.awt.RenderingHints;
 import java.awt.image.RenderedImage;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
@@ -55,6 +56,7 @@ import org.apache.sis.image.PlanarImage;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.gui.map.MapCanvas;
 import org.apache.sis.gui.map.MapCanvasAWT;
+import org.apache.sis.gui.map.RenderingMode;
 import org.apache.sis.gui.map.StatusBar;
 import org.apache.sis.portrayal.RenderException;
 import org.apache.sis.internal.gui.GUIUtilities;
@@ -193,6 +195,7 @@ public class CoverageCanvas extends MapCanvasAWT {
         coverageProperty     .addListener((p,o,n) -> onImageSpecified());
         sliceExtentProperty  .addListener((p,o,n) -> onImageSpecified());
         interpolationProperty.addListener((p,o,n) -> onInterpolationSpecified(n));
+        super.setRenderingMode(RenderingMode.DIRECT);
     }
 
     /**
@@ -600,10 +603,13 @@ public class CoverageCanvas extends MapCanvasAWT {
         }
 
         /**
-         * Draws the image in a background buffer after {@link #render()} finished to prepare
data.
+         * Draws the image after {@link #render()} finished to prepare data.
+         * This method may be invoked in a background thread or in JavaFX thread,
+         * depending on {@link RenderingMode}.
          */
         @Override
         protected void paint(final Graphics2D gr) {
+            gr.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
             gr.drawRenderedImage(prefetchedImage, resampledToDisplay);
         }
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index a9c342e..a6f1e93 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -788,6 +788,9 @@ public abstract class MapCanvas extends PlanarCanvas {
         /**
          * Sets the width and height to the size of the given view,
          * then returns {@code true} if the view is non-empty.
+         *
+         * <p>This method is invoked after {@link #createRenderer()}
+         * and before {@link #createWorker(Renderer)}.</p>
          */
         final boolean initialize(final Pane view) {
             width  = Numerics.clamp(Math.round(view.getWidth()));
@@ -818,10 +821,9 @@ public abstract class MapCanvas extends PlanarCanvas {
          * {@link MapCanvas} property; if some canvas properties are needed, they should
have been
          * copied at construction time.
          *
-         * @throws TransformException if the rendering required coordinate transformation
and that
-         *         operation failed.
+         * @throws Exception if an error occurred while preparing data or rendering them.
          */
-        protected abstract void render() throws TransformException;
+        protected abstract void render() throws Exception;
 
         /**
          * Invoked in JavaFX thread after {@link #render()} completion. This method can update
the
@@ -989,7 +991,7 @@ public abstract class MapCanvas extends PlanarCanvas {
     Task<?> createWorker(final Renderer renderer) {
         return new Task<Void>() {
             /** Invoked in background thread. */
-            @Override protected Void call() throws TransformException {
+            @Override protected Void call() throws Exception {
                 renderer.render();
                 return null;
             }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
index 42749a8..4770d0d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
@@ -22,10 +22,12 @@ import java.awt.AlphaComposite;
 import java.awt.Graphics2D;
 import java.awt.GraphicsEnvironment;
 import java.awt.GraphicsConfiguration;
-import java.awt.image.BufferedImage;
+import java.awt.RenderingHints;
 import java.awt.image.DataBufferInt;
 import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
 import java.awt.image.VolatileImage;
+import javafx.application.Platform;
 import javafx.geometry.Rectangle2D;
 import javafx.scene.image.ImageView;
 import javafx.scene.image.PixelBuffer;
@@ -33,7 +35,7 @@ import javafx.scene.image.PixelFormat;
 import javafx.scene.image.WritableImage;
 import javafx.concurrent.Task;
 import javafx.util.Callback;
-import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 
 
@@ -60,6 +62,17 @@ public abstract class MapCanvasAWT extends MapCanvas {
     private static final boolean NATIVE_ACCELERATION = false;
 
     /**
+     * Whether {@link Renderer} will write directly into the {@link #buffer} in JavaFX thread,
+     * instead of writing into {@link #doubleBuffer} in a background thread and copy the
result
+     * in {@link #buffer} later. This flag should be set to {@code true} only when the painting
+     * is known to be fast.
+     *
+     * @see #getRenderingMode()
+     * @see #setRenderingMode(RenderingMode)
+     */
+    private boolean isDirect;
+
+    /**
      * A buffer where to draw the content of the map for the region to be displayed.
      * This buffer uses ARGB color model, contrarily to the {@link RenderedImage} of
      * {@link org.apache.sis.coverage.grid.GridCoverage} which may have any color model.
@@ -68,6 +81,11 @@ public abstract class MapCanvasAWT extends MapCanvas {
      *
      * <p>This buffered image contains the same data than the {@linkplain #image} of
this canvas.
      * Those two images will share the same data array (no copy) and the same coordinate
system.</p>
+     *
+     * <h4>Restriction</h4>
+     * Type is restricted to {@link BufferedImage#TYPE_INT_ARGB_PRE} or {@link BufferedImage#TYPE_4BYTE_ABGR_PRE}
+     * because JavaFX {@link PixelBuffer} (stored in {@link #bufferWrapper}) accepts only
those types.
+     * We arbitrarily choose {@code TYPE_INT_ARGB_PRE}.
      */
     private BufferedImage buffer;
 
@@ -75,16 +93,17 @@ public abstract class MapCanvasAWT extends MapCanvas {
      * A temporary buffer where to draw the {@link RenderedImage} in a background thread.
      * We use this double-buffering when the {@link #buffer} is already wrapped by JavaFX.
      * After creating the image in background, its content is copied to {@link #buffer} in
-     * JavaFX thread.
+     * JavaFX thread. This is used only with {@link RenderingMode#DOUBLE_BUFFERED}.
      */
     private VolatileImage doubleBuffer;
 
     /**
      * The graphic configuration at the time {@link #buffer} has been rendered.
-     * This will be used for creating compatible {@link VolatileImage} for updating.
+     * Used for creating compatible {@link #doubleBuffer} before updating image content.
      * This configuration determines whether native acceleration will be enabled or not.
      *
      * @see #NATIVE_ACCELERATION
+     * @see RenderingMode#DOUBLE_BUFFERED
      */
     private GraphicsConfiguration bufferConfiguration;
 
@@ -92,12 +111,17 @@ public abstract class MapCanvasAWT extends MapCanvas {
      * Wraps {@link #buffer} data array for use by JavaFX images. This is the mechanism used
      * by JavaFX 13+ for allowing {@link #image} to share the same data than {@link #buffer}.
      * The same wrapper can be used for many {@link WritableImage} instances (e.g. thumbnails).
+     *
+     * <h4>Invariants</h4>
+     * <ul>
+     *   <li>Shall be non-null if and only If {@link #buffer} is non-null.</li>
+     * </ul>
      */
     private PixelBuffer<IntBuffer> bufferWrapper;
 
     /**
      * The node where the rendered map will be shown. Its content is prepared in a background
thread
-     * by {@link Renderer#paint(Graphics2D)}. Subclasses should not set the image content
directly.
+     * by {@link Renderer}. Subclasses should not set the image content directly.
      */
     protected final ImageView image;
 
@@ -114,6 +138,45 @@ public abstract class MapCanvasAWT extends MapCanvas {
     }
 
     /**
+     * Clears {@link #buffer} and all support fields.
+     */
+    private void clearBuffer() {
+        buffer              = null;
+        doubleBuffer        = null;
+        bufferWrapper       = null;
+        bufferConfiguration = null;
+    }
+
+    /**
+     * Returns the current strategy (double buffering) for painting the map.
+     * The use of double buffering is related to the thread doing the painting.
+     * If double buffering is enabled (the default), then {@link Renderer#paint(Graphics2D)}
is invoked
+     * in a background thread and the result is copied into {@link #image} in JavaFX thread
when ready.
+     * If double buffering is disabled, then {@link Renderer#paint(Graphics2D)} is invoked
in JavaFX thread
+     * and modifies {@link #image} data in a more direct way.
+     *
+     * @return the current strategy (double buffering) for painting the map.
+     */
+    public RenderingMode getRenderingMode() {
+        return isDirect ? RenderingMode.DIRECT : RenderingMode.DOUBLE_BUFFERED;
+    }
+
+    /**
+     * Specifies whether to use double buffering when rendering the map.
+     * The default value is {@link RenderingMode#DOUBLE_BUFFERED}.
+     * That value should be changed to {@link RenderingMode#DIRECT} only if painting is known
to be fast
+     * because that mode causes {@link Renderer#paint(Graphics2D)} to be invoked in JavaFX
thread.
+     *
+     * @param  mode  the new rendering strategy.
+     */
+    public void setRenderingMode(final RenderingMode mode) {
+        ArgumentChecks.ensureNonNull("mode", mode);
+        if (isDirect != (isDirect = RenderingMode.DIRECT.equals(mode))) {
+            clearBuffer();
+        }
+    }
+
+    /**
      * Invoked in JavaFX thread for creating a renderer to be executed in a background thread.
      * Subclasses should copy in this method all {@code MapCanvas} properties that the background
thread
      * will need for performing the rendering process.
@@ -127,16 +190,16 @@ public abstract class MapCanvasAWT extends MapCanvas {
     /**
      * A snapshot of {@link MapCanvasAWT} state to paint as an image.
      * The snapshot is created in JavaFX thread by the {@link MapCanvasAWT#createRenderer()}
method,
-     * then the rendering process is executed in a background thread by {@link #paint(Graphics2D)}.
+     * then the rendering process is executed in a background thread.
      * Methods are invoked in the following order:
      *
      * <table class="sis">
      *   <caption>Methods invoked during a map rendering process</caption>
-     *   <tr><th>Method</th>                     <th>Thread</th>
           <th>Remarks</th></tr>
-     *   <tr><td>{@link #createRenderer()}</td>  <td>JavaFX thread</td>
    <td></td></tr>
-     *   <tr><td>{@link #render()}</td>          <td>Background thread</td>
<td></td></tr>
-     *   <tr><td>{@link #paint(Graphics2D)}</td> <td>Background thread</td>
<td>May be invoked many times.</td></tr>
-     *   <tr><td>{@link #commit(MapCanvas)}</td> <td>JavaFX thread</td>
    <td></td></tr>
+     *   <tr><th>Method</th>                     <th>Thread</th>
                     <th>Remarks</th></tr>
+     *   <tr><td>{@link #createRenderer()}</td>  <td>JavaFX thread</td>
              <td>Collects all needed information.</td></tr>
+     *   <tr><td>{@link #render()}</td>          <td>Background thread</td>
          <td>Computes what can be done in advance.</td></tr>
+     *   <tr><td>{@link #paint(Graphics2D)}</td> <td>{@link #getRenderingMode()}</td>
<td>May be invoked many times.</td></tr>
+     *   <tr><td>{@link #commit(MapCanvas)}</td> <td>JavaFX thread</td>
              <td>Saves data to cache for reuse.</td></tr>
      * </table>
      *
      * This class should not access any {@link MapCanvasAWT} property from a method invoked
in background thread
@@ -157,7 +220,10 @@ public abstract class MapCanvasAWT extends MapCanvas {
         }
 
         /**
-         * Returns whether the given buffer is non-null and have the expected size.
+         * Returns whether the given buffer is non-null and has the expected size.
+         * This verification shall be done only after {@link #initialize(Pane)} has been
invoked.
+         *
+         * @param  buffer  value of {@link #buffer}.
          */
         final boolean isValid(final BufferedImage buffer) {
             return (buffer != null)
@@ -172,18 +238,20 @@ public abstract class MapCanvasAWT extends MapCanvas {
          *
          * <p>The default implementation does nothing.</p>
          *
-         * @throws TransformException if the rendering required coordinate transformation
and that
-         *         operation failed.
+         * @throws Exception if an error occurred while preparing data.
          */
         @Override
-        protected void render() throws TransformException {
+        protected void render() throws Exception {
         }
 
         /**
-         * Invoked in a background thread for rendering the map. This method should not access
any
-         * {@link MapCanvas} property; if some canvas properties are needed, they should
have been
-         * copied at construction time. This method may be invoked many times if the rendering
is
-         * done in a {@link VolatileImage}.
+         * Invoked after {@link #render()} for doing the actual map painting.
+         * This method is invoked in the JavaFX thread if the rendering mode is {@link RenderingMode#DIRECT},
+         * or in a background thread otherwise. This method should not access any {@link
MapCanvas} property;
+         * if some canvas properties are needed, they should have been copied at construction
time.
+         *
+         * <p>This method may be invoked many times if the rendering is done in a {@link
VolatileImage}.
+         * It may happen only with {@link RenderingMode#DOUBLE_BUFFERED}.</p>
          *
          * @param  gr  the Java2D handler to use for rendering the map.
          */
@@ -213,34 +281,37 @@ public abstract class MapCanvasAWT extends MapCanvas {
      * It may be because the map has new content, or because the viewed region moved or
      * has been zoomed.
      *
-     * <p>There is two possible situations:</p>
+     * <p>There is three possible situations:</p>
      * <ul class="verbose">
      *   <li>If the current buffers are not suitable, then we clear everything related
to Java2D buffered images.
-     *     Those resources will recreated from scratch in background thread. There is no
need for double-buffering
+     *     Those resources will be recreated from scratch in background thread. There is
no need for double-buffering
      *     in such case because the new {@link BufferedImage} will not be shared with JavaFX
image before the end
      *     of this task.</li>
-     *   <li>If the current buffer are still valid, then we should not update {@link
BufferedImage} in background
+     *   <li>Otherwise (current buffer it still valid), we should not update {@link
BufferedImage} in a background
      *     thread because the internal array of that image is shared with JavaFX image. That
image can be updated
-     *     only in JavaFX thread through the {@code PixelBuffer.update(…)} method. In this
case we will use a
-     *     {@link VolatileImage} as a temporary buffer.</li>
+     *     only in JavaFX thread through the {@code PixelBuffer.update(…)} method. We do
that in two possible ways:
+     *     <ul>
+     *       <li>{@link RenderingMode#DIRECT}: paint directly in the JavaFX thread.</li>
+     *       <li>{@link RenderingMode#DOUBLE_BUFFERED}: use a {@link VolatileImage}
as a temporary buffer.</li>
+     *     </ul>
+     *   </li>
      * </ul>
      *
-     * In both cases we need to be careful to not use directly any {@link MapCanvas} field
from the {@code call()}
+     * In all cases we need to be careful to not use directly any {@link MapCanvas} field
from the {@code call()}
      * methods. Information needed by {@code call()} must be copied first.
      *
      * @see #requestRepaint()
      */
     @Override
     final Task<?> createWorker(final MapCanvas.Renderer mc) {
+        assert Platform.isFxApplicationThread();
         final Renderer context = (Renderer) mc;
         if (!context.isValid(buffer)) {
-            buffer              = null;
-            doubleBuffer        = null;
-            bufferWrapper       = null;
-            bufferConfiguration = null;
+            clearBuffer();
             return new Creator(context);
         } else {
-            return new Updater(context);
+            return isDirect ? new Direct (context)
+                            : new Updater(context);
         }
     }
 
@@ -252,7 +323,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
     private final class Creator extends Task<WritableImage> {
         /**
          * The user-provided object which will perform the actual rendering.
-         * Its {@link Renderer#paint(Graphics2D)} method will be invoked.
+         * Its {@link Renderer#paint(Graphics2D)} method will be invoked in background thread.
          */
         private final Renderer renderer;
 
@@ -275,10 +346,16 @@ public abstract class MapCanvasAWT extends MapCanvas {
         private GraphicsConfiguration configuration;
 
         /**
+         * Value of {@link MapCanvasAWT#isDirect} at creation time.
+         */
+        private final boolean isDirect;
+
+        /**
          * Creates a new task for painting without resource recycling.
          */
         Creator(final Renderer context) {
             renderer = context;
+            isDirect = MapCanvasAWT.this.isDirect;
         }
 
         /**
@@ -287,13 +364,16 @@ public abstract class MapCanvasAWT extends MapCanvas {
          * background thread is executed; no direct reference to {@link MapCanvas} here.
          */
         @Override
-        protected WritableImage call() throws TransformException {
+        protected WritableImage call() throws Exception {
             renderer.render();
             final int width  = renderer.getWidth();
             final int height = renderer.getHeight();
             drawTo = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
             final Graphics2D gr = drawTo.createGraphics();
             try {
+                gr.setRenderingHint(RenderingHints.KEY_RENDERING,
+                        isDirect ? RenderingHints.VALUE_RENDER_SPEED
+                                 : RenderingHints.VALUE_RENDER_QUALITY);
                 configuration = gr.getDeviceConfiguration();
                 renderer.paint(gr);
             } finally {
@@ -338,6 +418,68 @@ public abstract class MapCanvasAWT extends MapCanvas {
     }
 
     /**
+     * Tasks for updating {@link BufferedImage} directly in the JavaFX thread.
+     * This task is used if the rendering mode is {@link RenderingMode#DIRECT}.
+     */
+    private final class Direct extends Task<Void> implements Callback<PixelBuffer<IntBuffer>,
Rectangle2D> {
+        /**
+         * The user-provided object which will perform the actual rendering.
+         * Its {@link Renderer#paint(Graphics2D)} method will be invoked in JavaFX thread.
+         */
+        private final Renderer renderer;
+
+        /**
+         * Creates a new task for painting directly into the buffer shared by JavaFX image.
+         */
+        Direct(final Renderer context) {
+            renderer = context;
+        }
+
+        /**
+         * Invoked in background thread for preparing the image.
+         */
+        @Override
+        protected Void call() throws Exception {
+            renderer.render();                                  // Arbitrary user-specified
action.
+            return null;
+        }
+
+        /**
+         * Invoked by {@link PixelBuffer#updateBuffer(Callback)} for updating the {@link
#buffer} content.
+         */
+        @Override
+        public Rectangle2D call(final PixelBuffer<IntBuffer> wrapper) {
+            final BufferedImage drawTo = buffer;
+            final Graphics2D gr = drawTo.createGraphics();
+            try {
+                gr.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+                gr.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
+                gr.clearRect(0, 0, drawTo.getWidth(), drawTo.getHeight());
+                renderer.paint(gr);
+            } finally {
+                gr.dispose();
+            }
+            return null;
+        }
+
+        /**
+         * Invoked in JavaFX thread on success.
+         */
+        @Override
+        protected void succeeded() {
+            bufferWrapper.updateBuffer(this);       // This will invoke the `call(PixelBuffer)`
method above.
+            final boolean done  = renderer.commit(MapCanvasAWT.this);
+            renderingCompleted(this);
+            if (!done || contentsChanged()) {
+                repaint();
+            }
+        }
+
+        @Override protected void failed()    {renderingCompleted(this);}
+        @Override protected void cancelled() {renderingCompleted(this);}
+    }
+
+    /**
      * Background tasks for painting in an existing {@link BufferedImage}. This task is invoked
      * when previous resources (JavaFX image and Java2D volatile/buffered image) can be reused.
      * The Java2D volatile image will be rendered in background thread, then its content
will be
@@ -346,7 +488,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
     private final class Updater extends Task<VolatileImage> implements Callback<PixelBuffer<IntBuffer>,
Rectangle2D> {
         /**
          * The user-provided object which will perform the actual rendering.
-         * Its {@link Renderer#paint(Graphics2D)} method will be invoked.
+         * Its {@link Renderer#paint(Graphics2D)} method will be invoked in background thread.
          */
         private final Renderer renderer;
 
@@ -382,7 +524,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
          * background thread is executed; no direct reference to {@link MapCanvas} here.
          */
         @Override
-        protected VolatileImage call() throws TransformException {
+        protected VolatileImage call() throws Exception {
             renderer.render();
             final int width  = renderer.getWidth();
             final int height = renderer.getHeight();
@@ -399,6 +541,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
                     }
                     final Graphics2D gr = drawTo.createGraphics();
                     try {
+                        gr.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                         gr.setBackground(ColorModelFactory.TRANSPARENT);
                         gr.clearRect(0, 0, drawTo.getWidth(), drawTo.getHeight());
                         renderer.paint(gr);
@@ -464,10 +607,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
     @Override
     protected void clear() {
         image.setImage(null);
-        buffer              = null;
-        bufferWrapper       = null;
-        doubleBuffer        = null;
-        bufferConfiguration = null;
+        clearBuffer();
         super.clear();
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/RenderingMode.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/RenderingMode.java
new file mode 100644
index 0000000..ee75679
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/RenderingMode.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.map;
+
+import java.awt.Graphics2D;
+import java.awt.image.RenderedImage;
+import java.awt.geom.AffineTransform;
+
+
+/**
+ * Rendering strategies for {@link MapCanvasAWT}.
+ * This enumeration controls whether {@link MapCanvasAWT.Renderer#paint(Graphics2D)}
+ * should write directly into the {@linkplain MapCanvasAWT#image JavaFX image} buffer,
+ * or write in a temporary buffer to be copied later into the JavaFX image.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public enum RenderingMode {
+    /**
+     * {@link MapCanvasAWT.Renderer#paint(Graphics2D) Renderer.paint(…)} is invoked in
the JavaFX thread
+     * and paints directly into the JavaFX image buffer. This mode blocks the JavaFX thread
for the duration
+     * of the paint event. This mode should be used only when that painting is known to be
very fast,
+     * for example a single call to {@link Graphics2D#drawRenderedImage(RenderedImage, AffineTransform)}
+     * with an identity transform or a transform having only translation terms.
+     */
+    DIRECT,
+
+    /**
+     * {@link MapCanvasAWT.Renderer#paint(Graphics2D) Renderer.paint(…)} is invoked in
a background thread
+     * and paints into a temporary buffer. That temporary buffer is copied later into the
JavaFX image.
+     * This is the default rendering mode.
+     */
+    DOUBLE_BUFFERED
+}


Mime
View raw message