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"); + + } + +}