# Compute Shaders in Processing

**URL:** https://discourse.processing.org/t/compute-shaders-in-processing/21400
**Category:** Development
**Created:** [May 29, 2020, 1:19pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400 "2020-05-29T13:19:44Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)
#### Post date: [May 29, 2020, 1:19pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/1 "2020-05-29T13:19:44Z")

</div>

I’m working on a gradient library for Processing and I am exploring the use of colour spaces other than RGB to use as the basis to interpolate between different colors (to generate the gradient).

One of these alternative colour spaces is LAB, which generally provides much [better](https://www.alanzucconi.com/2016/01/06/colour-interpolation/4/) gradient results than interpolating between the R,G,B components of two RGB colours.

The downside is that one must convert the colours in the gradient from RGB to LAB, interpolate using the LAB components to produce a new LAB colour, and then convert that back into RGB (to give to Processing to draw).

The interpolation is fairly inexpensive, but the _RGB–\>LAB_ and _LAB–\>RGB_ conversion is very expensive. In testing, using the LAB colorspace (with these additional 2 conversions for every pixel)  
is about 15x slower than working in RGB only.

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/9/96f1a09ccbcc6d4dcc3fdc0bc0d9142a9f288745.png)

Therefore, I’ve thought about translating the _RGB\<–\>LAB_ Java methods into a glsl compute shader to get the GPU to do the heavy lifting. These functions are maths-only which I believe can be ported into glsl quite well. However, I don’t have experience writing a computer shader, nor using one in Processing.

So, I have two questions:

- Has anyone used compute shaders in Processing?
- Where does one begin translating a maths-heavy Java method into a compute shader?

---

<div class="post-metadata">

### Author: ![cansik](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/cansik/32/8815_2.png) [@cansik](https://discourse.processing.org/u/cansik)
#### Post date: [May 29, 2020, 7:08pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/2 "2020-05-29T19:08:25Z")

</div>

I do not think that you need compute shaders here because a simple fragment shader would do it for generating gradients. I have added you a simple example which currently uses `RGB` values to calculate the gradient. I leave it up to you to extend it with the lab conversion. But as a hint, check out this shadertoy example: [https://www.shadertoy.com/view/wt23Rt](https://www.shadertoy.com/view/wt23Rt)

```auto
PShader gradientShader;
PGraphics canvas;

void setup() {
  size(500, 500, P2D);

  canvas = createGraphics(400, 400, P2D);

  gradientShader = createShader();
}

void draw() {
  background(55);

  gradientShader.set("resolution", canvas.width, canvas.height);
  gradientShader.set("startColor", 1.0, 0, 0); // red
  gradientShader.set("endColor", 0, 0, 1.0); // blue
  canvas.filter(gradientShader);

  imageMode(CENTER);
  image(canvas, width / 2, height / 2);
}

public PShader createShader() {
  String[] vert = new String[] {
    "#version 150", 

    "uniform mat4 transform;", 

    "in vec4 position;", 
    "in vec4 color;", 

    "out vec4 vertColor;", 

    "void main() {", 
    " gl_Position = transform * position;", 
    " vertColor = color;", 
    "}"
  };

  String[] frag = new String[] {
    "#version 150",

    "in vec4 vertColor;", 
    "out vec4 fragColor;", 
    
    "uniform vec2 resolution;",
    
    "uniform vec3 startColor;",
    "uniform vec3 endColor;",

    "void main() {",
    " vec2 coordinates = gl_FragCoord.xy / resolution;",
    " vec3 value = mix(startColor, endColor, coordinates.x);",
    " fragColor = vec4(value.xyz, 1.0);",
    "}"
  };

  return new PShader(this, vert, frag);
}

```

Preview

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/c/ccfc75a47e56d8f00754a1eba6e15e24dd1ce06d.png)

---

<div class="post-metadata">

### Author: ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)
#### Post date: [May 29, 2020, 10:07pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/3 "2020-05-29T22:07:34Z")

</div>

Thanks for the reply.

It seems that this technique would tie the main sketch to OpenGL mode since the `PShader()` constructor needs an OpenGL PApplet, and not simply an OpenGL PGraphics object. With a library, this is what I was trying to avoid.

Is there a way to get around this: using PShaders without the need for the **parent** sketch to be in OpenGL mode? Alternatively, circumventing PShaders and going via JOGL directly?

---

<div class="post-metadata">

### Author: ![cansik](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/cansik/32/8815_2.png) [@cansik](https://discourse.processing.org/u/cansik)
#### Post date: [May 29, 2020, 10:32pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/4 "2020-05-29T22:32:47Z")

</div>

Of course you can run it directly via JOGL, but then you would have to dig inside the framework and risk the break with the API of processing in future versions. Maybe an openCL kernel could help here, but again, you would have to include JOCL and get into it (also a bit tricky in the beginning). Also you would have to do something with the returned data (set each pixel value), which again can take a lot of time because it first has to be moved back to CPU memory and pixel access is slow in java.

I would suggest that you share a simple example (one-page-sketch), which shows the slow performance and we will have a look at it. Because regarding the profiling you showed: Math.pow method takes a long time to calculate, have you tried replacing it with `value * value`? And what kind of precision are you using for your calculations? How do you iterate over the pixels and so on…

---

<div class="post-metadata">

### Author: ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)
#### Post date: [May 30, 2020, 9:19am UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/5 "2020-05-30T09:19:25Z")

</div>

For a linear gradient, I’m iterating pixels like this:

```auto
for (int i = 0, y = 0, x; y < height; ++y) {
	for (x = 0; x < width; ++x, ++i) {
		float step = Functions.project(ox, oy, dx, dy, x, y);
		gradientPG.pixels[i] = gradient.eval(step);
	}
}

```

This iteration can be multi-threaded on the CPU quite easily (where each thread writes to a portion of the PGraphics pixel array):

> **Code...**
>
> ```auto
> final int threadCount = 4;
> final int pixels = gradientPG.pixels.length / threadCount;
> ExecutorService exec = Executors.newFixedThreadPool(threadCount);
> 
> gradientPG = p.createGraphics((int) dimensions.x, (int) dimensions.y);
> gradientPG.beginDraw();
> gradientPG.loadPixels();
> 
> HashSet<LinearThread> threads = new HashSet<>(threadCount);
> for (int i = 0; i < threadCount; i++) {
> threads.add(new LinearThread(i * pixels, pixels, ox, oy, dx, dy, gradient));
> }
> 
> try {
> exec.invokeAll(threads); // run threads, block until all finished
> } catch (InterruptedException e) {
> e.printStackTrace();
> }
> 
> gradientPG.updatePixels();
> gradientPG.endDraw();
> 
> ```

Where a `LinearThread` is this:

> **Code...**
>
> ```auto
> public class LinearThread implements Callable<Boolean> {
> 
> final int offset, pixels;
> final float ox, oy, dx, dy;
> final Gradient gradient;
> 
> public LinearThread(int offset, int pixels, float ox, float oy, float dx, float dy,
> Gradient gradient) {
> this.offset = offset;
> this.gradient = new Gradient();
> this.dx = dx;
> this.dy = dy;
> this.ox = ox;
> this.oy = oy;
> this.pixels = pixels;
> }
> 
> @Override
> public Boolean call() {
> for (int i = offset; i < offset + pixels; i++) {
> float step = Functions.project(ox, oy, dx, dy, i % p.width, (i - i % p.width) / p.height);
> gradientPG.pixels[i] = gradient.eval(step);
> }
> return true;
> }
> 
> }
> 
> ```

The `Gradient` class (a wrapper for a gradient’s colour and colour stops) `eval()` method looks like this. Given a position (_step_) in the gradient, it calculates the color at that point using the current colour space. However, the time this method takes is dominated by the maths methods during LAB conversion.

> **Code...**
>
> ```auto
> int eval(final float step) {
> final int size = colorStops.size();
> 
> // Exit from the function early whenever possible.
> if (size == 0) {
> return 0x00000000;
> } else if (size == 1 || step < 0.0) {
> return colorStops.get(0).clr;
> } else if (step >= 1.0) {
> return colorStops.get(size - 1).clr;
> }
> 
> ColorStop currStop;
> ColorStop prevStop;
> float currPercent, scaledst;
> for (int i = 0; i < size; i++) {
> currStop = colorStops.get(i);
> currPercent = currStop.percent;
> 
> if (step < currPercent) {
> 
> // These can be declared within the for-loop because
> // if step < currPercent, the function will return
> // and no more iterations will be executed.
> float[] originclr = new float[4];
> float[] destclr = new float[4];
> float[] rsltclr = new float[4];
> 
> // If not at the first stop in the gradient (i == 0),
> // then get the previous.
> prevStop = colorStops.get(i - 1 < 0 ? 0 : i - 1);
> 
> scaledst = step - currPercent;
> float denom = prevStop.percent - currPercent;
> if (denom != 0) {
> scaledst /= denom;
> }
> 
> // Assumes that color stops' colors are ints. They could
> // also be float[] arrays, in which case they wouldn't
> // need to be decomposed.
> switch (colorSpace) {
> // TODO CALC STEP HERE ?
> case HSB :
> Functions.rgbToHsb(currStop.clr, originclr);
> Functions.rgbToHsb(prevStop.clr, destclr);
> Functions.smootherStepHsb(originclr, destclr, scaledst, rsltclr);
> return Functions.composeclr(Functions.hsbToRgb(rsltclr));
> 			
> case RGB :
> Functions.decomposeclr(currStop.clr, originclr); // decompose int color to [R,G,B,A] (0-1)
> Functions.decomposeclr(prevStop.clr, destclr); // decompose int color to [R,G,B,A] (0-1)
> Functions.smootherStepRgb(originclr, destclr, scaledst, rsltclr); // lerp between colors?
> return Functions.composeclr(rsltclr);
> 					
> case LAB : 
> double[] rsltclr1 = new double[4];
> double[] labA = LAB.rgb2lab(Functions.decomposeclr(currStop.clr)); // RGB->LAB
> double[] labB = LAB.rgb2lab(Functions.decomposeclr(prevStop.clr));
> LAB.interpolate(labA, labB, scaledst, rsltclr1); // stil in LAB
> float[] inter = LAB.lab2rgb(rsltclr1);
> return Functions.composeclr(inter[0], inter[1], inter[2]);
> 
> case FAST_LAB :
> double[] rsltclr2 = new double[4];
> double[] labC = FastLAB.rgb2lab(Functions.decomposeclr(currStop.clr)); // RGB->LAB
> double[] labD = FastLAB.rgb2lab(Functions.decomposeclr(prevStop.clr));
> FastLAB.interpolate(labC, labD, scaledst, rsltclr2); // stil in LAB
> float[] inter1 = FastLAB.lab2rgb2(rsltclr2);
> return Functions.composeclr(inter1[0], inter1[1], inter1[2]);
> default :
> break;
> }
> }
> }
> return colorStops.get(size - 1).clr;
> }
> 
> ```

* * *

Regarding the profiling, I’ve since replaced the Processing methods with [jafama](https://github.com/jeffhain/jafama).FastMath, and I’m now working in doubles only until the very end (so only a single cast to float, rather than casting in every method) – these changes have doubled the speed.

I’ve developed a version that uses the optimised approximate methods `pow()`, `ln()` and `exp()` methods from [here](https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/); these are indeed much faster (x5), but sadly too imprecise, often making a noticeable impact in the gradient results.

Example: Left gradient uses FastMath methods; right gradient uses approximate methods – this is a more severe example.

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/4/4494532374e499e6d8d9bb4a6a44e26ddea543c4.png) ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/d/d8737f94b8d948342b60bc9999840c694d243884.png)

