include config.make
-java_MODULES=notzed.zcl notzed.zcl.demo
+java_MODULES=notzed.zcl notzed.zcl.demo notzed.zcl.demo.fx
notzed.zcl.demo_JDEPMOD=notzed.zcl
+notzed.zcl.demo.fx_JDEPMOD=notzed.zcl
include java.make
+
+mandelbrot: all
+ LD_LIBRARY_PATH=../nativez/bin/notzed.nativez/linux-amd64/lib:bin/notzed.zcl/linux-amd64/lib \
+ $(JAVA) --module-path bin/modules:../nativez/bin/modules:$(JAVAFX_HOME)/lib \
+ -m notzed.zcl.demo.fx/fxdemo.fract.Mandelbrot $(ARGS)
+
+clinfo: all
+ LD_LIBRARY_PATH=../nativez/bin/notzed.nativez/linux-amd64/lib:bin/notzed.zcl/linux-amd64/lib \
+ strace -e open -o log $(JAVA) --module-path bin/modules:../nativez/bin/modules \
+ -m notzed.zcl.demo/au.notzed.zcl.tools.clinfo $(ARGS)
--- /dev/null
+/*
+ * Copyright (C) 2020 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package fxdemo.fract;
+
+import static au.notzed.zcl.CL.*;
+import au.notzed.zcl.CLBuffer;
+import au.notzed.zcl.CLCommandQueue;
+import au.notzed.zcl.CLContext;
+import au.notzed.zcl.CLDevice;
+import au.notzed.zcl.CLException;
+import au.notzed.zcl.CLKernel;
+import au.notzed.zcl.CLPlatform;
+import au.notzed.zcl.CLProgram;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import java.nio.ByteBuffer;
+
+public class Calculate {
+
+ CLDevice[] devs;
+ CLContext cl;
+ CLCommandQueue q;
+ CLBuffer image;
+ CLProgram prog;
+ int width, height;
+ CLKernel mandelbrot;
+ CLKernel dmandelbrot;
+
+ Calculate(int width, int height) throws CLException {
+ devs = new CLDevice[]{CLPlatform.getBestDevice(CL_DEVICE_TYPE_ALL)};
+ cl = CLContext.createContext(null, devs);
+ q = cl.createCommandQueue(devs[0], 0);
+
+ this.width = width;
+ this.height = height;
+
+ image = cl.createBuffer(0, width * height * 4);
+
+ prog = cl.createProgramWithSource(getClass().getResourceAsStream("mandelbrot.cl"));
+ prog.buildProgram(devs, null);
+
+ mandelbrot = prog.createKernel("mandelbrot");
+ mandelbrot.setArg(0, image);
+ mandelbrot.setArg(1, width, height);
+
+ dmandelbrot = prog.createKernel("dmandelbrot");
+ dmandelbrot.setArg(0, image);
+ dmandelbrot.setArg(1, width, height);
+ }
+
+ public void run(ByteBuffer buffer, double ox, double oy, double sx, double sy, double arg, int m) {
+ try {
+ mandelbrot.setArg(2, ox, oy);
+ mandelbrot.setArg(3, sx, sy);
+ mandelbrot.setArg(4, sin(arg), cos(arg));
+ mandelbrot.setArg(5, m);
+ System.out.println("enqueue");
+ q.enqueue2DKernel(mandelbrot, 0, 0, width, height, 8, 8, null, null);
+ q.enqueueReadBuffer(image, true, 0, buffer.limit(), buffer, null, null);
+ System.out.println("finish");
+ q.finish();
+ } catch (CLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void rund(ByteBuffer buffer, double ox, double oy, double sx, double sy, double arg, int m) {
+ try {
+ dmandelbrot.setArg(2, ox, oy);
+ dmandelbrot.setArg(3, sx, sy);
+ dmandelbrot.setArg(4, sin(arg), cos(arg));
+ dmandelbrot.setArg(5, m);
+
+ System.out.println("enqueue");
+ q.enqueue2DKernel(dmandelbrot, 0, 0, width, height, 8, 8, null, null);
+ q.enqueueReadBuffer(image, true, 0, buffer.limit(), buffer, null, null);
+ System.out.println("finish");
+ q.finish();
+ } catch (CLException ex) {
+ ex.printStackTrace();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2020 Michael Zucchi
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package fxdemo.fract;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.animation.Transition;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.scene.Scene;
+import javafx.scene.image.ImageView;
+import javafx.scene.image.PixelFormat;
+import javafx.scene.image.WritableImage;
+import javafx.scene.input.KeyCombination;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+import java.nio.ByteBuffer;
+import au.notzed.zcl.CLMemory;
+
+/**
+ * Simple mandelbrot demo.
+ */
+public class Mandelbrot extends Application {
+
+ int width = 1920 / 2;
+ int height = 1200 / 2;
+ Calculate calc;
+ WritableImage image;
+ ByteBuffer buffer;
+ ExecutorService queue = Executors.newSingleThreadExecutor((r) -> {
+ Thread t = new Thread(r);
+ t.setDaemon(true);
+ return t;
+ });
+ float scale = 0.001f;
+ //double cx = -0.235125, cy = 0.827215;
+ //double cx = -0.74529, cy = 0.11307;
+ // double cx = -0.761574, cy =-0.0847596;
+ double cx = -0.7746806106269039, cy = -0.1374168856037867;
+
+ long nframes;
+ long lframes;
+ boolean goin = true;
+ double vscale = 0.01;
+
+ @Override
+ public void start(Stage stage) throws Exception {
+
+ calc = new Calculate(width, height);
+ image = new WritableImage(width, height);
+
+ ImageView iv = new ImageView(image);
+ Text fps = new Text("FPS?");
+ fps.setStyle("-fx-font: 24 monospace; -fx-fill: orange; -fx-effect: dropshadow(two-pass-box, orangered, 6, 0.5, 0, 0);");
+
+ Scene scene = new Scene(new AnchorPane(iv, fps));
+
+ scene.getAccelerators().put(KeyCombination.valueOf("ESC"), stage::close);
+
+ // stage.setFullScreen(true);
+ AnchorPane.setLeftAnchor(fps, 24.0);
+ AnchorPane.setTopAnchor(fps, 24.0);
+
+ stage.setScene(scene);
+ stage.show();
+
+ buffer = CLMemory.alloc(width * height * 4);
+
+ Timeline tofps = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
+ fps.setText(String.valueOf((nframes - lframes)));
+ lframes = nframes;
+ }));
+ tofps.setCycleCount(Timeline.INDEFINITE);
+ tofps.play();
+
+ Transition zoom = new Transition(60) {
+ {
+ setCycleDuration(Duration.seconds(15));
+ }
+
+ @Override
+ protected void interpolate(double frac) {
+ //double scale = frac * (0.001 - 0.000001) + 0.000001;
+ if (true) {
+ double scale = 0.005 / Math.exp(frac * 30);
+ recalcd(scale, (int) (-Math.log(scale) * 80));
+ } else {
+ double scale = 0.005 / Math.exp(frac * 12);
+ recalc(scale, (int) (-Math.log(scale) * 160));
+ }
+
+ //System.out.printf("scale %e iter %f\n", scale, -Math.log(scale) * 100);
+ //recalc((float) scale);
+ //recalc(scale);
+ //recalc(vscale);
+ //vscale *= 0.95;
+ }
+ };
+ zoom.setInterpolator(Interpolator.EASE_BOTH);
+ zoom.setAutoReverse(true);
+ zoom.setCycleCount(Timeline.INDEFINITE);
+ zoom.play();
+
+ //recalc();
+ }
+
+ boolean busy;
+ double arg = 0;
+
+ void recalcd(double scale, int m) {
+ arg -= 0.05;
+ if (true) {
+ if (busy)
+ return;
+ busy = true;
+ //System.out.println(scale);
+ queue.submit(() -> {
+ double x = cx - width * scale * 0.5;
+ double y = cy - height * scale * 0.5;
+
+ calc.rund(buffer, x, y, scale, scale, arg, m);
+ Platform.runLater(() -> {
+ nframes++;
+ image.getPixelWriter()
+ .setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), buffer, width * 4);
+ busy = false;
+ });
+ });
+ } else {
+ double x = cx - width * scale * 0.5;
+ double y = cy - height * scale * 0.5;
+
+ calc.rund(buffer, x, y, scale, scale, arg, m);
+ nframes++;
+ image.getPixelWriter()
+ .setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), buffer, width * 4);
+ }
+ }
+
+ void recalc(double scale, int m) {
+ arg += 0.05;
+ if (true) {
+ if (busy)
+ return;
+ busy = true;
+ queue.submit(() -> {
+ double x = cx - width * scale * 0.5;
+ double y = cy - height * scale * 0.5;
+
+ calc.run(buffer, x, y, scale, scale, arg, m);
+
+ Platform.runLater(() -> {
+ nframes++;
+ image.getPixelWriter()
+ .setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), buffer, width * 4);
+ busy = false;
+ });
+ });
+ } else {
+ double x = cx - width * scale * 0.5;
+ double y = cy - height * scale * 0.5;
+
+ calc.run(buffer, x, y, scale, scale, arg, m);
+ nframes++;
+ image.getPixelWriter()
+ .setPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), buffer, width * 4);
+ }
+
+ }
+
+}
--- /dev/null
+
+#pragma OPENCL EXTENSION cl_khr_fp64 : enable
+
+#define LWS_X 8
+#define LWS_Y 8
+
+kernel void
+__attribute__((reqd_work_group_size(LWS_X, LWS_Y, 1)))
+mandelbrot(global uchar4 *image, int2 size, double2 origin, double2 scale, double2 sincos, int m) {
+ int dx = get_global_id(0);
+ int dy = get_global_id(1);
+
+ if (dx < size.x && dy < size.y) {
+#if 0
+ float2 c = (float2)(dx, dy) * scale + origin;
+#else
+ double2 s = convert_double2(size);
+ double2 v;
+
+ v = (double2)(dx, dy) - s * 0.5;
+ v = (double2)(v.x * sincos.x + v.y * sincos.y,
+ v.x * sincos.y - v.y * sincos.x);
+ v += s * 0.5;
+
+ v *= scale;
+ v += origin;
+
+ float2 c = convert_float2(v);
+#endif
+ int n = 0;
+ float2 z = 0;
+
+ do {
+ z = (float2)(z.x * z.x - z.y * z.y, 2.0f * z.x * z.y) + c;
+ n++;
+ } while (dot(z, z) < 4 && n < m);
+
+ // we use a simple cosine palette to determine color:
+ // http://iquilezles.org/www/articles/palettes/palettes.htm
+ float t = n * 10.0f / m;
+ float3 d = (float3)(0.5f, 0.5f, 0.5f);
+ float3 e = (float3)(0.5f, 0.5f, 0.5f);
+ float3 f = (float3)(1.0f, 1.0f, 1.0f);
+ float3 g = (float3)(0.00f, 0.33f, 0.67f);
+ float4 colour = (float4)( d + e * cos(6.28318f * (f * t + g)), 1.0f);
+
+ if (convert_int(n) == m)
+ colour = (float4)(0, 0, 0, 1);
+
+ // store the rendered mandelbrot set into a storage buffer:
+ image[dx + dy * size.x] = convert_uchar4(colour * 255.0f);
+ }
+}
+
+// rot-zoomer
+kernel void
+__attribute__((reqd_work_group_size(LWS_X, LWS_Y, 1)))
+dmandelbrot(global uchar4 *image, int2 size, double2 origin, double2 scale, double2 sincos, int m) {
+ int dx = get_global_id(0);
+ int dy = get_global_id(1);
+
+ if (dx < size.x && dy < size.y) {
+ double2 s = convert_double2(size);
+ double2 c;
+
+ c = (double2)(dx, dy) - s * 0.5;
+ c = (double2)(c.x * sincos.x + c.y * sincos.y,
+ c.x * sincos.y - c.y * sincos.x);
+ c += s * 0.5;
+
+ c *= scale;
+ c += origin;
+
+ int n = 0;
+ double2 z = 0;
+
+ do {
+ z = (double2)(z.x * z.x - z.y * z.y, 2.0f * z.x * z.y) + c;
+ n++;
+ } while (dot(z, z) < 4 && n < m);
+
+ // we use a simple cosine palette to determine color:
+ // http://iquilezles.org/www/articles/palettes/palettes.htm
+ float t = n * 10.0f / m;
+ float3 d = (float3)(0.5f, 0.5f, 0.5f);
+ float3 e = (float3)(0.5f, 0.5f, 0.5f);
+ float3 f = (float3)(1.0f, 1.0f, 1.0f);
+ float3 g = (float3)(0.00f, 0.33f, 0.67f);
+ float4 colour = (float4)( d + e * cos(6.28318f * (f * t + g)), 1.0f);
+
+ if (convert_int(n) == m)
+ colour = (float4)(0, 0, 0, 1);
+
+ // store the rendered mandelbrot set into a storage buffer:
+ image[dx + dy * size.x] = convert_uchar4(colour * 255.0f);
+ }
+}
--- /dev/null
+
+/**
+ * OpenCL demos that use JavaFX.
+ */
+module notzed.zcl.demo.fx {
+ requires javafx.base;
+ requires javafx.controls;
+ requires javafx.graphics;
+ requires javafx.swing;
+
+ requires notzed.zcl;
+
+ exports fxdemo.fract to javafx.graphics;
+
+}