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