Problem with storing packed-data in a texture

Hello forum members,
I’m new to using shaders in Processing and need some help please. There is something I don’t understand about my code and I’m hoping one of you experts will be able to give me some guidance.

In summary: I’m experimenting with storing particle positions in a texture, then retrieving the information to plot the particle. Everything seems to work as expected so long as I use low resolution positions which will fit into the 8-bit channel of the texture, i.e. if I save the positions as vec4(x, y, 0., 1.). However, I need more resolution but when I try to pack the channels like this: vec4(LSB-x, LSB-y, MSB-x, MSB-y) I run into problems.

I’ve searched this forum and the internet for clues and there are a few leads, but nothing that directly answers my questions. These links provided helpful context:

To add some more details about my particular code:

  1. I use 2 shaders: ‘initShader’ (frag only) initializes the positions of N particles and writes these positions into a texture. It is my intention to use random positions, but while I’m debugging I’m currently setting all positions to the middle of the sketch window i.e. (x,y) = (0.5, 0.5).

  2. ‘plotShader’ (vertex+fragment) to plot the particle on the sketch window: ‘plot.vert’ takes the texture and extracts the position information, then ‘plot.frag’ draws a green dot at the correct position.

  3. The texture ‘tex’ is a PGraphic with size (N x 1) where N is the number of particles. It is my understanding that each of the N entries can hold four 8-bit channels i.e. vec4(R, G, B, A)

  4. There is also a PShape ‘shp’ containing N vertices. I draw this shape to ensure the ‘plot.vert’ shader is executed the correct number (N) times - once for each particle. The vertex coordinates (0,0), (1,0), (2,0), … (N-1,0) are simply used as an index into the texture which contains the actual particle positions.

Here is the full code:

“Particle_packing.pde”

//------------------------------------------------------------------------------------------
PShader initShader, plotShader;
PGraphics tex;
PShape shp;
ArrayList<PVector> vectors = new ArrayList<PVector>();
int N = 100;  // number of particles  

//------------------------------------------------------------------------------------------
void setup() {
  size(400, 400, P3D);                   
  strokeWeight(2); stroke(255); smooth(2);
  
// define shaders
  initShader = loadShader("init.frag");                   // randomly initializes the particle positions  
  plotShader = loadShader("plot.frag", "plot.vert");      // plots the particle positions

// initialize the texture  - it holds the actual particle positions
  tex  = createGraphics(N, 1, P2D);  					  // N particles 
  tex.beginDraw();  
    //To aid with debugging, 'initShader' currently assigns all particles positions to (0.5, 0.5)
    tex.shader(initShader);                
    tex.rect(0,0,tex.width, tex.height);                  // write info to the texture
  tex.endDraw();  
   
// Create a shape containing N vertices, where N is the number of particles we want 
  // We use this shape to ensure the plot.vertex shader is executed the correct number(N) times - once for 
  // each particle. The vertex coordinate is only used as an index into the texture which 
  // contains the actual particle positions
  for (int i = 0; i<N; i++) {  vectors.add(new PVector(i,0));  }
  shp = createShape();
    shp.beginShape(POINTS);                               // We want individual points to be plotted, not quads
    for (PVector v: vectors) { shp.vertex(v.x, v.y); }    // Encode index into shp vertex position (1,0),...(N-1,0)
  shp.endShape();   
}
//------------------------------------------------------------------------------------------
void draw(){   
  background(0);
  // Use 'plotShader' instead of the default shader for future drawing
    shader(plotShader);  
  // pass the texture containing the particle positions
    plotShader.set("tex", tex); 
  // pass the size of sketch window as a uniform    
    plotShader.set("sketchSize",float(width), float(height));         
  // Use 'plotShader' to draw 'shp': It should pull particle positions from the texture and plot these   
    shape(shp, 0, 0);                    
}
//------------------------------------------------------------------------------------------

“init.frag”

