1

I'm trying to render frames from a Processing sketch in a separate thread. Processing has a loadPixels() function. The catch is with the OpenGL renderer (P2D or P3D in Processing.): PGraphicsOpenGL loadPixels()

Here's my example sketch:

void setup(){
  size(300,300,P2D);
  noFill();
  background(255);
  rectMode(CENTER);
  stroke(0,32);
  smooth(8);
  // instantiate and start rendering thread
  new PNGRenderer(this);
}

void draw(){
  // draw moving shapes
  translate(width * 0.5, height * 0.5);
  rotate(sin(frameCount * 0.075));
  scale(sin(frameCount * 0.01));
  translate(cos(frameCount * 0.01) * width * 0.1,sin(frameCount * 0.01) * height * 0.1);
  rect(0,0,width * .9,height * .9);
}

public class PNGRenderer implements Runnable{

  PApplet parent;
  PImage  frame;
  boolean shouldSave = false;

  int savedFrameCount;

  boolean isRunning = true;

  PNGRenderer(PApplet parent){
    this.parent = parent;
    this.parent.registerMethod("draw",this);

    frame = createImage(parent.width,parent.height,ARGB);
    frame.loadPixels();

    Thread renderThread = new Thread(this);
    renderThread.setName("Renderer-Thread");
    renderThread.start();
  }

  public void draw(){
    // all is well if I sample pixels in the same OpenGL thread
    //parent.loadPixels();
    shouldSave = true;
  } 

  synchronized void sampleAndSavePixels(){
    if(shouldSave){
      // program crashes if I try to save in a separate thread
      parent.loadPixels();
      arrayCopy(parent.pixels,frame.pixels);
      frame.updatePixels();
      frame.save(dataPath("frames/frame_"+nf(savedFrameCount++,4)+".png"));
      println("saved frame",savedFrameCount);
      shouldSave = false;
    }
  }

  public void run(){
    while(isRunning){
      sampleAndSavePixels();
    }
  }

}

As far I understand (and I'm not an OpenGL wizard by far) reading pixels needs to be done in the same OpenGL thread. This is a slowdown I'm trying to avoid.

I did look a similar threads on stackoverflow and other forums:

As far a I understand from the above I need to create a separate OpenGL context in my renderer thread and share the texture/buffer from Processing's OpenGL thread to mine. I'm unsure on the syntax at the moment.

It might be a case of RTFM but can you please point me in the direction in terms of Java OpenGL reading pixels in a separate thread (can be plain Java, not necessarily using Processing)?

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • 1
    The [OpenGL Context](https://www.khronos.org/opengl/wiki/OpenGL_Context) is thread-local. The context has to be made "current" in the thread. The question is how to do this in Processing. – Rabbid76 Jun 27 '19 at 11:43
  • @Rabbid76 Please correct me if I'm wrong, the way I understand this is: I would make separate OpenGL Context in my thread and for each frame first pause processing's animation thread, then make the renderer thread's OpenGL Context current, somehow copy the buffer from the Processing context into the renderer context, resume's animation thread, while the renderer thread "downloads"/reads the pixels from the copied buffer in the renderer thread and saves the frame ? – George Profenza Jun 27 '19 at 12:47
  • You should ask this question on the official JogAmp forum. What are you trying to achieve? – gouessej Jun 28 '19 at 08:00
  • @gouessej I'm trying to save a png sequence of an animation generated using Processing. I've profiled my code and for 1080p frame rate drops from ~25 to ~15fps. It's not bad, but after profiling CPU usage I see `loadPixels()` takes quite a few millis to complete. I would like to move that operation in a separate thread, however it looks I need to jump through some GL hoops :) Gernot's suggestion sounds good. Do you have ideas I should try ? :D – George Profenza Jun 28 '19 at 09:50
  • Use com.jogamp.opengl.util.GLReadBufferUtil: https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/util/GLReadBufferUtil.html – gouessej Jul 02 '19 at 05:43

1 Answers1

1

The OpenGL Context is thread-local. The context has to be made "current" in the thread. Different threads can use the same context, but a context cannot be current in multiple threads at the same time.

You've to ensure that the threads don't use the same context at the same time. Of course you can create different contexts for different threads and share one context to the other.
See OPENGL MULTI-CONTEXT

But note, the GPU already operates parallel. There is no benefit of using the 1 GPU in different threads. The GPU can only handle the instructions of 1 thread at once. Of course each stage of the Rendering Pipeline process the vertices, fragments, etc. parallel

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Thank you(+1). If I understand correctly, I can use P2D's OpenGL Context from my renderer thread, but I need to make it "inactive" in P2D, and "current/active" in my thread, read pixels, then make the Context current in the P2D thread again ? If that is the case, wouldn't rendering have to pause on the P2D renderer while I loadPixels() from my thread, defeating the purpose of reading pixels in a separate thread ? I'm not 100% sure I understand which is why I'm double checking with you :) – George Profenza Jun 28 '19 at 09:53
  • @GeorgeProfenza A context cannot be current in multiple threads at the same time. *"wouldn't rendering have to pause on the P2D renderer while I loadPixels() from my thread, defeating the purpose of reading pixels in a separate thread ?"* - Yes it would. Note, the framebuffer is an object of the context. OpenGL has an instruction cache (per context) and only 1 instruction can be processed at the same time. Since OpenGL is a state engine (e.g. the current frambuffer is a state) the instructions have to be in a well defined order. – Rabbid76 Jun 28 '19 at 10:23
  • Here's an (uninformed) idea/question: Would I be able to use two OpenGL Contexts (P2D, thread), copy the GPU buffer from P2D to my thread, then have my thread read the pixels from the duplicate buffer? (my "am-i-overcomplicating-this-?" sense is tingling :))) ) – George Profenza Jun 28 '19 at 11:20
  • 1
    @GeorgeProfenza I think you want to improve the performance. The idea may work. But I've no idea how to do this in processing. I would know how to do this in native OpenGL. – Rabbid76 Jun 28 '19 at 11:53
  • Would you be able to share a basic OpenGL snippet ? I'll find my way through PGL :D – George Profenza Jun 28 '19 at 12:12