The LAB\<–\>RGB conversion uses fraction exponents, logs, etc (that’s why it’s so heavy) Here’s the conversion class:

> **Code...**
>
> ```auto
> final class LAB {
> 
> // D65 standard referent
> private static final double Xn = 0.950470;
> private static final double Yn = 1.0;
> private static final double Zn = 1.088830;
> 
> private static final double t0 = 0.137931034; // 4 / 29
> private static final double t1 = 0.206896552; // 6 / 29
> private static final double t2 = 0.12841855; // 3 * t1 * t1
> private static final double t3 = 0.008856452; // t1 * t1 * t1
> 
> private static final double third = 1 / 3.0f;
> 
> /**
> * sRGB (0-255) to LAB (CIE-L*ab)
> * 
> * @param rgb
> * @return
> */
> public static double[] rgb2lab(float[] rgb) {
> double[] xyz = rgb2xyz(rgb[0], rgb[1], rgb[2]);
> double l = 116 * xyz[1] - 16;
> if (l < 0) {
> return new double[0]; // todo?
> }
> return new double[] { l, 500 * (xyz[0] - xyz[1]), 200 * (xyz[1] - xyz[2]) };
> }
> 
> private static double rgb_xyz(double r) {
> if ((r /= 255) <= 0.04045)
> return r / 12.92;
> return FastMath.pow((r + 0.055) / 1.055, 2.4);
> }
> 
> private static double xyz_lab(double t) {
> if (t > t3) {
> return FastMath.pow(t, third);
> } else {
> return t / t2 + t0;
> }
> }
> 
> private static double[] rgb2xyz(double r, double g, double b) {
> r = rgb_xyz(r);
> g = rgb_xyz(g);
> b = rgb_xyz(b);
> final double x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
> final double y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / Yn);
> final double z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / Zn);
> return new double[] { x, y, z };
> }
> 
> // --------------------------------------------------------------------------------------------
> 
> static final float Xn2 = 95.04f;
> static final float Yn2 = 100;
> static final float Zn2 = 108.89f;
> 
> public static float[] lab2rgb(double[] lab) {
> return xyz2rgb(lab2xyz(lab));
> }
> 
> /**
> * https://github.com/Heanzy/bupt-wechatapp/tree/master/src/main/java/com/buptcc/wechatapp/utils
> * @param Lab
> * @return
> */
> private static double[] lab2xyz(double[] Lab) {
> double[] XYZ = new double[3];
> final double L, a, b;
> final double fx, fy, fz;
> 
> L = Lab[0];
> a = Lab[1];
> b = Lab[2];
> 
> fy = (L + 16) / 116;
> fx = a / 500 + fy;
> fz = fy - b / 200;
> 
> if (fx > 0.2069) {
> XYZ[0] = (Xn2 * FastMath.pow(fx, 3));
> } else {
> XYZ[0] = Xn2 * (fx - 0.1379f) * 0.1284f;
> }
> 
> if ((fy > 0.2069) || (L > 8)) {
> XYZ[1] = (Yn2 * FastMath.pow(fy, 3));
> } else {
> XYZ[1] = Yn2 * (fy - 0.1379f) * 0.1284f;
> }
> 
> if (fz > 0.2069) {
> XYZ[2] = (Zn2 * FastMath.pow(fz, 3));
> } else {
> XYZ[2] = Zn2 * (fz - 0.1379f) * 0.1284f;
> }
> 
> return XYZ;
> }
> 
> private static float[] xyz2rgb(double[] XYZ) {
> final float[] sRGB = new float[3];
> final double X, Y, Z;
> double dr, dg, db;
> X = XYZ[0];
> Y = XYZ[1];
> Z = XYZ[2];
> 
> dr = 0.032406 * X - 0.015371 * Y - 0.0049895 * Z;
> dg = -0.0096891 * X + 0.018757 * Y + 0.00041914 * Z;
> db = 0.00055708 * X - 0.0020401 * Y + 0.01057 * Z;
> 
> if (dr <= 0.00313) {
> dr = dr * 12.92;
> } else {
> dr = (FastMath.exp(FastMath.log(dr) / 2.4) * 1.055 - 0.055);
> }
> 		
> 
> if (dg <= 0.00313) {
> dg = dg * 12.92;
> } else {
> dg = (FastMath.exp(FastMath.log(dg) / 2.4) * 1.055 - 0.055);
> }
> 
> if (db <= 0.00313) {
> db = db * 12.92;
> } else {
> db = (FastMath.exp(FastMath.log(db) / 2.4) * 1.055 - 0.055);
> }
> 
> dr = dr * 255; // 255 here TODO
> dg = dg * 255;
> db = db * 255;
> 
> dr = FastMath.min(255, dr);
> dg = FastMath.min(255, dg);
> db = FastMath.min(255, db);
> 
> sRGB[0] = (int) (dr + 0.5);
> sRGB[1] = (int) (dg + 0.5);
> sRGB[2] = (int) (db + 0.5);
> 
> return sRGB;
> }
> 
> /**
> * originclr, destclr, scaledst, rsltclr
> * 
> * @param a LAB col 1
> * @param b LAB col 2
> * @param st step
> * @param out new col
> * @return
> */
> public static double[] interpolate(double[] a, double[] b, float step, double[] out) {
> step = Functions.calcStep(step);
> out[0] = a[0] + step * (b[0] - a[0]);
> out[1] = a[1] + step * (b[1] - a[1]);
> out[2] = a[2] + step * (b[2] - a[2]);
> return out;
> }
> }
> 
> ```