//----------------------------------------------------------------------------------------
void main() {
//To aid with debugging, temporarily assign all particles positions to (0.5, 0.5)
  gl_FragColor = vec4( .5, .5, 0., 1.);  //debug override
}
//----------------------------------------------------------------------------------------

“plot.vert”

//----------------------------------------------------------------------------------------
// These uniforms and attributes are automatically generated by Processing
uniform mat4 projection;    
uniform mat4 modelview;     
attribute vec4 position;    // the vertex data of 'shp' - i.e. (0,0), ...(N-1,0)
attribute vec2 offset;
// These are manually passed
uniform sampler2D data;     // texture containing position data
uniform vec2 sketchSize;

//----------------------------------------------------------------------------------------
void main() {
// This shader is executed once for each vertex in the shp object, i.e. numParticles times

// The 'position' attribute for each vertex in shp has been set to (1,1),...(N-1,N-1)
// We use 'position.x' as an index into the data buffer which contains the actual particle positions
// 'data' is an Nx1 buffer, to extract the data correctly use the col index of 0
  vec4 extractedPos = texelFetch(data, ivec2(position.x,0), 0);  // e.g. (.5, .5, 0., 1.)

// Now plot to the screen... these additional transformation steps are necessary when using P3D 
  extractedPos.xy *= sketchSize;       // convert from normalized to absolute position - only multiply .xy, not .z
  vec4 pos = modelview * extractedPos;            
  vec4 clip = projection * pos;
  gl_Position = clip + projection * vec4(offset, 0, 0); 
}
//----------------------------------------------------------------------------------------

“plot.frag”

//----------------------------------------------------------------------------------------
void main() {
  gl_FragColor = vec4(0., 1., 0., 1.);    // plot a green dot at the particle position
}
//----------------------------------------------------------------------------------------

This code works as expected with all the particles being plotted at the same position - so the sketch window shows a single green dot in the middle. If you change (.5, .5, 0., 1.) in the initShader you can move this dot to other locations to convince yourself that this is working as intended. Also, if you modify ‘initShader’ to save each particle at a different random position you get N green dots, as expected.

But as I mentioned above, I need a higher resolution for my particle positions otherwise I’m limited to a 256x256 sketch window. Many coders have described how to pack position data into 4-bytes (2 for x, 2 for y) in order to get a resolution of 65536 instead. They typically pack the data like this: vec4(LSB-x, LSB-y, MSB-x, MSB-y), so the packed data will still fit within a normal texture. This is the code I used in the shaders:

//--------------------------------------------------------------------
vec4 pack(vec2 pos) {                                         // pos is normalized 0-1
    return vec4( fract(pos*255.), floor(pos*255.)/255. );	  // (LSB-x, LSB-y, MSB-x, MSB-y)
}
//--------------------------------------------------------------------
vec2 unpack(vec4 data){
	return vec2(data.x/255. + data.z, data.y/255. + data.w ); // returns normalized pos 0-1
}
//--------------------------------------------------------------------

In the second version of my code I include this pack/unpack feature: In the ‘initShader’ I pack the position into 4-bytes (as above) then write it to the texture. In the ‘plot.vert’ shader I read back this 4-byte packed-data from the texture, then unpack to position data (float(x,y)) before plotting.

Here’s the new code: Please note that only ‘init.frag’ and ‘plot.vert’ differ from the ‘no-packing version’ - all other code is the same as above:

“init.frag”

//--------------------------------------------------------------------
vec4 pack(vec2 pos) {    // pos is normalized (0-1)
    return vec4( fract(pos*255.), floor(pos*255.)/255. );       // (LSB-x, LSB-y, MSB-x, MSB-y )
}
//----------------------------------------------------------------------------------------
void main() {
	vec2 pos = vec2(.5, .5);		
	gl_FragColor = pack(pos);
}
//----------------------------------------------------------------------------------------

“plot.vert”

