Shader output to Processing

Is there a way to get the shader out value back to the CPU thread (processing)?

Hi @JohnC,

You need to get a bit more concrete for your question.

Cheers
ā€” mnse

You can have your shaders render to an image and then interpret the colors as your output data or you can use SSBOs which requires you to go ā€œunder the coversā€ of Processing and use OpenGL calls directly.

without ssbos its hard work, ive tried implementing this here and have run into several issues, currently working with limitations are division substraction and addition. The limitation being the floating point precision available. only 3 decimal places and I do not know how to increase this.
Multiplication also hits a ceiling when multiplying numbers larger than 4000 together and for some reason the float component fails completely. Its the last part I have to try to figure out.

//this is due to precision errors, either the shader or PGraphics cannot handle the larger numbers, numbers between 0-500

based on the base 256 conversion in shader here is a sketch to show an example of conversion between negative and positive. Note than in github Ive simplified this enormously and instead just set a bool in the red value of shader 2 to know whether a value is negative or positive. It does limit the amount of information we can theoretically retrieve, but as the program cannot make use of any more than three decimal places the red value is uselless anyway, unless this can be rectified, then the approach in the following sketch is required.
//now updated to 4, still causes issues when multiplying large numbers, and theres an issue with rounding
Also to note the shader has no way yet to know whether a value is negative or positive, though it is as simple as modifying the writeshader program to read the red value in the shaderf shader and seeing if its set to 1.0 or 255, then adjusting accordingly I just havent got round to it, as Iā€™m still figuring out the multiplication float component issue.

use the latest version on github the others work, if you find a read error in println() comment out the line, and v6 does not work in processing 4 as I could not revert it back to java mode.

color c;
void setup(){
  size(1200,400);
};
float mx = 120000;
int base = 256;
float r,g,b,r_,b_,g_,num;
int mm = 0;
void draw(){
  //if(mousePressed)mx+=2;
  mx = map(mouseX,-0,width,1,-1);
  mx*=1000000;
  //mx = 6553600;
  //mm = abs(int(mx));
  mm = (int(mx));
  if(mm<0)mm=6553600+abs(int(mx));
  c = (mm);
  //background(c);
  background(255);
  
  fill(0);
  color k = color(c);
  text("c: "+k,100,100);
  text("mx: "+mx,100,120);
  
  text("red: "+red(k),100,140);
  text("green: "+green(k),100,160);
  text("blue: "+blue(k),100,180);
  
  r = floor(mm/(base*base)%base);
  float r4 = floor(mm/(base*base)%100);
  float r5 = (floor(mm/(base*base)%255)-floor(mm/(base*base)%100));
  g = floor((mm/(base)%base));
  b = floor(mm)%base;
  
  //r = floor(c/(base*base)%base);
  //float r4 = floor(c/(base*base)%100);
  //float r5 = (floor(c/(base*base)%255)-floor(c/(base*base)%100))/100;
  //g = floor((c/(base)%base));
  //b = floor(c)%base;
  fill(0);
  text("r: "+r,100,220);
  text("g: "+g,100,240);
  text("b: "+b,100,260);
  
  float r1 = r/255;
  float rr1 = r4/100;
  float g1 = g/255;
  float b1 = b/255;
  
  text("r1: "+r1+","+ 4%3,100,280);
  text("g1: "+g1,100,300);
  text("b1: "+b1,100,320);
  
  
  num = r1*255*base*base+g1*255*base+b1*255;
  
  //num = rr1*99*base*base+g1*255*base+b1*255;
  
  text("num  : "+num,100,380);
  float num1 = rr1*100*base*base+g1*255*base+b1*255;
  if(floor(mm/(base*base)%255)>99)num1 = -num1;
  //if(floor(mm/(base*base)%255)>100)
  println("hello",rr1,r4,floor(mm/(base*base)%100));
  //num1 = -num1;
  text("num1  : "+num1,100,390);
  
  float r2 = r1*255;
  float g2 = g1*255;
  float b2 = b1*255;
  
  text("r2: "+r2,200,220);
  text("g2: "+g2,200,240);
  text("b2: "+b2,200,260);
  
  float r3 = r1*255;
  float g3 = g1*255;
  float b3 = b1*255;
  
  text("r3: "+r2,200,220+60);
  text("g3: "+g2,200,240+60);
  text("b3: "+b2,200,260+60);
  text("r4: "+r4,200,280+60);
  text("r5: "+r5,200,300+60);
  stroke(0);
  strokeWeight(2);
  point(width/2,100);
};

