/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008 - 2009, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display2d.container; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.geotoolkit.display2d.canvas.RenderingContext2D; import org.geotoolkit.display2d.primitive.GraphicJ2D; import org.geotoolkit.map.MapItem; /** * Multithread rendering process used by GraphicContextJ2D. * this class handle several threads and buffer to speed up * rendering when distant layers exist in the mapcontext. * * @author Johann Sorel (Geomatys) * @module */ public class MultiThreadedRendering{ private static final ExecutorService executor = Executors.newFixedThreadPool(20); private final MapItem context; private final Map<MapItem, GraphicJ2D> layerGraphics; private final RenderingContext2D renderingContext; private final SortedMap<Integer, BufferedImage> buffers = new TreeMap<>(); public MultiThreadedRendering(final MapItem context, final Map<MapItem, GraphicJ2D> layerGraphics, final RenderingContext2D renderingContext){ this.context = context; this.layerGraphics = layerGraphics; this.renderingContext = renderingContext; } /** * Clear the buffer cache. */ public void dispose() { buffers.clear(); } /** * Pack the buffers, merge the different buffer when possible. * @return true if there is no more buffer to pack. false * if some buffers are not ready yet. */ private boolean pack() { synchronized(buffers){ final Set<Integer> keySet = buffers.keySet(); final Integer[] keys = keySet.toArray(new Integer[keySet.size()]); boolean valid = true; //we go throw the buffer and merge (with the renderingContext or between them) //when possible to reduce used memory as much as possible BufferedImage buffer = null; BufferedImage previousBuffer = null; Integer index = null; Integer previousIndex = null; for(int i=keys.length-1 ; i>=0 ; i--){ index = keys[i]; buffer = buffers.get(index); if(buffer == null){ //a buffer is not ready valid = false; }else{ if(previousBuffer != null){ //check if the buffer has a valid ARGB type buffer = validateBuffer(buffer); buffers.put(index, buffer); //we merge with previous buffer to reduce memory use buffer.getGraphics().drawImage(previousBuffer, 0, 0, null); //remove previous buffer from cache buffers.remove(previousIndex); } if(i==0){ renderingContext.switchToDisplayCRS(); renderingContext.getGraphics().drawImage(buffer, 0,0,null); //remove buffer from cache buffers.remove(index); } } previousIndex = index; previousBuffer = buffer; } return valid; } } /** * Wake the dispatching thread. */ private synchronized void wake() { notifyAll(); } /** * Make sleep the dispatch thread. */ private synchronized void block() { try { wait(); } catch (InterruptedException ex) { ex.printStackTrace(); } } /** * Build a temporary bufferImage of the canvas size. */ private BufferedImage buildBuffer(){ final Rectangle rect = renderingContext.getCanvasDisplayBounds(); return new BufferedImage( rect.width, rect.height, BufferedImage.TYPE_INT_ARGB ); } /** * Check the type of the buffer, * @return if the type is correct the original buffer is returned otherwise * a new buffer is created and the content of the old buffer is painted on the * new one. */ private BufferedImage validateBuffer(final BufferedImage buffer){ if(buffer.getType() != BufferedImage.TYPE_INT_ARGB){ final BufferedImage newBuffer = buildBuffer(); newBuffer.getGraphics().drawImage(buffer, 0, 0, null); return newBuffer; } return buffer; } public void render(){ final List<MapItem> layers = context.items(); final int size = layers.size(); if(size == 0){ return; }else if(size == 1){ //bypass threading final MapItem child = layers.get(0); if (!child.isVisible()) { return; } final GraphicJ2D gra = layerGraphics.get(child); gra.paint(renderingContext); return; } for (final MapItem child : layers) { //we ignore invisible layers if (!child.isVisible()) { continue; } final int zOrder = layers.indexOf(child); synchronized(buffers){ buffers.put(zOrder, null); } final Runnable call = new Runnable() { @Override public void run() { final BufferedImage img = buildBuffer(); final RenderingContext2D tc = renderingContext.create(img.createGraphics()); final GraphicJ2D gra = layerGraphics.get(child); gra.paint(tc); synchronized(buffers){ buffers.put(zOrder, img); } //we wake the dispatch thread that may be waiting for it wake(); } }; executor.execute(call); } //we now wait for every rendering to finish synchronized(this){ while (!pack()) { block(); } } } // /** // * {@inheritDoc } // */ // public void render() { // // final List<MapLayer> layers = context.layers(); // // final Rectangle rect = renderingContext.getCanvasDisplayBounds(); // final int width = rect.width; // final int height = rect.height; // // //first pass to start threads for dynamic layers // for (final MapLayer layer : layers) { // // //we ignore invisible layers // if (!layer.isVisible()) { // continue; // } // // if (layer instanceof DynamicMapLayer) { // final StatelessDynamicLayerJ2D gra = (StatelessDynamicLayerJ2D) layerGraphics.get(layer); // final int zOrder = layers.indexOf(layer); // buffers.put(zOrder, null); // // //start a separate thread that handle the distant serveur // new Thread() { // // @Override // public void run() { // BufferedImage img = null; // Object obj = null; // try { // obj = gra.query(renderingContext); // } catch (PortrayalException ex) { // img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); // Logging.getLogger("org.geotoolkit.display2d.container").log(Level.WARNING, null, ex); // } // // if(obj instanceof BufferedImage){ // img = (BufferedImage) obj; // }else if(obj instanceof Image){ // final BufferedImage buffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); // final Graphics2D g2 = buffer.createGraphics(); // g2.drawImage((Image)obj, 0,0,null); // g2.dispose(); // img = buffer; // }else if(obj instanceof RenderedImage){ // final BufferedImage buffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); // final Graphics2D g2 = buffer.createGraphics(); // g2.drawRenderedImage( (RenderedImage)obj, new AffineTransform()); // g2.dispose(); // img = buffer; // }else if(obj instanceof RenderableImage){ // final BufferedImage buffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); // final Graphics2D g2 = buffer.createGraphics(); // g2.drawRenderableImage( (RenderableImage)obj, new AffineTransform()); // g2.dispose(); // img = buffer; // }else if(obj instanceof URL){ // BufferedImage buffer = null; // try { // buffer = ImageIO.read((URL) obj); // } catch (IOException ex) { // Logging.getLogger("org.geotoolkit.display2d.container").log(Level.WARNING, null, ex); // } // img = buffer; // }else{ // final BufferedImage buffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); // //TODO DRAW a message to show that we couldn't paint this layer // img = buffer; // } // // synchronized(buffers){ // buffers.put(zOrder, img); // } // // //we wake the dispatch thread that may be waiting for it // wake(); // } // }.start(); // } // } // // //flag used to know if the previous layer is done // boolean previousIsNotReady = false; // RenderingContext2D validContext = null; // // for (final MapLayer layer : layers) { // // //we ignore invisible layers // if (!layer.isVisible()) { // continue; // } // // if (layer instanceof DynamicMapLayer) { // // A dynamic layer // final int zOrder = layers.indexOf(layer); // // //see if the distant layer has finished rendering // final Image layerImage = buffers.get(new Integer(zOrder)); // if (layerImage != null) { // //the distant layer has finished rendering // //we can directly paint the result image // // if (previousIsNotReady) { // //if the previous laye was distant and not ready we have to create a temp buffer. // final BufferedImage buffer = buildBuffer(); // //append the buffer in the buffers set // buffers.put(layers.indexOf(layer), buffer); // // if(validContext != null && validContext != renderingContext){ // //the context is not the main rendering context, we dispose it //// validContext.gdisposeGraphics(); //// validContext.setGraphics(null, null); // } // // //create a new rendering context using the bufferedimage // validContext = renderingContext.create(buffer.createGraphics()); // } else { // //we use the last valid rendering context or the default one. // if (validContext == null) { // validContext = renderingContext; // } // } // // validContext.switchToDisplayCRS(); // validContext.getGraphics().drawImage(layerImage, 0, 0, null); // // previousIsNotReady = false; // } else { // //Distant layer is not ready, we will have to wait // previousIsNotReady = true; // } // // } else { // // A normal feature layer // final GraphicJ2D gra = layerGraphics.get(layer); // // if (previousIsNotReady) { // //if the previous laye was distant and not ready we have to create a temp buffer. // final BufferedImage buffer = buildBuffer(); // //append the buffer in the buffers set // buffers.put(layers.indexOf(layer), buffer); // // if(validContext != null && validContext != renderingContext){ // //the context is not the main rendering context, we dispose it //// validContext.disposeGraphics(); //// validContext.setGraphics(null, null); // } // // //create a new rendering context using the bufferedimage // validContext = renderingContext.create(buffer.createGraphics()); // } else { // //we use the last valid rendering context or the default one. // if (validContext == null) { // validContext = renderingContext; // } // } // // gra.paint(validContext); // previousIsNotReady = false; // } // // } // // //we now wait for every rendering to finish // synchronized(this){ // while (!pack()) { // block(); // } // } // // } }