//----------------------------------------------------------------------------------------
uniform mat4 projection;		
uniform mat4 modelview;			
attribute vec4 position;		// the vertex data of 'shp' - i.e. (0,0),(1,0),...(N-1,0)
attribute vec2 offset;
uniform sampler2D tex;			// texture containing position data
uniform vec2 sketchSize;

//--------------------------------------------------------------------
vec2 unpack(vec4 data){
	return vec2(data.x/255. + data.z, data.y/255. + data.w );   // returns (0, 1)
}
//----------------------------------------------------------------------------------------
void main() {
	vec4 packedData = texelFetch(tex, ivec2(position.x,0), 0);  // (LSB-x, LSB-y, MSB-x, MSB-y )
    vec4 unpackedData = vec4(unpack(packedData), 0., 1.);

 	unpackedData.xy *= sketchSize; 	     // convert from normalized to absolute position
 	vec4 pos = modelview * unpackedData;						
 	vec4 clip = projection * pos;
 	gl_Position = clip + projection * vec4(offset, 0, 0); 
}
//----------------------------------------------------------------------------------------

However, this version of the code doesn’t run correctly - instead of a single dot in the middle of the screen I now get 2 dots, and neither is in the right place.

What have I tried?
5. If I calculate my ‘pack’ and ‘unpack’ functions using a pencil and paper they seem fine - even for small numbers < 1/255, e.g. unpack(pack(.001)) = .001. So I believe the functions are correct, i.e. unpack is working as the inverse of pack, and my resolution is increased beyond 8-bits.

  1. In the ‘initShader’ I have tried to pack, then immediately unpack, the position data before writing it to the texture. The vertex shader reads back this already unpacked-data and plots it correctly.

  2. I can do a similar ‘unpack(pack(…))’ thing in the vertex shader, again it plots correctly.

These 3 tests suggest that the packing and unpacking is being handled correctly, but there is something about writing packed-data into the texture that I don’t understand.

Also, why do I get 2 dots? There should only be one since all positions are initialized the same.

  1. What I have found is that if I change the number of particles this can vary: if N=1 there is only one dot (but in the wrong place); if N>=2 there are two dots (both wrong place).

To summarize the problem: My code for storing packed-data in a texture is not working properly. The output positions are wrong and I get 2 different outputs when I should only get one. Similar code with non-packed-data is OK. The tests I performed suggest that there is something about the way I am writing packed-data into the texture that I don’t properly understand.

Sorry this is such a long post. I know that there are other ways of doing this but I’d really like to understand why this particular approach doesn’t work. I’m slowly learning shaders, but at the moment I’m stumped and would really appreciate any insights. Thanks!

My one very big peeve with Processing is its use of y-coordinate down when math, physics, and most notably OpenGL all use y-up. This can be a huge pain when trying to pass data to and from shaders through textures. It shouldn’t, however, be your problem since you are just using a Nx1 data texture. You’ll hit it when you want more than 16,384 points, though since that’s the max width allowed, at least with my nVidia driver. The limit might be lower on mobile devices. You can pass a PGraphics to a shader as a uniform either by its name or by name.get(). One seems to flip vertically and the other doesn’t and I don’t ever remember which is which.

Processing stores textures as ARGB, so, in my code below, I store the positions as y in AR and x in GB. Each has 65536 values that I let range across the width and height of the screen. I initialize the random position of the particles by filling in the texture. x just goes from left to right and y is random.

To draw, I’m using an OpenGL call drawArrays telling it to render nParts points. In the shader, I know which particle I’m on using the gl_VertexID variable. I keep everything in clip coordinates outputing from -1 to 1, so I’m not using any of Processing’s view matrices just to keep the code simpler.

Here is my version:

import com.jogamp.opengl.*;

int nParts = 250000;

PShader updateShdr;
PShader drawShdr;
PGraphics data;