fragment shader creating for integer component of values

#ifdef GL_ES
precision highp float;
precision highp int;
#endif
#define PROCESSING_TEXTURE_SHADER
uniform float v[10];
uniform sampler2D texture;
uniform sampler2D va0;
uniform sampler2D vb0;
uniform sampler2D va1;
uniform sampler2D vb1;
uniform float mult = 1.0;
uniform float type;
uniform float thresh;
uniform vec2 texOffset;
varying vec4 vertColor;
varying vec4 vertTexCoord;
uniform vec2 resolution;
uniform float w;
uniform int [1000]s;
uniform float [10]ops;
uniform float [1000]vars;
void main(void) {
float x = 1.0 / resolution.x;
float y = 1.0 / resolution.y;
vec2 tx = vertTexCoord.st;
vec4 via0 = texture2D(va0 , vertTexCoord.st);
vec4 vib0 = texture2D(vb0 , vertTexCoord.st);
vec4 via1 = texture2D(va1 , vertTexCoord.st);
vec4 vib1 = texture2D(vb1 , vertTexCoord.st);
float base = 256;
float b1 = base*base*255;
float b2 = base*255;
float b3 = 255;
float tva0 = (via0.r*b1 + via0.g*b2 + via0.b*b3);
float tva1 = (via1.r*b1 + via1.g*b2 + via1.b*b3);
float tvb0 = (vib0.r*b1 + vib0.g*b2 + vib0.b*b3 )/100000;
float tvb1 = (vib1.r*b1 + vib1.g*b2 + vib1.b*b3 )/100000;
float tvf0 = (tvb0+tva0);
float tvf1 = (tvb1+tva1);
float num = ((tvf0)*(tvf1))*1.0;
float numf = int(num);
if(num<0.0) numf = -int(num);
float rni = floor(mod (numf/(base*base), base))/255;
float gni = floor(mod ((numf/base), base))/255;
float bni = floor(mod (numf, base))/255;
gl_FragColor = vec4(rni, gni, bni, 1.0);
}

float component generated in frag shader

#ifdef GL_ES
precision highp float;
precision highp int;
#endif
#define PROCESSING_TEXTURE_SHADER
uniform float v[10];
uniform sampler2D texture;
uniform sampler2D va0;
uniform sampler2D vb0;
uniform sampler2D va1;
uniform sampler2D vb1;
uniform float mult = 1.0;
uniform float type;
uniform float thresh;
uniform vec2 texOffset;
varying vec4 vertColor;
varying vec4 vertTexCoord;
uniform vec2 resolution;
uniform float w;
uniform int [1000]s;
uniform float [10]ops;
uniform float [1000]vars;
void main(void) {
float x = 1.0 / resolution.x;
float y = 1.0 / resolution.y;
vec2 tx = vertTexCoord.st;
vec4 via0 = texture2D(va0 , vertTexCoord.st);
vec4 vib0 = texture2D(vb0 , vertTexCoord.st);
vec4 via1 = texture2D(va1 , vertTexCoord.st);
vec4 vib1 = texture2D(vb1 , vertTexCoord.st);
float base = 256;
float b1 = base*base*255;
float b2 = base*255;
float b3 = 255;
float tva0 = (via0.r*b1 + via0.g*b2 + via0.b*b3);
float tva1 = (via1.r*b1 + via1.g*b2 + via1.b*b3);
float tvb0 = (vib0.r*b1 + vib0.g*b2 + vib0.b*b3 )/100000;
float tvb1 = (vib1.r*b1 + vib1.g*b2 + vib1.b*b3 )/100000;
float tvf0 = (tvb0+tva0);
float tvf1 = (tvb1+tva1);
float num = ((tvf0)*(tvf1))*1.0;
float numf = abs(num - int(num))*100000 ;
float rni = floor(mod (numf/(base*base), base))/255;
 rni = 0.0;
if(num<0.0)rni = 1.0;
float gni = floor(mod ((numf/base), base))/255;
float bni = floor(mod (numf, base))/255;
gl_FragColor = vec4(rni, gni, bni, 1.0);
}