* * *

Looking at it just now, I think these two lines:

```auto
double[] labA = LAB.rgb2lab(Functions.decomposeclr(currStop.clr));
double[] labB = LAB.rgb2lab(Functions.decomposeclr(prevStop.clr));

```

… can be computed just once for the color stop (rather than for every pixel!!!)

---

<div class="post-metadata">

### Author: ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)
#### Post date: [May 30, 2020, 12:16pm UTC](https://discourse.processing.org/t/compute-shaders-in-processing/21400/6 "2020-05-30T12:16:35Z")

</div>

**Update:**

Turns out _jafama_ provides `__Quick` versions of the methods with less accuracy. The speed up is roughly the same speed up as the optimised approximate methods used before, but the quality is much better.

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/5/56b111a427c2f99b54384fb823d1f3f7b7c4fd91.png) ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/e/eb841f96b78dee7ec5f322b9cdfcf6bc126abf12.png).

_Better quality: not identical, but less banding than prior quick methods!_

Another optimisation was refactoring the 3 cases of integer powers to multiplication:  
`XYZ[0] = (Xn2 * FastMath.pow(fx, 3));` to  
`XYZ[0] = (Xn2 * fx * fx * fx);`

And the big one: calculating the LAB value of a colorstop during its construction (once) and not recalculating every pixel. Now, for each pixel, we call `lab2rgb()` only, rather than a call to `lab2rgb` and two calls to `rgb2lab()`. The new `LAB` part in `eval()` looks like this:

```auto
double[] rsltclr1 = new double[4];
LAB.interpolate(currStop.labclr, prevStop.labclr, smoothStep, rsltclr1);
return LAB.lab2rgb(rsltclr1);

```

With all these optimisations, on a single thread, I’ve gone from **~1fps to ~23fps** at 800x800 pixels (In contrast, RGB interpolation runs at ~37fps).

* * *

Another trick I’ve just thought of is rendering every nth pixel, which seems to work very well for gradients:

Modifying main loop to this:

```auto
int recentPixel = 0;
for (int i = 0, y = 0, x; y < height; ++y) {
	for (x = 0; x < width; ++x, ++i) {
		if (i % n == 0) {
			float step = Functions.project(ox, oy, dx, dy, x, y);
			recentPixel = gradient1.eval(step);
		}
		gradient.pixels[i] = recentPixel;
	}
}

```

Results:

n=1 (render every pixel, as before)

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/5/53d296da14b57f7f3e190b3dc67109fad9ab23b9.png)  
n=2  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/1/1cd24716fb821642212ed9a6fde58ba56e7cdf2e.png)  
n=4  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/4/49cfb41cd8e481577177d695544eea21f9ffc4d5.png)  
n=8 (banding is finally noticeable)  
 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/1/1a8f48afa6d00611b9781d9fab12f00f2f89854d.png)