void setup() {
  size( 1200, 1200, P3D );
  updateShdr = new PShader( this, updateVertSrc, updateFragSrc );
  drawShdr = new PShader( this, drawVertSrc, drawFragSrc );
  drawShdr.set( "nParts", nParts );
  
  data = createGraphics( 4096, (nParts-1)/4096+1, P2D );
  data.beginDraw();
  data.noStroke();
  data.loadPixels();
  for( int i=0; i<nParts; i++ ) {
    float u = float(i)/nParts;
    data.pixels[i] = (int(random(65536)) << 16) | 
                      int(u*65536);
  }
  data.updatePixels();
  data.endDraw();

  frameRate(60);
}


void draw() {
  data.beginDraw();
  updateShdr.set( "mouse", 1.*mouseX/width, 1.-1.*mouseY/height,
                           mousePressed ? 1. : 0. );
  updateShdr.set( "data", data );
  data.filter( updateShdr );
  data.endDraw();

  background( 0 );
  drawShdr.bind();
  drawShdr.set( "data", data );
  PJOGL pgl = (PJOGL) beginPGL();
  GL4 gl = pgl.gl.getGL4();
  gl.glPointSize(1);
  pgl.drawArrays( PGL.POINTS, 0, nParts );
  endPGL();
  drawShdr.unbind();
}


String[] updateVertSrc = { """
#version 330 core
uniform mat4 transformMatrix;
in vec4 position;
void main() {
  gl_Position = transformMatrix * position;
}
""" };


String[] updateFragSrc = {"""
#version 330 core
uniform vec3 mouse;
uniform sampler2D data;
uniform vec2 resolution;

out vec4 outData;

#define TAU 6.283185307179586

// http://www.jcgt.org/published/0009/03/02/
// https://www.shadertoy.com/view/XlGcRh
uvec4 pcg4d( uvec4 v ) {
  v = v * 1664525u + 1013904223u;
  v.x += v.y*v.w;  v.y += v.z*v.x;  v.z += v.x*v.y;  v.w += v.y*v.z;
  v ^= v >> 16u;
  v.x += v.y*v.w;  v.y += v.z*v.x;  v.z += v.x*v.y;  v.w += v.y*v.z;
  return v;
}
vec4 pcg4df( int a, int b, int c, int d ) {
  return pcg4d( uvec4( a, b, c, d ) ) / float( 0xffffffffU );
}

void main() {
  vec2 uv = gl_FragCoord.xy;
  //uv.y = 1.0 - uv.y;

  vec4 d = texelFetch( data, ivec2( uv ), 0 );
  vec2 pos = vec2( d.g+d.b/255.0, d.a+d.r/255.0 ); // * 2. - 1.;
  
  int i = int( uv.y*4096.0 + uv.x );
  vec2 vel = (pcg4df( i, 324, 762, 2 ).xy - 0.5) * 0.2;
  vel += vec2( cos(2.*TAU*pos.y), sin(2.*TAU*pos.x) );
  if( mouse.z > 0. ) {
    vec2 v = fract( pos - mouse.xy + 0.5 ) - 0.5;
    vel -= 1.*v / (.1+100.*pow(length(v),4.));
  }

  pos = mod( pos+vel * 0.001, 1.);

  outData = vec4(fract(pos*255.0), floor(pos*255.0)/255.0).yzxw;
}
"""};


String[] drawVertSrc = {"""
#version 330 core
precision highp float;
uniform int nParts;
uniform sampler2D data;
out vec3 col;
#define TAU 6.283185307179586

vec3 hsb2rgb( in vec3 c ) {
   vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
                            6.0)-3.0)-1.0,   0.0,   1.0 );
   rgb = rgb*rgb*(3.0-2.0*rgb);
   return c.z * mix( vec3(1.0), rgb, c.y);
}

void main() {
  vec4 d = texelFetch( data, ivec2( gl_VertexID%4096, textureSize( data, 0 ).y - 1 - gl_VertexID/4096 ), 0 );
  //vec4 d = texelFetch( data, ivec2( gl_VertexID%4096, gl_VertexID/4096 ), 0 );
  vec2 pos = vec2( d.g+d.b/256.0, d.a+d.r/256.0 ) * 2. - 1.;
  gl_Position = vec4( pos, 0., 1. );
  col = hsb2rgb( vec3(float(gl_VertexID)/float(nParts), 1., 1.) );
}
"""};