in the main sketch window

PGraphics c;
float[]num, x, y;
int max = 1000;
PShader s,s1,s2,s3;
PImage imgxa,imgxb,imgya,imgyb;
PGraphics c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15;
int n = 123456789;
int m;
int z = 0;
int cwidth = 400;
int cheight = 200;
color col = color(0, 0, 0);
color cc = color(5,99,98,97);
color cc1 = color(5,99,98,97);
int[]t = new int[4];
int cmax = 255;
String []text;
String ss = "{";
String []Conv;
int []conv;
cShader cs;


void setup() {
  size(400, 400, P2D);
  text = new String[10000000];
  max = cwidth*cheight;
  surface.setLocation(1440/2-20, 0);
  x = new float[max];
  y = new float[max];
  num = new float[max];
  //initVectors();
  //numToF();
//operations to perform note that due to an error you always have to add one more operation 
//the last one is ignored.
  String op = "*-";
//this sets the size of the shader but also the maximum variables used as its based on the max 
//width of the shader so larger value will set higher integer float combination
//the second valu is the number of variables to compute
//third value can be ignored for now
  cs = new cShader(500,2,op);
  
  //cs.createshader(1,
  //String[]text = new String[10000000];
  
  //println(text);
  cs.lodRandom2();
  //smooth(8);
};

int k = 0;
float con = 0;

void draw() {
  background(255);
  //cs.runDV();//with debug info
  //cs.runDF();//with debug info
  cs.runVF();//without
  //cs.lodRandom2();//od rndom vribles
  //for(int i=0;i<cs.num.length;i+=2){
  //  point(cs.num[i],cs.num[i+1]);
  //}
  //println("frmerte",frameRate);
  strokeWeight(20);
  fill(0);
  text(frameRate, 50, 50);
};

as you can see the shader code is simple enough and there are some bits at the top that I need to remove however its not without its challenges.
va and vb are the integer component and float component of randomly generated variables stored in a PGraphics as rgb values

and initially I had tried to make use of rgba and not just rgb but the alpha channel inteferes with all the other values

I agree that I donā€™t know of any way to retrieve the full 32-bit output from the fragment shader without the alpha value getting multiplied on, so weā€™re stuck with only 24-bit output per pixel. That doesnā€™t mean we canā€™t get more bits out, it just means we have to spread them across multiple pixels at the cost of some speed for duplicating computations. For instance, we can do the same computation in two pixels and return the high bits of the result in one pixel, the low in the next, and then put the two together on the CPU when we read it out.

The right way to do this would be to use OpenGLā€™s float32 textures and framebuffers to send and receive the data. Processing doesnā€™t give us access to those, but we can still pass and receive floats by converting them to and from integers on both the Java and GLSL sides. If we know the bounds on the values and precision of our input and can store them within 24-bits, we can just scale and shift them to squeeze into 24 bits worth of integers. If you want the full range of floats, however, Java has the functions

int bits = Float.floatToIntBits( x );

and

float x = Float.intBitsToFloat( bits );

so you can store those bits directly in your texture pixels array to pass it to the shader.

On the GLSL side, we have

