diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 50e4421c9e6d13a89987b518002082df310f6337..11c670d8499ccb0ce5ddd89742cb186ce03d0b95 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -9,6 +9,7 @@
 * Paul Miner
 * Lorenzo Gallucci
 * Subhomoy Haldar (HungryBlueDev)
+* Marco Stefanetti 
 
 # Details
 
diff --git a/src/main/java/com/aparapi/examples/All.java b/src/main/java/com/aparapi/examples/All.java
index 995bbc81a36d04813907b217a93b091ebf9a4b93..fa1adcb3dea5f25ff975bf53ec390e84e67d0643 100644
--- a/src/main/java/com/aparapi/examples/All.java
+++ b/src/main/java/com/aparapi/examples/All.java
@@ -83,6 +83,9 @@ public class All {
         System.out.println(" 34) OOPN Body");
         System.out.println(" 35) Map-reduce");
         System.out.println(" 36) Correlation Matrix");
+        System.out.println(" 37) AparapiFractals - Mandelbrot explorer ");
+        System.out.println(" 38) AparapiFractals - soft benchmark ");
+        System.out.println(" 39) AparapiFractals - hard benchmark ");
         System.out.println();
 
         Scanner in = new Scanner(System.in);
@@ -221,6 +224,15 @@ public class All {
             case "36":
                 com.aparapi.examples.matrix.Main.main(args);
                 break;
+            case "37":
+                com.aparapi.examples.afmandelbrot.AfMain.main(args);
+                break;
+            case "38":
+                com.aparapi.examples.afmandelbrot.AfBenchmark.main(new String[]{"SOFT"});
+                break;
+            case "39":
+                com.aparapi.examples.afmandelbrot.AfBenchmark.main(new String[]{"HARD"});
+                break;
             default:
                 System.out.println("Invalid selection.");
         }
diff --git a/src/main/java/com/aparapi/examples/afmandelbrot/AfAparapiUtils.java b/src/main/java/com/aparapi/examples/afmandelbrot/AfAparapiUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5dfaaeb14c5fff758634150913c57acd177364a
--- /dev/null
+++ b/src/main/java/com/aparapi/examples/afmandelbrot/AfAparapiUtils.java
@@ -0,0 +1,270 @@
+package com.aparapi.examples.afmandelbrot;
+
+import java.util.List;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+import com.aparapi.Kernel;
+import com.aparapi.Range;
+import com.aparapi.device.Device;
+import com.aparapi.device.OpenCLDevice;
+import com.aparapi.internal.kernel.KernelManager;
+
+/**
+ * Aparapi Fractals
+ * 
+ * Aparapi code is here and in the kernel.
+ * 
+ * The constructor prepares a map of Aparapi Devices using a String as a key.
+ * The strings used as keys are created combining device shortDescription and
+ * deviceId. That's for convenience, to show the keys on the gui combo box and
+ * use them to retrieve the selected device and kernel from the maps.
+ * 
+ * @author marco.stefanetti at gmail.com
+ * 
+ */
+public class AfAparapiUtils {
+
+	/** logger */
+	private static final Logger LOG = Logger.getLogger(AfAparapiUtils.class);
+
+	/** the list of device keys in an array, used to populate the Swing JComboBox */
+	private String[] deviceKeys;
+
+	/** a map between device keys and available devices */
+	private TreeMap<String, Device> devicesMap = new TreeMap<>();
+
+	/** keep a kernel instance ready for each device */
+	private TreeMap<String, AfKernel> kernelsMap = new TreeMap<>();
+
+	/** best device key, based on KernelManager.instance().bestDevice() */
+	private String bestDeviceKey;
+
+	/** selected device */
+	private Device device;
+
+	/** the range to be used by the kernel */
+	private Range range;
+
+	/** the kernel for the selected device */
+	private AfKernel kernel;
+
+	/** the name of the last device used */
+	private String deviceName;
+
+	/**
+	 * The constructor prepares the keys, the map containing the devices and a map
+	 * with a kernel instance for each device.
+	 */
+	@SuppressWarnings("deprecation")
+	public AfAparapiUtils() {
+
+		List<Device> devices = KernelManager.instance().getDefaultPreferences().getPreferredDevices(null);
+		deviceKeys = new String[devices.size()];
+
+		int p = 0;
+		for (Device device : devices) {
+
+			/** prepare a talking key, description and id **/
+			String talkingKey = device.getType().toString() + " " + device.getShortDescription() + " ("
+					+ device.getDeviceId() + ")";
+
+			if (device == KernelManager.instance().bestDevice()) {
+				talkingKey += " *";
+				bestDeviceKey = talkingKey;
+			}
+
+			/** add the device to the devices map */
+			devicesMap.put(talkingKey, device);
+
+			/**
+			 * must instantiate a dedicated new kernel for each device, otherwise It's
+			 * always using the first GPU device
+			 */
+			AfKernel deviceKernel = new AfKernel();
+
+			/** add a kernel dedicated to this device in the map of kernels */
+			kernelsMap.put(talkingKey, deviceKernel);
+
+			/**
+			 * I set the execution mode to GPU or CPU to have legacyExecutionMode in
+			 * Kernel.execute(String _entrypoint, Range _range, int _passes)
+			 **/
+			if (device.getType().equals(Device.TYPE.GPU)) {
+				deviceKernel.setExecutionModeWithoutFallback(Kernel.EXECUTION_MODE.GPU);
+			} else if (device.getType().equals(Device.TYPE.CPU)) {
+				deviceKernel.setExecutionModeWithoutFallback(Kernel.EXECUTION_MODE.CPU);
+			} else {
+				deviceKernel.setFallbackExecutionMode();
+			}
+
+			/** fake execution on the device before adding to the array for the GUI */
+			boolean success = fakeExecution(talkingKey);
+
+			if (!success) {
+
+				LOG.warn("AfAparapiUtils device test FAILED : " + talkingKey);
+
+			} else {
+
+				/**
+				 * I save the keys both in an array and in a map for convenience. The array
+				 * contains devices that succeeded the fake execution. 
+				 **/
+				deviceKeys[p++] = talkingKey;
+				LOG.info("AfAparapiUtils device test OK : " + talkingKey);
+			}
+
+		}
+	}
+
+	/**
+	 * fake execution to see kernel working on a device and to see kernel
+	 * compilation at startup
+	 * 
+	 * @return fake execution success
+	 */
+	private boolean fakeExecution(String key) {
+
+		try {
+			/** prepares the kernel as for an image 1x1 pixels */
+			init(key, 1, 1);
+			kernel.init(-2, -2, 2, 2, 1, 1, 1);
+			kernel.execute(range);
+		} catch (Throwable exc) {
+			LOG.error("!!! " + exc.getMessage());
+			LOG.error("fake execution FAILED");
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * calls the init with a default localSize.
+	 * 
+	 * @param deviceKey
+	 * @param W
+	 * @param H
+	 */
+	public void init(String deviceKey, int W, int H) {
+
+		/**
+		 * TODO 8x8 is empirically the best
+		 */
+		init(deviceKey, W, H, 8, 8);
+	}
+
+	/**
+	 * Prepares the range and reads device description, based on the device and
+	 * image size the range can be reused many times, so we need to instantiate the
+	 * range only when device changes or image size changes
+	 * @param localSize2 
+	 */
+	public void init(String deviceKey, int W, int H, int localSize0, int localSize1) {
+
+		device = devicesMap.get(deviceKey);
+
+		kernel = kernelsMap.get(deviceKey);
+
+		int localWidth = localSize0;
+		int localHeight = localSize1;
+
+		/** global sizes must be a multiple of local sizes */
+		int globalWidth = (1 + W / localWidth) * localWidth;
+		int globalHeight = (1 + H / localHeight) * localHeight;
+
+		range = device.createRange2D(globalWidth, globalHeight, localWidth, localHeight);
+
+		deviceName = device.getShortDescription();
+		if (device instanceof OpenCLDevice) {
+			OpenCLDevice ocld = (OpenCLDevice) device;
+			deviceName = ocld.getName();
+		}
+
+	}
+
+	/**
+	 * call the kernel execution and track elapsed time
+	 * 
+	 * @return elapsed milliseconds
+	 */
+	public long execute(double cx1, double cy1, double cx2, double cy2, int w, int h, int maxIterations) {
+
+		if (kernel == null) {
+			LOG.error("null Kernel");
+			return 0;
+		}
+
+		while (kernel.isExecuting()) {
+			LOG.warn("Already running, waiting ... " + device.getShortDescription());
+			try {
+				Thread.sleep(10l);
+			} catch (InterruptedException e) {
+			}
+		}
+
+		long startTime = System.currentTimeMillis();
+		kernel.init(cx1, cy1, cx2, cy2, w, h, maxIterations);
+		kernel.execute(range);
+		long endTime = System.currentTimeMillis();
+		long elapsed = (endTime - startTime);
+
+		if ((kernel != null) && (kernel.getProfileInfo() != null)) {
+			kernel.cleanUpArrays();
+		}
+
+		return elapsed;
+
+	}
+
+	/** @return the list of keys of the devices */
+	public String[] getDeviceKeys() {
+		return deviceKeys;
+	}
+
+	/** @return the name of the last device used */
+	public String getDeviceName() {
+		return deviceName;
+	}
+
+	/** @return the dimension XxY of the local widths of the range */
+	public String getLocalSizes() {
+		String localSizes = range.getLocalSize_0() + " x " + range.getLocalSize_1();
+		return localSizes;
+	}
+
+	/**
+	 * @return the key of the best device by KernelManager
+	 */
+	public String getBestDeviceKey() {
+		return bestDeviceKey;
+	}
+
+	/**
+	 * @return last device selected
+	 */
+	public Device getDevice() {
+		return device;
+	}
+
+	/**
+	 * @return the kernel of the selected device
+	 */
+	public AfKernel getKernel() {
+		return kernel;
+	}
+
+	/**
+	 * @return the range
+	 */
+	public Range getRange() {
+		return range;
+	}
+
+	public int[][] getResult() {
+		return kernel.getResult();
+	}
+
+}
diff --git a/src/main/java/com/aparapi/examples/afmandelbrot/AfBenchmark.java b/src/main/java/com/aparapi/examples/afmandelbrot/AfBenchmark.java
new file mode 100644
index 0000000000000000000000000000000000000000..1764d4bcbe6efb44ee7216ab8158ee6e38728e1f
--- /dev/null
+++ b/src/main/java/com/aparapi/examples/afmandelbrot/AfBenchmark.java
@@ -0,0 +1,231 @@
+package com.aparapi.examples.afmandelbrot;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+
+import org.apache.log4j.Logger;
+
+import com.aparapi.device.Device;
+
+/**
+ * Aparapi Fractals
+ * 
+ * only benchmark, results on the console, no graphics
+ * 
+ * @author marco.stefanetti at gmail.com
+ * 
+ */
+public class AfBenchmark {
+
+	/** logger */
+	private static final Logger LOG = Logger.getLogger(AfBenchmark.class);
+
+	/** flag to stop benchmarks */
+	private static boolean running = false;
+
+	/** no localSize specified */
+	private static int[][] defaultLocalSizes = { { 0, 0 } };
+
+	/** set of different range2D, localSize x localSize   */
+	private static int[][] multipleLocalSizes = { {2,2}, {4,4}, {4,8}, {8,4}, {8,8}, {10,10}, {4,16}, {16,8}, {16,16} };
+
+	/** used by the GUI to ask to stop benchmark */
+	public static void requestStop() {
+		running = false;
+	}
+
+	/**
+	 * executes a soft benchmark
+	 * 
+	 * @param afAparapiUtils
+	 */
+	public static void benchmarkSoft(AfAparapiUtils afAparapiUtils) {
+		LOG.debug("Starting benchmark Soft");
+		AfBenchmark.benchmark(afAparapiUtils, false, "Soft", -2, -2, 2, 2, 500, 500, 100000, "ALL", 1000);
+	}
+
+	/**
+	 * executes a hard benchmark
+	 * 
+	 * @param afAparapiUtils
+	 */
+	public static void benchmarkHard(AfAparapiUtils afAparapiUtils) {
+		LOG.debug("Starting benchmark Hard");
+		AfBenchmark.benchmark(afAparapiUtils, false, "Hard", -2, -2, 2, 2, 500, 500, 1000000, "ALL", 1000);
+	}
+
+	/**
+	 * executes with different localSizes
+	 * 
+	 * @param afAparapiUtils
+	 */
+	public static void benchmarkLocalSizes(AfAparapiUtils afAparapiUtils) {
+		LOG.debug("Starting benchmark localSizes");
+		AfBenchmark.benchmark(afAparapiUtils, true, "localSizes", -2, -2, 2, 2, 900, 900, 10000, "GPU", 10);
+	}
+
+	/**
+	 * executes a repeated loop over all devices
+	 * 
+	 * @param afAparapiUtils
+	 */
+	public static void benchmarkStress(AfAparapiUtils afAparapiUtils) {
+		LOG.debug("Starting benchmark Stress");
+		for (int n = 0; n < 100; n++) {
+			AfBenchmark.benchmark(afAparapiUtils, false, "Stress" + n, -2, -2, 2, 2, 500, 500, 10000, "ALL", 0);
+		}
+	}
+
+	public static void benchmark(AfAparapiUtils afAparapiUtils, String mode) {
+		
+		if ("SOFT".equals(mode)) {
+
+			benchmarkSoft(afAparapiUtils);
+
+		} else if ("HARD".equals(mode)) {
+
+			benchmarkHard(afAparapiUtils);
+
+		} else if ("STRESS".equals(mode)) {
+
+			benchmarkStress(afAparapiUtils);
+
+		} else if ("LSIZE".equals(mode)) {
+
+			benchmarkLocalSizes(afAparapiUtils);
+
+		} else {
+			
+			LOG.warn("Unknown mode : "+mode);
+			
+		}
+	}
+	
+	/**
+	 * execute the kernel on different devices and tracks timings. The iterations
+	 * are discarded, used only here, no image refresh.
+	 */
+	@SuppressWarnings("deprecation")
+	public static void benchmark(AfAparapiUtils afAparapiUtils, boolean loopLocalSizes, String title, double cx1,
+			double cy1, double cx2, double cy2, int W, int H, int max_iterations, String deviceTypeFilter, long sleep) {
+
+		running = true;
+
+		OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
+		int cpus = osBean.getAvailableProcessors();
+		String osArc = osBean.getArch();
+		String osName = osBean.getName();
+
+		System.out.println();
+		System.out.println("Starting benchmark...");
+		System.out.println("Example : 'GeForce GTX 1650 SUPER' soft 348ms, hard 3058ms ");
+		System.out.println("Example : 'Java Alternative Algorithm', AMD 3700X, soft 390ms, hard 3993ms ");
+		System.out.println();
+		System.out.println("OperatingSystem: " + osName + " CPU:" + cpus + " " + osArc);
+		System.out.println(
+				"=======================================================================================================================================");
+		System.out.printf("AparapiFractals - Mandelbrot Benchmark - %s \n", title);
+		System.out.printf("  image size     : %d x %d \n", W, H);
+		System.out.printf("  maxIterations  : %,d \n", max_iterations);
+		System.out.printf("  complex region : %2.16fd,%2.16fd %2.16fd,%2.16fd \n", cx1, cy1, cx2, cy2);
+		System.out.println();
+
+		System.out.println(
+				"+-----+--------------------------------+----------------+--------------------------------------------+----------+--------+------------+");
+		System.out.println(
+				"|Type | shortDescription               | deviceId       | Name                                       | LSizes   | ExMode | Elapsed(ms)|");
+		System.out.println(
+				"+-----+--------------------------------+----------------+--------------------------------------------+----------+--------+------------+");
+
+		int[][] localSizes;
+
+		if (loopLocalSizes) {
+			localSizes = multipleLocalSizes;
+		} else {
+			localSizes = defaultLocalSizes;
+		}
+
+		for (String key : afAparapiUtils.getDeviceKeys()) {
+
+			for (int[] localSize : localSizes) {
+
+				if (!running) {
+					System.out.println("benchmark interrupted");
+					return;
+				}
+
+				if(localSize[0]==0) {
+					afAparapiUtils.init(key, W, H);
+				} else {
+					afAparapiUtils.init(key, W, H, localSize[0], localSize[1]);
+				}
+
+				Device device = afAparapiUtils.getDevice();
+
+				if (("ALL".equals(deviceTypeFilter)) || (device.getType().toString().equals(deviceTypeFilter))) {
+
+					String description = device.getShortDescription();
+
+					String execMode;
+
+					long elapsed = 0;
+
+					elapsed = afAparapiUtils.execute(cx1, cy1, cx2, cy2, W, H, max_iterations);
+					execMode = afAparapiUtils.getKernel().getExecutionMode().toString();
+
+					/*
+					 * int[][] iterations = afAparapiUtils.getResult(); 
+					 * long totalIterations = 0;
+					 * for (int i = 0; i < W; i++) { for (int j = 0; j < H; j++) { totalIterations
+					 * += iterations[i][j]; } }
+					 * 
+					 * if (totalIterations < 1000) { LOG.warn("FAILED totalIterations:" +
+					 * totalIterations); }
+					 */
+
+					System.out.printf("| %3s | %-30s | %-14d | %-42s | %-8s | %6s | %10d |\n",
+							device.getType().toString(), description, device.getDeviceId(),
+							afAparapiUtils.getDeviceName(), afAparapiUtils.getLocalSizes(), execMode, elapsed);
+				}
+
+				if (!running) {
+					System.out.println("benchmark interrupted");
+					return;
+				}
+
+				try {
+					Thread.sleep(sleep);
+				} catch (InterruptedException e) {
+				}
+
+			}
+
+		}
+		System.out.println(
+				"+-----+--------------------------------+----------------+--------------------------------------------+----------+--------+------------+");
+		System.out.println();
+		System.out.println(
+				"=======================================================================================================================================");
+
+		running = false;
+		LOG.debug("Benchmark over");
+	}
+
+	public static void main(String[] args) {
+
+		System.setProperty("com.aparapi.enableShowGeneratedOpenCL", "true");
+		System.setProperty("com.aparapi.dumpProfilesOnExit", "true");
+
+		String mode = "SOFT";
+
+		if (args.length > 0) {
+			mode = args[0];
+		}
+
+		AfAparapiUtils afAparapiUtils = new AfAparapiUtils();
+
+		benchmark(afAparapiUtils, mode);
+
+	}
+
+}
diff --git a/src/main/java/com/aparapi/examples/afmandelbrot/AfGUI.java b/src/main/java/com/aparapi/examples/afmandelbrot/AfGUI.java
new file mode 100644
index 0000000000000000000000000000000000000000..489038f7c13f95df260bc980f1ebe6354f8983ea
--- /dev/null
+++ b/src/main/java/com/aparapi/examples/afmandelbrot/AfGUI.java
@@ -0,0 +1,990 @@
+package com.aparapi.examples.afmandelbrot;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.image.BufferedImage;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Aparapi Fractals
+ *  
+ * The GUI, a swing JFrame with components. There is no Aparapi code here, only
+ * swing and events handling. The GUI has no access to aparapiUtils, It
+ * interacts only with the AfMain.
+ * 
+ * @author marco.stefanetti at gmail.com
+ * 
+ */
+
+public class AfGUI {
+
+	/** logger */
+	private static final Logger LOG = Logger.getLogger(AfGUI.class);
+
+	/**
+	 * a pointer to the main to read calculated iterations and Mandelbrot settings
+	 */
+	private AfMain main;
+
+	/** arbitrary choice on max colors */
+	private int MAX_COLORS = 128;
+
+	/** colors palette is calculated once in colors[] */
+	private int[] colors = new int[MAX_COLORS];
+
+	/** used for the Mandelbrot set, always black */
+	private int RGB_BLACK = Color.BLACK.getRGB();
+
+	/** colors offset, may be one day we will cycle colors */
+	private int colorOffset = 0;
+
+	/** the main window */
+	JFrame frame;
+
+	/** the image */
+	private BufferedImage image;
+
+	/** the panel that contains the image, to be refreshed */
+	private JPanel imagePanel;
+
+	/** swing fields to refreshed **/
+	private JTextField tfcx1;
+	private JTextField tfcy1;
+	private JTextField tfcx2;
+	private JTextField tfcy2;
+	private JTextField tfcdx;
+	private JTextField tfcdy;
+
+	private JTextField tfW;
+	private JTextField tfH;
+	private JTextField tfMaxIterations;
+	private JTextField tfZoom;
+	private JSlider jsZoom;
+
+	private JTextField tfDeviceName;
+	private JTextField tfElapsed;
+	private JTextField tfTotalIterations;
+	private JLabel lblDeviceLed;
+	private JLabel lblBenchmarkLed;
+	private JComboBox<String> deviceComboBox;
+
+	/** DEBUG enable a button to execute multiple loops on all devices */
+	private boolean showDeviceLoopButton = false;
+
+	/** mouse events dragging flag */
+	boolean dragging = false;
+	/** drag starting x */
+	private int dragging_px;
+	/** drag starting y */
+	private int dragging_py;
+	/** drag x remaining due to fractions */
+	private float dragging_rx;
+	/** drag y remaining due to fractions */
+	private float dragging_ry;
+
+	/** mouse wheel zooming */
+	boolean mouseWheelZooming = false;
+
+	/** refreshing flag, disable the slider event while setting It's value */
+	protected boolean jsZoomDisable = false;
+
+	/** starts the gui ignoring events */
+	protected boolean guiEvents = false;
+
+	int yOffset = 0;
+	int xOffset = 0;
+
+	/** refreshing the GUI */
+	protected boolean refreshing = false;
+
+	/** last maxIterations used is saved in the GUI for the GUI refresh */
+	protected long lastMaxIterations;
+
+	/** setup all the swing components and event listeners */
+	public AfGUI(AfMain _main) {
+
+		main = _main;
+
+		/** setup once color palette */
+		for (int c = 0; c < MAX_COLORS; c++) {
+			float hue = (float) c / (float) MAX_COLORS;
+			float saturation = 1.0f;
+			float brightness = 1.0f;
+			Color color = Color.getHSBColor(hue, saturation, brightness);
+			colors[c] = color.getRGB();
+		}
+
+		/** frame is the main window */
+		frame = new JFrame("Aparapi Fractals - Mandelbrot set");
+		frame.setMinimumSize(new Dimension(100, 100));
+		Dimension dim = new Dimension((int) (main.W + 400), (int) (main.H + 50));
+		frame.setPreferredSize(dim);
+		frame.setSize(dim);
+		frame.setBackground(Color.BLACK);
+
+		Container contentPane = frame.getContentPane();
+
+		/** image panel */
+		Border imageBevel = BorderFactory.createLoweredBevelBorder();
+		JPanel imageBorderedPanel = new JPanel(new GridBagLayout());
+		imageBorderedPanel.setBorder(imageBevel);
+
+		imagePanel = new JPanel() {
+			private static final long serialVersionUID = -2006337199526432552L;
+
+			public void paint(Graphics g) {
+				g.drawImage(image, xOffset, yOffset, this);
+			}
+		};
+		imagePanel.setBackground(Color.BLACK);
+
+		imageBorderedPanel.add(imagePanel, new GridBagConstraints(1, 1, 1, 1, 1, 1, GridBagConstraints.LINE_START,
+				GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+
+		/** inputs panel */
+		Border inputBevel = BorderFactory.createRaisedBevelBorder();
+		JPanel inputBorderedPanel = new JPanel(new GridBagLayout());
+		inputBorderedPanel.setBorder(inputBevel);
+
+		int lineSpace = 5;
+		int elementsSpace = 10;
+
+		JPanel inputPanel = new JPanel();
+		inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
+
+		JButton b0 = new JButton("Home");
+		JButton b1 = new JButton("Sun");
+		JButton b2 = new JButton("Spider");
+		JButton b3 = new JButton("Crystal");
+		JButton b4 = new JButton("Cell");
+		JButton b5 = new JButton("Flower");
+		JButton b6 = new JButton("Psyco");
+		JButton b7 = new JButton("Needlework");
+		JButton b8 = new JButton("Peter");
+		JButton b9 = new JButton("Rings");
+		JButton b10 = new JButton("Jellyfish");
+		JButton b11 = new JButton("FastHome");
+		{
+			JPanel buttons = new JPanel();
+			buttons.setLayout(new GridLayout(4, 3));
+			buttons.add(b0);
+			buttons.add(b1);
+			buttons.add(b2);
+			buttons.add(b3);
+			buttons.add(b4);
+			buttons.add(b5);
+			buttons.add(b6);
+			buttons.add(b7);
+			buttons.add(b8);
+			buttons.add(b9);
+			buttons.add(b10);
+			buttons.add(b11);
+			buttons.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(buttons);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		{
+			JLabel complexplane = new JLabel("Complex Plane");
+			complexplane.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(complexplane);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		{
+			JPanel c1p = new JPanel();
+			c1p.setLayout(new BoxLayout(c1p, BoxLayout.X_AXIS));
+			JLabel lcx1 = new JLabel("x1 ");
+			c1p.add(lcx1);
+			tfcx1 = new JTextField(1);
+			tfcx1.setEditable(false);
+			c1p.add(tfcx1);
+			c1p.add(Box.createRigidArea(new Dimension(10, 0)));
+			JLabel lcy1 = new JLabel("y1 ");
+			c1p.add(lcy1);
+			tfcy1 = new JTextField(1);
+			tfcy1.setEditable(false);
+			c1p.add(tfcy1);
+			c1p.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(c1p);
+		}
+
+		{
+			JPanel c2p = new JPanel();
+			c2p.setLayout(new BoxLayout(c2p, BoxLayout.X_AXIS));
+			JLabel lcx2 = new JLabel("x2 ");
+			c2p.add(lcx2);
+			tfcx2 = new JTextField(1);
+			tfcx2.setEditable(false);
+			c2p.add(tfcx2);
+			c2p.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			JLabel lcy2 = new JLabel("y2 ");
+			c2p.add(lcy2);
+			tfcy2 = new JTextField(1);
+			tfcy2.setEditable(false);
+			c2p.add(tfcy2);
+			c2p.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(c2p);
+		}
+
+		{
+			JPanel cd = new JPanel();
+			cd.setLayout(new BoxLayout(cd, BoxLayout.X_AXIS));
+			JLabel cdx = new JLabel("dx ");
+			cd.add(cdx);
+			tfcdx = new JTextField(1);
+			tfcdx.setEditable(false);
+			cd.add(tfcdx);
+			cd.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			JLabel cdy = new JLabel("dy ");
+			cd.add(cdy);
+			tfcdy = new JTextField(1);
+			tfcdy.setEditable(false);
+			cd.add(tfcdy);
+			cd.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cd);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+		inputPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		{
+			JPanel cp = new JPanel();
+			cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
+			JLabel canvas = new JLabel("Image ");
+			cp.add(canvas);
+			cp.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			JLabel cW = new JLabel(" W ");
+			cp.add(cW);
+			tfW = new JTextField(1);
+			cp.add(tfW);
+			tfW.setEditable(false);
+			cp.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			JLabel cH = new JLabel(" H ");
+			cp.add(cH);
+			tfH = new JTextField(1);
+			tfH.setEditable(false);
+			cp.add(tfH);
+			JLabel lmi = new JLabel("  MaxIterations ");
+			cp.add(lmi);
+			tfMaxIterations = new JTextField(2);
+			tfMaxIterations.setEditable(false);
+			cp.add(tfMaxIterations);
+			cp.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cp);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		{
+
+			JPanel cp = new JPanel();
+			cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
+			JLabel zl = new JLabel("Zoom ");
+			cp.add(zl);
+			tfZoom = new JTextField(10);
+			cp.add(tfZoom);
+			tfZoom.setEditable(false);
+			cp.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			jsZoom = new JSlider(0, 100);
+			/** POI scrollbar zoom */
+			jsZoom.addChangeListener(new ChangeListener() {
+				@Override
+				public void stateChanged(ChangeEvent event) {
+
+					if ((!jsZoomDisable && guiEvents) && !(jsZoom.getValueIsAdjusting() && main.elapsed > 200)) {
+						int value = jsZoom.getValue();
+						stopAll();
+						zoom(value);
+					}
+
+				}
+			});
+			cp.add(jsZoom);
+			cp.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cp);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+		inputPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		{
+			JPanel cp = new JPanel();
+			cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
+			JLabel cW = new JLabel("total iterations ");
+			cp.add(cW);
+			tfTotalIterations = new JTextField(3);
+			tfTotalIterations.setEditable(false);
+			cp.add(tfTotalIterations);
+			JLabel cH = new JLabel(" ms ");
+			cp.add(cH);
+			tfElapsed = new JTextField(1);
+			tfElapsed.setEditable(false);
+			cp.add(tfElapsed);
+			cp.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cp);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+		{
+			JPanel cp = new JPanel();
+			cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
+			deviceComboBox = new JComboBox<String>(main.getDeviceKeys());
+			int index = 0;
+			for (int i = 0; i < main.getDeviceKeys().length; i++) {
+				if (main.selectedDeviceKey.equals(main.getDeviceKeys()[i])) {
+					index = i;
+				}
+			}
+			deviceComboBox.setSelectedIndex(index);
+
+			/** POI device change listener */
+			deviceComboBox.addItemListener(new ItemListener() {
+				@Override
+				public void itemStateChanged(ItemEvent e) {
+					if (e.getStateChange() == ItemEvent.SELECTED) {
+						deviceComboBox.setEnabled(false);
+						String newDeviceKey = (String) deviceComboBox.getSelectedItem();
+						deviceChange(newDeviceKey);
+					}
+				}
+
+			});
+			cp.add(deviceComboBox);
+			cp.add(Box.createRigidArea(new Dimension(elementsSpace, 0)));
+			lblDeviceLed = new JLabel("OFF", JLabel.CENTER);
+			cp.add(lblDeviceLed);
+
+			cp.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cp);
+
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+		{
+			JPanel cp = new JPanel();
+			cp.setLayout(new BoxLayout(cp, BoxLayout.X_AXIS));
+			JLabel cW = new JLabel("Device ");
+			cp.add(cW);
+			tfDeviceName = new JTextField(10);
+			tfDeviceName.setEditable(false);
+			cp.add(tfDeviceName);
+			cp.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(cp);
+		}
+
+		inputPanel.add(Box.createRigidArea(new Dimension(1, lineSpace)));
+
+		JButton benchSoft = new JButton("Soft");
+		JButton benchHard = new JButton("Hard");
+		JButton benchSizes = new JButton("Sizes");
+		JButton benchHere = new JButton("Here");
+		JButton benchStop = new JButton("Stop");
+		lblBenchmarkLed = new JLabel("OFF", JLabel.CENTER);
+		{
+			JLabel l = new JLabel("Benchmark");
+			l.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(l);
+			JPanel buttons = new JPanel();
+			buttons.setLayout(new GridLayout(1, 6));
+			buttons.add(benchSoft);
+			buttons.add(benchHard);
+			buttons.add(benchHere);
+			buttons.add(benchSizes);
+			buttons.add(benchStop);
+			buttons.add(lblBenchmarkLed);
+			buttons.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(buttons);
+
+		}
+
+		JButton randomDebug = new JButton("DEBUG device loop");
+		if (showDeviceLoopButton) {
+			JPanel p = new JPanel();
+			p.add(randomDebug);
+			p.setAlignmentX(Component.LEFT_ALIGNMENT);
+			inputPanel.add(p);
+		}
+
+		inputBorderedPanel.add(inputPanel, new GridBagConstraints(1, 1, 1, 1, 1, 1, GridBagConstraints.NORTH,
+				GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0));
+
+		// ----- frame -----
+
+		contentPane.setLayout(new GridBagLayout());
+		contentPane.add(imageBorderedPanel, new GridBagConstraints(1, 1, 1, 1, 0.99, 1, GridBagConstraints.NORTH,
+				GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+		contentPane.add(inputBorderedPanel, new GridBagConstraints(2, 1, 1, 1, 0.01, 1, GridBagConstraints.NORTH,
+				GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+
+		frame.setLocationByPlatform(true);
+		frame.setVisible(true);
+
+		{
+			Dimension d = lblDeviceLed.getSize();
+			lblDeviceLed.setPreferredSize(d);
+			lblDeviceLed.setMaximumSize(d);
+			lblDeviceLed.setMinimumSize(d);
+			deviceLedOff();
+		}
+
+		{
+			Dimension d = lblBenchmarkLed.getSize();
+			lblBenchmarkLed.setPreferredSize(d);
+			lblBenchmarkLed.setMaximumSize(d);
+			lblBenchmarkLed.setMinimumSize(d);
+			benchmarkLedOff();
+		}
+
+		/** close event */
+		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+		b0.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.threadGoHome(10);
+			}
+
+		});
+
+		b1.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(0.1329154802887031d, 0.6706861139480367d, 0.1329154802897748d, 0.6706861139491085d, 100d);
+			}
+
+		});
+
+		b2.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-1.9687843996500283d, -0.0000000000623248d, -1.9687843995067890d, 0.0000000000628460d,
+						100d);
+
+			}
+		});
+
+		b3.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(0.4480950090233584d, -0.4102090411357999d, 0.4480950091319934d, -0.4102090410418815d,
+						80d);
+			}
+		});
+
+		b4.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-0.6907229455219234d, 0.4652530104374417d, -0.6907229455187313d, 0.4652530104399114d,
+						100d);
+			}
+		});
+
+		b5.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-1.7496850545473188d, -0.0000000451105513d, -1.7496849592009920d, 0.0000000426514086d,
+						100d);
+			}
+		});
+
+		b6.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-0.7379598288776590d, -0.2191410537224941d, -0.7379598288765801d, -0.2191410537215613d,
+						80d);
+			}
+		});
+
+		b7.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-0.7499210743130593d, 0.0315822442134116d, -0.7499210743119602d, 0.0315822442143673d,
+						100d);
+			}
+		});
+
+		b8.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-1.7494348011240726d, 0.0000005026481909d, -1.7494348011229415d, 0.0000005026492321d,
+						100d);
+			}
+		});
+
+		b9.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-0.2175644994305805d, -1.1144202070198740d, -0.2175644994295201d, -1.1144202070190840d,
+						100d);
+			}
+		});
+
+		b10.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+				main.threadGo(-1.7494326807753284d, -0.0000000000012870d, -1.7494326807728570d, 0.0000000000011843d,
+						100d);
+			}
+		});
+
+		b11.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+				main.goHome();
+			}
+		});
+
+		benchSoft.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				startBenchmark("SOFT");
+			}
+		});
+
+		benchHard.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				startBenchmark("HARD");
+			}
+		});
+
+		benchSizes.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				startBenchmark("LSIZE");
+			}
+		});
+
+		benchHere.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				startBenchmark("CURRENT");
+			}
+		});
+
+		benchStop.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				stopAll();
+			}
+
+		});
+
+		randomDebug.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				String startingKey = main.selectedDeviceKey;
+				LOG.debug("randomDebug start");
+				int badTotalIterations = 0;
+				for (int n = 0; n < 100; n++) {
+					for (String key : main.getDeviceKeys()) {
+						deviceChange(key);
+						if (main.totalIterations < 1000) {
+							LOG.warn("!!!!!!!!!!!!!!!!  total iterations only : " + main.totalIterations);
+							badTotalIterations++;
+						}
+					}
+				}
+				deviceChange(startingKey);
+				LOG.debug("randomDebug end, bad total iterations : " + badTotalIterations);
+			}
+
+		});
+
+		imagePanel.addMouseWheelListener(new MouseWheelListener() {
+
+			/** POI mouse wheel zoom */
+			@Override
+			public void mouseWheelMoved(MouseWheelEvent e) {
+
+				e.consume();
+
+				if (!mouseWheelZooming) {
+
+					/** it will be reenabled at the end of gui refresh */
+					mouseWheelZooming = true;
+
+					int amount = (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) ? e.getUnitsToScroll()
+							: (e.getWheelRotation() < 0 ? -1 : 1);
+
+					LOG.debug(String.format("wheel %d", amount));
+
+					double zoom = 1f + ((double) amount * 8d / 100d);
+					stopAll();
+					main.move(main.W / 2, main.H / 2, zoom);
+
+				}
+			}
+		});
+
+		imagePanel.addMouseListener(new MouseAdapter() {
+			@Override
+			public void mouseClicked(MouseEvent e) {
+
+				if (e.getClickCount() == 2 && !e.isConsumed()) {
+					e.consume();
+					LOG.debug("Double Click");
+					stopAll();
+					main.move(e.getPoint().x, main.H - e.getPoint().y - 1, 1f);
+				}
+
+				if (e.getClickCount() == 1 && !e.isConsumed()) {
+					e.consume();
+					LOG.debug("Single Click");
+					stopAll();
+				}
+			}
+
+			@Override
+			public void mousePressed(MouseEvent e) {
+				LOG.debug(String.format("mouse pressed %d %d", e.getPoint().x, e.getPoint().y));
+				dragging_px = e.getPoint().x;
+				dragging_py = e.getPoint().y;
+				dragging = true;
+			}
+
+			@Override
+			public void mouseReleased(MouseEvent e) {
+
+				xOffset = 0;
+				yOffset = 0;
+
+				int nx = e.getPoint().x;
+				int ny = e.getPoint().y;
+
+				dragging_px = dragging_px - nx;
+				dragging_py = dragging_py - ny;
+
+				int newCenterX = main.W / 2 + dragging_px;
+				dragging_rx += (float) main.W % 2;
+				if (dragging_rx >= 2f) {
+					newCenterX += dragging_rx / 2;
+					dragging_rx -= dragging_rx;
+				}
+
+				int newCenterY = main.H / 2 - dragging_py;
+				dragging_ry += (float) main.H % 2;
+				if (dragging_ry >= 2f) {
+					newCenterY += dragging_ry / 2;
+					dragging_ry -= dragging_ry;
+				}
+
+				main.move(newCenterX, newCenterY, 1f);
+
+				dragging = false;
+			}
+
+		});
+
+		/** POI drag */
+		imagePanel.addMouseMotionListener(new MouseMotionAdapter() {
+
+			@Override
+			public void mouseDragged(MouseEvent e) {
+
+				if (dragging) {
+
+					int nx = e.getPoint().x;
+					int ny = e.getPoint().y;
+
+					xOffset = nx - dragging_px;
+					yOffset = ny - dragging_py;
+
+					imagePanel.update(imagePanel.getGraphics());
+
+					main.stopThread();
+
+					{
+						int dx = dragging_px - nx;
+						int dy = dragging_py - ny;
+
+						dragging_px = e.getPoint().x;
+						dragging_py = e.getPoint().y;
+
+						int newCenterX = main.W / 2 + dx;
+						dragging_rx += (float) main.W % 2;
+						if (dragging_rx >= 2f) {
+							newCenterX += dragging_rx / 2;
+							dragging_rx -= dragging_rx;
+						}
+
+						int newCenterY = main.H / 2 - dy;
+						dragging_ry += (float) main.H % 2;
+						if (dragging_ry >= 2f) {
+							newCenterY += dragging_ry / 2;
+							dragging_ry -= dragging_ry;
+						}
+
+						main.move(newCenterX, newCenterY, 1f);
+					}
+
+				}
+			}
+
+		});
+
+		/** disable dynamic resizing, called only when mouse released */
+		Toolkit.getDefaultToolkit().setDynamicLayout(false);
+
+		/** frame resize event */
+		frame.addComponentListener(new ComponentAdapter() {
+			public void componentResized(ComponentEvent e) {
+				if (guiEvents) {
+					resizeImage();
+				}
+			}
+		});
+
+	}
+	
+	
+	private void startBenchmark(String benchmarkMode){
+
+		if (main.benchmarkRunning) {
+			LOG.warn("Benchmark already running");
+			return;
+		}
+
+		stopAll();
+		main.benchmark(benchmarkMode);
+
+	}
+
+	/** zoom in or out based on an empirical formula */
+	private void zoom(int percentage) {
+		double tdx = 4d * Math.pow(10, (percentage * Math.log10(main.minXWidth / 4d) / 100d));
+		double mx = (main.cx1 + main.cx2) / 2d;
+		double my = (main.cy1 + main.cy2) / 2d;
+		double tx1 = mx - tdx / 2d;
+		double ty1 = my - main.dy * (tdx / main.dx) / 2d;
+		double tx2 = mx + tdx / 2d;
+		double ty2 = my + main.dy * (tdx / main.dx) / 2d;
+		main.threadGo(tx1, ty1, tx2, ty2, 0);
+	}
+
+	/** stops running threads and stop dragging */
+	private synchronized void stopAll() {
+		dragging = false;
+		xOffset = 0;
+		yOffset = 0;
+
+		main.stopBenchmark();
+
+		main.stopThread();
+
+		/** waits refresh stop */
+		while (refreshing) {
+			try {
+				Thread.sleep(1);
+			} catch (InterruptedException e) {
+				LOG.error(e.getMessage());
+			}
+		}
+	}
+
+	/**
+	 * after frame resize we have to instantiate a new image and reinitialize the
+	 * main
+	 */
+	public void resizeImage() {
+
+		stopAll();
+
+		int W = imagePanel.getWidth() + 1;
+		if (W < 100) {
+			W = 100;
+		}
+		int H = imagePanel.getHeight() + 1;
+		if (H < 100) {
+			H = 100;
+		}
+
+		image = new BufferedImage(W, H, BufferedImage.TYPE_INT_RGB);
+
+		main.init(main.selectedDeviceKey, W, H);
+
+		/**
+		 * move to the pixels center, same complex coordinates. 
+		 * refresh centering the image
+		 */
+		main.move(W / 2, H / 2, 1);
+	}
+
+	/** on device change initialize the main and refresh */
+	private void deviceChange(String newDeviceKey) {
+		stopAll();
+		main.init(newDeviceKey, main.W, main.H);
+		main.threadGo();
+	}
+
+	/**
+	 * after the main has completed calculations It calls the GUI refresh. We
+	 * refresh the image and all other input controls.
+	 */
+	public void refresh() {
+
+		if (refreshing) {
+			LOG.warn("already refreshing...");
+			return;
+		}
+
+		/** flag refreshing */
+		refreshing = true;
+
+		/** total iterations on all pixels, just to show the total number **/
+		main.totalIterations = 0;
+
+		/** image coordinates **/
+		int color;
+
+		/** transform iterations in a color and set the pixel on the image **/
+		for (int w = 0; w < main.W; w++)
+			for (int h = 0; h < main.H; h++) {
+
+				int iterations = main.iterations[w][h];
+
+				if (iterations >= lastMaxIterations) {
+					/** the Mandelbrot set is black */
+					color = RGB_BLACK;
+				} else {
+					/** color from the palette */
+					color = colors[(iterations + colorOffset) % MAX_COLORS];
+				}
+
+				image.setRGB(w, main.H - h - 1, color);
+
+				main.totalIterations += iterations;
+			}
+
+		if (main.totalIterations < 1000) {
+			LOG.warn("only " + main.totalIterations + " total iterations");
+		}
+
+		/** ready for a new image without drag offsets **/
+		xOffset = 0;
+		yOffset = 0;
+
+		/** instead of frame.repaint(); image.update works better on resizing */
+		imagePanel.update(imagePanel.getGraphics());
+
+		/** input controls refresh */
+		tfcx1.setText(String.format("%+2.16f", main.cx1));
+		tfcy1.setText(String.format("%+2.16f", main.cy1));
+		tfcx2.setText(String.format("%+2.16f", main.cx2));
+		tfcy2.setText(String.format("%+2.16f", main.cy2));
+		tfcdx.setText(String.format("%+2.16f", main.dx));
+		tfcdy.setText(String.format("%+2.16f", main.dy));
+
+		tfW.setText(String.format("%5d", main.W));
+		tfH.setText(String.format("%5d", main.H));
+		tfMaxIterations.setText(String.format("%,d", lastMaxIterations));
+
+		tfElapsed.setText("" + main.elapsed);
+		tfDeviceName.setText("" + main.getDeviceName());
+		tfTotalIterations.setText(String.format("%,d", main.totalIterations));
+
+		/** I assume -2,+2 as basic size, zoom=1 at home */
+		long zoom = (long) (4d / (main.dx));
+		tfZoom.setText(String.format("%,d", zoom));
+
+		/** zoom logarithmic scale for the scrollbar */
+		jsZoomDisable = true;
+		int zoomLog = (int) (100d * Math.log10(main.dx / 4d) / Math.log10(main.minXWidth / 4d));
+		jsZoom.setValue(zoomLog);
+		jsZoomDisable = false;
+
+		refreshing = false;
+
+		/** re-enable mouse wheel zooming */
+		mouseWheelZooming = false;
+
+		/** re-enable device change */
+		if (!deviceComboBox.isEnabled()) {
+			deviceComboBox.setEnabled(true);
+		}
+
+	}
+
+	public void deviceLedOn() {
+		lblDeviceLed.setText("ON");
+		lblDeviceLed.setForeground(Color.RED);
+	}
+
+	public void deviceLedOff() {
+		lblDeviceLed.setText("");
+		lblDeviceLed.setForeground(Color.GRAY);
+	}
+
+	public void benchmarkLedOn() {
+		lblBenchmarkLed.setText("ON");
+		lblBenchmarkLed.setForeground(Color.RED);
+	}
+
+	public void benchmarkLedOff() {
+		lblBenchmarkLed.setText("");
+		lblBenchmarkLed.setForeground(Color.GRAY);
+	}
+}
diff --git a/src/main/java/com/aparapi/examples/afmandelbrot/AfKernel.java b/src/main/java/com/aparapi/examples/afmandelbrot/AfKernel.java
new file mode 100644
index 0000000000000000000000000000000000000000..9680f51bd85466501736a67c53001c33a58ce2d8
--- /dev/null
+++ b/src/main/java/com/aparapi/examples/afmandelbrot/AfKernel.java
@@ -0,0 +1,107 @@
+package com.aparapi.examples.afmandelbrot;
+
+import com.aparapi.Kernel;
+
+/**
+ * Aparapi Fractals
+ * 
+ * the kernel executes the math with complex numbers. Coordinates refer to
+ * complex plane. result is a vector of number of iterations, It is transformed
+ * in a color in the GUI, not here
+ * 
+ * @author marco.stefanetti at gmail.com
+ * 
+ */
+public class AfKernel extends Kernel {
+
+	/** the result of all calculations, the iterations for each pixel */
+	private int[][] result;
+
+	/** max iterations for pixel */
+	private int max_iterations;
+
+	/** starting point, x lower-left */
+	private double cx1;
+	/** starting point, y lower-left */
+	private double cy1;
+
+	/** max width */
+	private int wmax;
+	/** max height */
+	private int hmax;
+
+	/** one pixel width */
+	private double wx;
+	/** one pixel height */
+	private double hy;
+
+	/** no values on the constructor, we will reuse the kernel after init */
+	public AfKernel() {
+		super();
+	}
+
+	/**
+	 * sets the parameters, send only few double to the device and a pointer to an
+	 * array to retrieve iterations
+	 */
+	public void init(double _cx1, double _cy1, double _cx2, double _cy2, int _W, int _H, int _max_iterations) {
+
+		wmax = _W;
+		hmax = _H;
+		result = new int[_W][_H];
+		max_iterations = _max_iterations;
+		cx1 = _cx1;
+		cy1 = _cy1;
+
+		wx = (_cx2 - cx1) / (double) wmax;
+		hy = (_cy2 - cy1) / (double) hmax;
+
+	}
+
+	/**
+	 * just executes the "simple" math on a pixel
+	 */
+	@Override
+	public void run() {
+
+		final int w = getGlobalId(0);
+		final int h = getGlobalId(1);
+
+		if ((w < wmax) && (h < hmax)) {
+
+			/** from pixel to complex coordinates */
+			final double cx = cx1 + w * wx;
+			final double cy = cy1 + h * hy;
+
+			double xn = cx;
+			double yn = cy;
+
+			double y2 = cy * cy;
+			/** I don't save x2, x squared, It's slower **/
+
+			int t = 0;
+
+			/**
+			 * the original code gave a "goto" error in some platform while(
+			 * (++t<max_iterations) && (xn*xn+y2<4) )
+			 */
+
+			for (t = 0; (t < max_iterations) && (xn * xn + y2 < 4); t++) {
+				yn = 2d * xn * yn + cy;
+				xn = xn * xn - y2 + cx;
+				y2 = yn * yn;
+			}
+
+			result[w][h] = t;
+
+		}
+
+	}
+
+	public int[][] getResult() {
+		return result;
+	}
+	
+	
+
+}
diff --git a/src/main/java/com/aparapi/examples/afmandelbrot/AfMain.java b/src/main/java/com/aparapi/examples/afmandelbrot/AfMain.java
new file mode 100644
index 0000000000000000000000000000000000000000..c61b8523874c2eb53660042d0f9efa5f119a1eaa
--- /dev/null
+++ b/src/main/java/com/aparapi/examples/afmandelbrot/AfMain.java
@@ -0,0 +1,534 @@
+package com.aparapi.examples.afmandelbrot;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Aparapi Fractals
+ *  
+ * The main class coordinates the GUI and Aparapi's executions. Complex plane
+ * coordinates and iterations are saved here. If you are interested in Aparapi
+ * code, just check AfAparapiUtils and AfKernel.
+ * 
+ * @author marco.stefanetti at gmail.com
+ * 
+ */
+public class AfMain {
+
+	/** logger */
+	private static final Logger LOG = Logger.getLogger(AfMain.class);
+
+	/** the GUI contains only swing controls and events handling */
+	private AfGUI gui;
+
+	/** devices list and kernel execution */
+	private AfAparapiUtils afAparapiUtils;
+
+	/** the key of the current selected device */
+	protected String selectedDeviceKey = null;
+
+	/** image width */
+	protected int W = 500;
+
+	/** image height */
+	protected int H = 500;
+
+	/** minimal iterations */
+	final protected int __MIN_ITERATIONS = 512;
+
+	/** max iterations per image pixel, It will vary while zooming */
+	private int maxIterations = 512;
+
+	/** the result of the iterations, It is transformed in colors in the GUI */
+	protected int[][] iterations;
+
+	/** lower-left in the complex plane */
+	protected double cx1 = -2;
+	protected double cy1 = -2;
+
+	/** top-right in the complex plane */
+	protected double cx2 = +2;
+	protected double cy2 = +2;
+
+	/** width and height in the complex plane */
+	protected double dx = +4;
+	protected double dy = +4;
+
+	/** minimum width in the complex plane using double */
+	protected double minXWidth = 0.000000000001d;
+
+	/** just to show total iterations for each image */
+	protected long totalIterations;
+
+	/** elapsed time for last image calculation */
+	protected long elapsed;
+
+	/** the thread is used to separate the GUI and the kernel */
+	protected GoThread goThread = null;
+
+	/** profile startup of main and GUI */
+	private long profilerLastTimeMillis = System.currentTimeMillis();
+
+	/** flag for benchmark running */
+	boolean benchmarkRunning = false;
+
+	/** separate thread for benchmarks */
+	private Thread benchmarkThread;
+
+	/**
+	 * the thread is used to separate calculations and keep the gui responsive to
+	 * other events
+	 */
+	class GoThread extends Thread {
+
+		private volatile boolean running = true;
+
+		/** destination coordinates */
+		private double tx1 = -2;
+		private double ty1 = -2;
+		private double tx2 = +2;
+		private double ty2 = +2;
+
+		/** steps to go from current coordinates to destination */
+		private double steps = 0;
+
+		public GoThread(double _tx1, double _ty1, double _tx2, double _ty2, double _steps) {
+			tx1 = _tx1;
+			ty1 = _ty1;
+			tx2 = _tx2;
+			ty2 = _ty2;
+			steps = _steps;
+		}
+
+		public void interrupt() {
+			running = false;
+		}
+
+		public boolean isRunning() {
+			return running;
+		}
+
+		public void run() {
+
+			if (steps == 0) {
+				go(tx1, ty1, tx2, ty2);
+				running = false;
+				return;
+			}
+
+			// start center
+			double sox = (cx2 + cx1) / 2d;
+			double soy = (cy2 + cy1) / 2d;
+
+			// initial with and height
+			double dsx = (cx2 - cx1);
+			double dsy = (cy2 - cy1);
+
+			// target center
+			double tox = (tx2 + tx1) / 2d;
+			double toy = (ty2 + ty1) / 2d;
+
+			// target with and height
+			double dtx = (tx2 - tx1);
+			double dty = (ty2 - ty1);
+
+			/**
+			 * d(0)=dsx ... d(n+1)=d(n)*f=d(0)*f^n ... d(N)=dtx=d(0)*f^N=dsx*f^N
+			 * 
+			 * f^N=dtx/dsx f=(dtx/dsx)^(1/n)
+			 */
+			double fx = Math.pow(dtx / dsx, 1 / (steps - 1));
+			double fy = Math.pow(dty / dsy, 1 / (steps - 1));
+
+			/** f^n */
+			double fxn = 1;
+			double fyn = 1;
+
+			/** new coordinates */
+			double nx1;
+			double ny1;
+			double nx2;
+			double ny2;
+
+			double ndx;
+			double ndy;
+
+			double mx;
+			double my;
+
+			double nox;
+			double noy;
+
+			/** sleep if the refresh is less than these ms */
+			long min_sleep = 30;
+
+			for (double s = 0; s < steps; s++) {
+
+				// used externally to stop the thread
+				if (!running) {
+					return;
+				}
+
+				/** activation function, to move fast but smootly on new center **/
+				mx = 2d * (1d / (1 + Math.exp(-(35d * s) / steps)) - 0.5d);
+				my = mx;
+
+				nox = sox + (tox - sox) * mx;
+				noy = soy + (toy - soy) * my;
+
+				ndx = dsx * fxn;
+				ndy = dsy * fyn;
+
+				nx1 = nox - ndx / 2d;
+				ny1 = noy - ndy / 2d;
+				nx2 = nox + ndx / 2d;
+				ny2 = noy + ndy / 2d;
+
+				long t = go(nx1, ny1, nx2, ny2);
+
+				fxn *= fx;
+				fyn *= fy;
+
+				/** sleep if the refresh was too fast **/
+				if (t < min_sleep)
+					try {
+						LOG.warn(String.format("sleeping %d ms ... ", min_sleep - t));
+						Thread.sleep(min_sleep - t);
+					} catch (InterruptedException e) {
+					}
+
+			}
+
+			if (!running) {
+				return;
+			}
+
+			go(tx1, ty1, tx2, ty2);
+
+			running = false;
+		}
+
+	}
+
+	/** constructor */
+	public AfMain() {
+
+		profiler("MAIN start");
+
+		afAparapiUtils = new AfAparapiUtils();
+		selectedDeviceKey = afAparapiUtils.getBestDeviceKey();
+		profiler("MAIN aparapi init");
+
+	}
+
+	/** initialize based on saved values */
+	private void init() {
+		init(selectedDeviceKey, W, H);
+	}
+
+	/**
+	 * initialize local iterations array and aparapiUtils
+	 * 
+	 * @param selectedDeviceKey new device key
+	 * @param _W                new image width
+	 * @param _H                new image height
+	 */
+	public synchronized void init(String _selectedDeviceKey, int _W, int _H) {
+
+		selectedDeviceKey = _selectedDeviceKey;
+		W = _W;
+		H = _H;
+
+		afAparapiUtils.init(selectedDeviceKey, W, H);
+
+		LOG.debug(String.format("canvas : %dx%d - max_iterations : %,d - LocalSizes %s ", W, H, maxIterations,
+				afAparapiUtils.getLocalSizes()));
+
+		LOG.debug(String.format("DeviceKey : %s - Device : %s ", selectedDeviceKey, afAparapiUtils.getDeviceName()));
+
+	}
+
+	/**
+	 * go to new coordinates using x,y pixel as new center and a zoom factor.
+	 * 
+	 * @param x          new central pixel x
+	 * @param y          new central pixel y
+	 * @param zoomFactor zoomFactor is relative to dimensions in complex plane
+	 */
+	public void move(int x, int y, double zoomFactor) {
+
+		double nx1 = cx1 + x * (cx2 - cx1) / W;
+		double ny1 = cy1 + y * (cy2 - cy1) / H;
+
+		double cw = zoomFactor * (cx2 - cx1);
+		double ch = zoomFactor * (cy2 - cy1);
+
+		/** stop when too small and zooming in **/
+		if ((cw < minXWidth) && (zoomFactor < 1d)) {
+			LOG.warn(String.format("!!! Zoom limit !!! x range : %2.20f", cw));
+			gui.mouseWheelZooming = false;
+			return;
+		}
+
+		/** too big or too far, the set is between -2 and 2 **/
+		if (((cw > 10) && (zoomFactor > 1d)) || (nx1 > 5) || (nx1 < -5) || (ny1 > 5) || (ny1 < -5)) {
+			LOG.warn(String.format("too big or too far, " + zoomFactor));
+			gui.mouseWheelZooming = false;
+			return;
+		}
+
+		threadGo(nx1 - 0.5f * cw, ny1 - 0.5f * ch, nx1 + 0.5f * cw, ny1 + 0.5f * ch, 0);
+
+	}
+
+	/**
+	 * go to new complex coordinates
+	 * 
+	 * @param tx1 target lower-left x
+	 * @param ty1 target lower-left y
+	 * @param tx2 target up-right x
+	 * @param ty2 target up-right y
+	 * @return elapsed refresh time
+	 */
+	private long go(double tx1, double ty1, double tx2, double ty2) {
+
+		dx = tx2 - tx1;
+
+		/**
+		 * we do not use ty2-ty1 as dy, to keep always proportional we use a calculated
+		 * dy based on dx,H,W
+		 **/
+		dy = dx * H / W;
+
+		cx1 = tx1;
+		cy1 = (ty2 + ty1) / 2d - 0.5d * dy;
+		cx2 = tx2;
+		cy2 = (ty2 + ty1) / 2d + 0.5d * dy;
+
+		dy = cy2 - cy1;
+
+		/** while zooming increase iterations, empirical formula **/
+		maxIterations = (int) (__MIN_ITERATIONS * (1d + 0.5d * Math.log(1 / (cx2 - cx1))));
+
+		if (maxIterations < __MIN_ITERATIONS) {
+			maxIterations = __MIN_ITERATIONS;
+		}
+
+		return refresh();
+	}
+
+	/**
+	 * call the aparapi execution and then refresh the gui
+	 * 
+	 * @return calculations elapsed time, not considering gui refreshing time
+	 */
+	private synchronized long refresh() {
+
+		if (benchmarkRunning) {
+			LOG.warn("Benchmark in progress...");
+			return 0;
+		}
+
+		gui.deviceLedOn();
+		gui.lastMaxIterations = maxIterations;
+
+		elapsed = afAparapiUtils.execute(cx1, cy1, cx2, cy2, W + 1, H + 1, maxIterations);
+		iterations = afAparapiUtils.getResult();
+
+		/*
+		 * long totalIterations=0; for(int i=0;i<W;i++) { for(int j=0;j<H;j++) {
+		 * totalIterations+=iterations[i][j]; } }
+		 * 
+		 * if(totalIterations<1000) { LOG.fatal("totalIterations:"+totalIterations+
+		 * " very small aborting"); System.exit(1); }
+		 */
+
+		LOG.debug(String.format("%2.16fd,%2.16fd %2.16fd,%2.16fd MaxIterations : %10d - Elapsed : %d ms", cx1, cy1, cx2,
+				cy2, maxIterations, elapsed));
+
+		gui.deviceLedOff();
+		gui.refresh();
+
+		return elapsed;
+	}
+
+	public void threadGoHome(int steps) {
+		threadGo(-2d, -2d, 2d, 2d, steps);
+	}
+
+	public void goHome() {
+		stopThread();
+		go(-2d, -2d, 2d, 2d);
+	}
+
+	public void stopThread() {
+
+		if (goThread == null) {
+			return;
+		}
+
+		goThread.interrupt();
+		try {
+			goThread.join();
+		} catch (InterruptedException e) {
+			LOG.error("error joining threadGO");
+		}
+
+	}
+
+	public void threadGo() {
+		threadGo(cx1, cy1, cx2, cy2, 0);
+	}
+
+	public void threadGo(double tx1, double ty1, double tx2, double ty2, double steps) {
+
+		stopThread();
+		goThread = new GoThread(tx1, ty1, tx2, ty2, steps);
+		goThread.start();
+
+	}
+
+	/** starts a benchmark in a separate thread */
+	public void benchmark(final String benchmarkMode) {
+
+		if (benchmarkRunning) {
+			LOG.warn("Benchmark akready running");
+			return;
+		}
+
+		benchmarkThread = new Thread() {
+			public void run() {
+
+				benchmarkRunning = true;
+
+				gui.benchmarkLedOn();
+
+				if ("CURRENT".equals(benchmarkMode)) {
+					/** executes the benchmark using current coordinates and max iterations */
+					AfBenchmark.benchmark(afAparapiUtils, false, "CurrentRegion", cx1, cy1, cx2, cy2, W, H, maxIterations,
+							"ALL", 100);
+				} else {
+
+					AfBenchmark.benchmark(afAparapiUtils,benchmarkMode);
+
+				}
+				
+
+				init();
+				gui.benchmarkLedOff();
+
+				benchmarkRunning = false;
+			}
+
+		};
+
+		benchmarkThread.start();
+
+	}
+
+	/** stops the benchmark thread */
+	public void stopBenchmark() {
+
+		if (!benchmarkRunning) {
+			return;
+		}
+
+		AfBenchmark.requestStop();
+
+		try {
+			benchmarkThread.join();
+		} catch (InterruptedException e) {
+		}
+
+	}
+
+	/**
+	 * used by the GUI, the GUI has no direct access to the aparapi stuffs
+	 * 
+	 * @return the list of devices from aparapiUtils
+	 */
+	public String[] getDeviceKeys() {
+		return afAparapiUtils.getDeviceKeys();
+	}
+
+	/**
+	 * used by the GUI to show the name of the device. It's different from the
+	 * combobox (selectedDeviceKey), here you get the real name of the device, e.g.
+	 * "NVidia 1650 SUPER""
+	 * 
+	 * @return the name of the current device
+	 */
+	public String getDeviceName() {
+		return afAparapiUtils.getDeviceName();
+	}
+
+	/** used to profile main and gui startup */
+	protected void profiler(String message) {
+		long ms = System.currentTimeMillis() - profilerLastTimeMillis;
+		LOG.debug(String.format("profiler - %-20s : %-10d ms", message, ms));
+		profilerLastTimeMillis = System.currentTimeMillis();
+	}
+
+	/** gui creation executed in the swing thread */
+	protected void createAndShowGUI() {
+
+		// swing load
+		java.awt.Window window = new java.awt.Window(null);
+		window.dispose();
+		profiler("GUI swing load");
+
+		// create the GUI
+		gui = new AfGUI(this);
+		profiler("GUI costructor");
+
+	}
+
+	public static void main(String[] args) {
+
+		System.setProperty("com.aparapi.enableShowGeneratedOpenCL", "true");
+		System.setProperty("com.aparapi.dumpProfilesOnExit", "true");
+
+		LOG.info("AparapiFractals");
+		LOG.info("double-click : recenter");
+		LOG.info("click        : stop zoom");
+		LOG.info("mouse wheel  : zoom");
+		LOG.info("mouse drag   : move");
+
+		final AfMain main = new AfMain();
+
+		// creating and showing this application's GUI
+		javax.swing.SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				main.createAndShowGUI();
+			}
+		});
+
+		// waits the GUI
+		while ((main.gui == null) || (main.gui.frame == null) || (!main.gui.frame.isValid())) {
+			try {
+				Thread.sleep(10);
+			} catch (InterruptedException e) {
+			}
+		}
+		main.profiler("GUI valid");
+
+		// gui validation
+		main.gui.frame.validate();
+		main.profiler("GUI validate");
+
+		// image refresh
+		main.gui.resizeImage();
+		main.profiler("GUI refresh");
+
+		// hope to ignore and flush starting events
+		try {
+			Thread.sleep(50);
+		} catch (InterruptedException e) {
+		}
+
+		// enables real handling of gui events
+		main.gui.guiEvents = true;
+		main.profiler("GUI events");
+
+	}
+
+}