String[] drawFragSrc = {"""
#version 330 core
precision highp float;
in vec3 col;
out vec4 fragColor;
void main() {
  fragColor = vec4( col, 1. );
}
"""};
1 Like

@scudly - Many thanks for your response and sharing your code. It runs very smoothly with exactly the precise motion that I am ultimately aiming for. I was clearly able to see how you implemented the data-packing. I particularly like the way you use gl_vertexID and pgl.drawArrays - using something similiar in my code would simplify it and eliminate the need for the PShape object.

I haven’t worked with PJOGL yet but I’ll take a look at it now that I can see how much it has to offer.

One very minor question: I noticed you used data.filter(updateShdr) to select your shader, whereas I used data.shader(…). I checked the PGraphics core documentation and these two methods appear to have simliar specs. Are you aware of any differences?

Returning to my original code, do you have any ideas why it doesn’t work as expected? I’d really like to understand where the error lies so that I can avoid it again in the future.

Anyway, thanks again, I really apprecitate it!

I couldn’t see any specific problem with your code. When I see doubled-up images vertically, it usually means that the texture is flipped in y and alternating between y and 1-y, but with a Nx1 texture, that shouldn’t be a problem. I wasted close to an hour with my code trying to figure out what needed to be turned upside down. Note that in my drawShdr, I subtract the y-coordinate from the textureSize to invert it.

filter() is just a simple function that sets the shader, turns off stroke, and draws a full screen rect. It’s just a convenient shorthand. Always remember to set noStroke() when you draw your rect or you could get zeroes along the border. filter takes care of that, so I prefer it when I remember to use it.

Normally with OpenGL, you would go through some elaborate bureaucracy to allocate, format, and fill buffers to give the vertex coordinates. And it can be tricky to mesh with the rest of Processing. Passing the data through a texture lets us route around it making things quite a bit simpler.

@scudly - Thanks for checking. I’ll keep puzzling away at it and let you know if I get to the bottom of it.

As I was playing with your code in order to fully understand it I noticed one curious thing: If you reduce the number of particles (to say 2500) and drop the frameRate to 1, then you can see that every particle is not a single dot but a closely spaced pair of dots. So there must be 2 draw operations per frame. I’m not yet sufficiently familiar with shaders to understand how this is happening in your code. Could you explain?
Thanks again.

My thinking is that the pairs are not two points being rendered but are an affectation of the way opengl attempts to anti-alias rendering of points when the center falls between pixels. Add a noSmooth() to setup() and the pairs effect goes away.

There will, of course, be naturally occurring pairs simply because some will start out near each other, but if you wait long enough, they will drift apart.

@scudly Ah yes, I think you’re right. Thanks.

Hi @Jon ,

Actually, don’t know which hardware you’re using, but if you’ve a modern graphic card with proper OpenGL support (4.3+) here is s.th. quite nice to study …

Cheers
— mnse

@mnse Many thanks, I’ll enjoy reading this.
P.S. I’m running OpenGL 4.6.

Hi Jon,

As this was interesting for your … Here the one I really wanted to send :joy:

Cheers
— mnse

@mnse Thanks again. I’d actually come across this one and have it bookmarked for once I get a little more experienced with the basics!

1 Like

@scudly @mnse Sorry to bother you again but I’m having trouble getting the syntax correct when I try to use the PJOGL approach that you suggested.

I have no trouble if I plot a set of POINTS directly to the sketch window using code like this…