uint bits;
float x = uintBitsToFloat ( bits );

and

uint bits = floatBitsToUint( x );

There are also int versions, but itā€™s safer to use uint so we donā€™t mess around with the sign bit by mistake.

A few other points: gl_FragCoord.xy gives you the screen (or framebuffer) coordinates of the current pixel. Itā€™s a float type since it was around before they added ints. Itā€™s easier to use than dealing with scaling texture coordinates. texelFetch() gets a single texture pixel without going through the texture filters. See the function fetchData() in my fragment shader code. The only reason the shader needs the resolution is because Processing insists on flipping the y-coordinates upside down and we have to put it back upright.

The code below generates two arrays of 1000000 floats, evaluates an expression on them, and prints out 10 of the results starting at 2000. I kept both the data and the evaluation expression very simple so that we could verify correctness of the result. The results are spit out in the green and blue channels across two pixels and so are combined into one 32-bit int before converting back to a float.

int nData = 1000000;
int nRows;

PShader shdr;
PImage inData1, inData2;
PGraphics outData;
String expr;

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

  // **** the expression to evaluate as a function of v1 and v2 **** //
  expr = "3.0 * v1 + 2.0 * v2";

  nRows = nData / 1024 + 1;
  inData1 = createImage( 1024, nRows, ARGB );
  inData2 = createImage( 1024, nRows, ARGB );
  outData = createGraphics( 2048, nRows, P2D );
  outData.noSmooth();
  
  inData1.loadPixels();
  inData2.loadPixels();
  // **** load up our input values **** //
  for( int i=0; i<nData; i++ ) {
    inData1.pixels[i] = Float.floatToIntBits( float(i) );
    inData2.pixels[i] = Float.floatToIntBits( float(i) );
  }
  inData1.updatePixels();
  inData2.updatePixels();

  fragSrc[1] = expr;
  shdr = new PShader( g.parent, vertSrc, fragSrc );
  shdr.set( "inData1", inData1 );
  shdr.set( "inData2", inData2 );
  
  outData.beginDraw();
  outData.background(0);
  outData.clear();
  outData.shader( shdr );
  outData.noStroke();
  outData.rect( 0, 0, outData.width, outData.height );
  outData.loadPixels();
  for( int i=2000; i<2010; i++ ) {
    int bits = (outData.pixels[2*i]&0xffff) + ((outData.pixels[2*i+1]&0xffff)<<16);
    float v = Float.intBitsToFloat( bits );
    println( i, v );
  }
  outData.endDraw();
  image( outData, 0, 0, width, height );
  
}

void draw() {}

String[] vertSrc = {"""
#version 330
uniform mat4 transform;   // passed in by Processing
in vec4 position;
void main() {
  gl_Position = transform * position;
}
"""};

String[] fragSrc = {"""
#version 330
precision highp float;
uniform vec2 resolution;   // passed in by Processing
uniform sampler2D inData1;
uniform sampler2D inData2;

float fetchData( sampler2D img, ivec2 ij ) {
  uvec4 d = uvec4( texelFetch( img, ij, 0 ) * 255 );
  uint u = (((d.a*256U)+d.r)*256U+d.g)*256U+d.b;
  return uintBitsToFloat( u );
}

out vec4 outColor;
void main() {
  ivec2 ij = ivec2( gl_FragCoord.xy );
  int hilo = ij.x & 1;
  ij.x /= 2;
  ij.y = int(resolution.y-1) - ij.y;  // Processing y is upside-down

  float v1 = fetchData( inData1, ij );
  float v2 = fetchData( inData2, ij );
  uint u = floatBitsToUint( 
""", 
"v1 + v2",
"""
);
  u = (u >> (16*hilo)) & 0xffffU;
  vec2 res = vec2( float((u>>8)&0xffU), float(u&0xffU) ) / 255.;
  outColor = vec4( 0., res, 1. );
}
"""};
1 Like