import com.jogamp.opengl.*;
...
  shader.bind();            
    PJOGL pgl = (PJOGL) beginPGL();
      GL4 gl = pgl.gl.getGL4();
      gl.glPointSize(1);
      pgl.drawArrays( PGL.POINTS, 0, numPts );
    endPGL();
  shader.unbind();

… but I need to plot to an offscreen buffer instead.

I can do it - as long as I don’t use PJOGL - with code like this…

PGraphics buffer;
...
  buffer.beginDraw();
    buffer.filter( shader );
  buffer.endDraw();  
  image(buffer,0,0);

… but when I try to combine these ideas I just can’t seem to get the syntax right. Can you help?
Thanks!

You can do something like:

PGraphics buffer;

...

shader.bind();            
    PJOGL pgl = (PJOGL) buffer.beginPGL();
      GL4 gl = pgl.gl.getGL4();
      gl.glPointSize(1);
      pgl.drawArrays( PGL.POINTS, 0, numPts );
    buffer.endPGL();
  shader.unbind();

which gets the opengl drawing context for the buffer frame buffer.

To be clear, use buffer.beginPGL() and buffer.endPGL() where buffer is the PGraphics that you are drawing to.

1 Like

@scudly Many thanks for your prompt response. Unfortunately, I’m getting a NullPointerException. Any ideas?

  PGraphics buffer;
  PShader shader;
...
  buffer = createGraphics(width, height,P2D);   
...
 shader.bind();        
    PJOGL pgl = (PJOGL) buffer.beginPGL();
      GL4 gl = pgl.gl.getGL4();    //  <--------NullPointerException here
      gl.glPointSize(1);
      pgl.drawArrays( PGL.POINTS, 0, numPts );
    buffer.endPGL();
  shader.unbind();      
...
image(buffer, 0, 0);  // display the buffer in the sketch window

Try moving the bind/unbind pair to be inside the beginPGL/endPGL pair.

The getGL4 line and the following glPointSize line are only needed if you want to make the points bigger than 1, so you can just delete them.

If you do want the points a size different than 1 pixel, then make sure you have the import com.jogamp.opengl.*;.

@scudly Still the same NullPointerException I’m afraid:

    PJOGL pgl = (PJOGL) buffer.beginPGL();
        shader.bind();      //  <-----------------NullPointerException
          pgl.drawArrays( PGL.POINTS, 0, numPts );
        shader.unbind();                       
    buffer.endPGL();               
...
image(buffer, 0, 0);

Did you include buffer.beginDraw() and buffer.endDraw() before and after using it?

import com.jogamp.opengl.*;

int nParts = 250000;

PShader updateShdr;
PShader drawShdr;
PGraphics data;
PGraphics outBuffer;

void setup() {
  size( 1200, 1200, P2D );
  outBuffer = createGraphics( width*2/3, height*2/3, P2D );
  updateShdr = new PShader( this, updateVertSrc, updateFragSrc );
  drawShdr = new PShader( this, drawVertSrc, drawFragSrc );
  drawShdr.set( "nParts", nParts );
  
  data = createGraphics( 4096, (nParts-1)/4096+1, P2D );
  data.beginDraw();
  data.noStroke();
  data.loadPixels();
  for( int i=0; i<nParts; i++ ) {
    float u = float(i)/nParts;
    data.pixels[i] = (int(random(65536)) << 16) | 
                      int(u*65536);
  }
  data.updatePixels();
  data.endDraw();

  frameRate(60);
  noSmooth();
}


void draw() {
  data.beginDraw();
  updateShdr.set( "mouse", 1.*mouseX/width, 1.-1.*mouseY/height,
                           mousePressed ? 1. : 0. );
  updateShdr.set( "data", data );
  data.filter( updateShdr );
  data.endDraw();

  outBuffer.beginDraw();
  outBuffer.background( 0 );
  PJOGL pgl = (PJOGL) outBuffer.beginPGL();
  drawShdr.bind();
  drawShdr.set( "data", data );
  GL4 gl = pgl.gl.getGL4();
  gl.glPointSize(1);
  pgl.drawArrays( PGL.POINTS, 0, nParts );
  drawShdr.unbind();
  outBuffer.endPGL();
  outBuffer.endDraw();
  image( outBuffer, 100, 100 );
}


String[] updateVertSrc = { """
#version 330 core
uniform mat4 transformMatrix;
in vec4 position;
void main() {
  gl_Position = transformMatrix * position;
}
""" };


String[] updateFragSrc = {"""
#version 330 core
uniform vec3 mouse;
uniform sampler2D data;
uniform vec2 resolution;

out vec4 outData;

#define TAU 6.283185307179586

// http://www.jcgt.org/published/0009/03/02/
// https://www.shadertoy.com/view/XlGcRh
uvec4 pcg4d( uvec4 v ) {
  v = v * 1664525u + 1013904223u;
  v.x += v.y*v.w;  v.y += v.z*v.x;  v.z += v.x*v.y;  v.w += v.y*v.z;
  v ^= v >> 16u;
  v.x += v.y*v.w;  v.y += v.z*v.x;  v.z += v.x*v.y;  v.w += v.y*v.z;
  return v;
}
vec4 pcg4df( int a, int b, int c, int d ) {
  return pcg4d( uvec4( a, b, c, d ) ) / float( 0xffffffffU );
}

void main() {
  vec2 uv = gl_FragCoord.xy;
  //uv.y = 1.0 - uv.y;

  vec4 d = texelFetch( data, ivec2( uv ), 0 );
  vec2 pos = vec2( d.g+d.b/255.0, d.a+d.r/255.0 ); // * 2. - 1.;
  
  int i = int( uv.y*4096.0 + uv.x );
  vec2 vel = (pcg4df( i, 324, 762, 2 ).xy - 0.5) * 0.2;
  vel += vec2( cos(2.*TAU*pos.y), sin(2.*TAU*pos.x) );
  if( mouse.z > 0. ) {
    vec2 v = fract(pos - mouse.xy + 0.5) - 0.5;
    vel += 1.*v / (.1+100.*pow(length(v),4.));
  }

  pos = mod( pos+vel * 0.001, 1.);

  outData = vec4(fract(pos*255.0), floor(pos*255.0)/255.0).yzxw;
}
"""};


String[] drawVertSrc = {"""
#version 330 core
precision highp float;
uniform int nParts;
uniform sampler2D data;
out vec3 col;
#define TAU 6.283185307179586

vec3 hsb2rgb( in vec3 c ) {
   vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
                            6.0)-3.0)-1.0,   0.0,   1.0 );
   rgb = rgb*rgb*(3.0-2.0*rgb);
   return c.z * mix( vec3(1.0), rgb, c.y);
}

void main() {
  vec4 d = texelFetch( data, ivec2( gl_VertexID%4096, textureSize( data, 0 ).y - 1 - gl_VertexID/4096 ), 0 );
  //vec4 d = texelFetch( data, ivec2( gl_VertexID%4096, gl_VertexID/4096 ), 0 );
  vec2 pos = vec2( d.g+d.b/256.0, d.a+d.r/256.0 ) * 2. - 1.;
  gl_Position = vec4( pos, 0., 1. );
  col = hsb2rgb( vec3(float(gl_VertexID)/float(nParts), 1., 1.) );
}
"""};

String[] drawFragSrc = {"""
#version 330 core
precision highp float;
in vec3 col;
out vec4 fragColor;
void main() {
  fragColor = vec4( col, 1. );
}
"""};
1 Like

@scudly Got it now - thanks for your patience helping me through this!
The problem turned out to be that I was ‘setting’ a uniform before binding the shader. (Not sure why the error message didn’t indicate the ‘setting’ line rather than the ‘binding’ line though). Anyway, simply reordering solved the problem. I previously posted ‘simplified’ code hoping this would highlight the essence of my problem, but in actuality this hid the misordering. Sorry!