2

I am writing a program that has a number of different views. One of which is fairly graphics intensive (it displays an interconnected graph). Others just display small but complex diagrams.

I'm finding the paint time for the main view is quite long (even just painting the currently visible area) and while it is being painted, the rest of the interface becomes very slow.

My question is this, can I create a new thread to handle the painting - and if so, will it result in a performance increase, my suspicion is that it wont. I have tried the following:

creating an abstract classs ThreadPaintablePanel, that my complex views inherit from.

public abstract class ThreadPaintablePanel extends JPanel{
 private Thread painter;
 public abstract void paintThread(Graphics g);

 protected void callPaintThread(Graphics g){
   if(painter != null){
     painter.interrupt();
   }
   painter = new Thread(new PaintRunnable(this, g));
   painter.start();
 }
} 

Then in my complicated views my paintComponent method is simply: super.callPaintThread(g);

The overridden paintThread method contains all my painting code. However this results in unpainted panels. Have I missed something obvious?

Thanks

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Zack Newsham
  • 2,810
  • 1
  • 23
  • 43

2 Answers2

8

You cannot let any thread but the Event Dispatch Thread (EDT) touch the GUI. Letting other threads mess with the GUI causes trouble and Exceptions. You can employ a multi-threaded multi-buffering technique. It involves two steps:

  1. In order to parallelize complicated drawing routines, you can simply divide the entire "view" into patches and let one thread draw one patch into one image. Here is a tutorial on working with images in Java.

  2. Once you have the images, you can override paintComponent and use the Graphics.drawImage method to let the EDT display the full or a partial view, by stitching the images of the corresponding patches together.

In order to avoid unnecessary work, make sure to perform the first step initially and then only after the view changed, else just draw previously computed results again. Furthermore, try to only update part of the patches, if you can narrow down the regions inside the views that have changed between frames.

Let us assume that your view is at least as many pixels high as your optimal number of threads, so we can just partition the view vertically. Also, let us assume that drawing any pixel takes about the same amount of work as any other, so we can use the same size for every patch. These two assumptions make things a lot easier.

Code follows. If you don't need your computer to do anything else, you can set nThreads to your number of cores. Note that the code also uses pseudocode for "parallel for" which is explained here:

// let multiple threads write all patches
public BufferedImage[] writePatches(...)
{
    // Given data:
    const int nThreads = ...;         // the amount of threads that you want
    Rectangle viewBox = ...;        // The view rectangle

    // Immediate data:
    Dimension viewSize = viewBox.getSize();
    int defaultPatchHeight = (int)ceil((float)viewSize.height / nThreads);
    int lastPatchHeight = viewSize.height - (nThreads-1) * defaultPatchHeight;

    // The actual "buffer" is a set of images
    BufferedImage[] images = new BufferedImage[nThreads];

    // ...

    // pseudocode for parallel processing of a for loop
    parallel-for (nThreads, threadId)
    {
        // the starting point and size of this thread's patch
        // handle boundary (last) patch
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        BufferedImage patch = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = off_Image.createGraphics();

        // use g to draw to patch image here
        // better yet: Re-use existing patches and only update the parts that changed.

        images[threadId] = patch;
    }
    return images;
}

// ...

// override paintComponent
@Override
void paintComponent(Graphics gg)
{
    Graphics2D g = (Graphics2D) gg;
    // ...

    // stitch all images together (you can also just display only some images here)
    for (int threadId = 0; threadId < nThreads; ++threadId)
    {
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        // use pre-computed images here
        BufferedImage patch = images[threadId];
        g.drawImage(patch, x, y, ...);
    }
}
Community
  • 1
  • 1
Domi
  • 22,151
  • 15
  • 92
  • 122
  • Excellent answer - I will mark it as correct as soon as I've tested it out. Is there a performance increase to be had from drawing in multiple images and then rendering as one? The reason I ask is that my (limited) understanding of threads, is that they only give a performance increase when IO (or some other blocking operation) is required. Why would rendering an image (essentially memory manipulation) meet this requirement? – Zack Newsham Dec 01 '13 at 03:39
  • 1
    Your question goes far beyond the scope of this thread. Your program is now [compute-bound (or CPU-bound)](http://stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound). The most basic summary: Running more than one thread allows you to throw more CPU cores at a problem that does not run fast enough on a single core. One thread cannot run on more than one core at a time. You will gain maximum performance when the amount of threads you have saturates all available cores. For problems with little communication overhead, that is when # threads == # cores. – Domi Dec 01 '13 at 03:45
  • I understand, this is what I thought would be the case. Thanks – Zack Newsham Dec 01 '13 at 03:55
  • Partitioning a 2D domain (such as the entire view) into patches of equal size needs a bit of getting used to. Also (as I just added to the answer), you need to pay special attention to the boundary patches. – Domi Dec 01 '13 at 04:14
  • @ZackNewsham If this is the solution to the problem, do you mind if I edit your question to make it more general? – Domi Dec 01 '13 at 04:49
  • feel free to edit the question - it certainly seems to be a solution (I havent tried the parallel part yet), it doesnt seem to have offered a performance increase as yet (I thought that this method allowed use of the graphics card?) but I am having an issue which is that I either have to block until the entire image is drawn, or the image that gets drawn is incomplete, I did look into the updateImage method, but it never gets called. – Zack Newsham Dec 01 '13 at 04:55
  • Great answer. Just wanted to add that the patches need not be of equal size - pick what makes sense. And even with an N of 1 you might see some speedup if the EDT is very busy with other stuff. Though obviously a higher N (up to a limit) is better. – user949300 Dec 01 '13 at 04:59
  • You can avoid incomplete images by simply not displaying anything until the images are all done. Also, you probably don't want to re-write your images every frame, unless you really have to. You can get rid of blocking by just using GUI-dependent values from the last frame. You cannot make use of the GPU with this yet, [maybe in the near future](http://blogs.nvidia.com/blog/2013/09/22/gpu-coming-to-java/). – Domi Dec 01 '13 at 05:00
  • @Domi, how do you not display something until the images are finished? Without holding up the EDT? – Zack Newsham Dec 01 '13 at 06:26
  • @ZackNewsham by just displaying the previous result. (For convenience you probably want to generate the buffer once before your first draw call.) – Domi Dec 01 '13 at 06:27
  • 1
    awwwww - man, I just figured out what you've been trying to say - I've been regenerating the buffered image every time I scroll, because I was only drawing the visible area each time, now I made it draw the whole thing once at the start, and once for each time something changes - the thing FLIES now, awesome, thanks! – Zack Newsham Dec 01 '13 at 06:33
  • 1
    @ZackNewsham can you post short executable example of what you get in result? I understand that years passed after the last time you were in this thread, but now I faced same problem and trying to increase fps in my java application. – SeniorJD Aug 31 '16 at 12:20
0

I think what you want to do is paint to a buffer in a non UI thread (so one you manage) and just copy the buffer across when done (in the UI thread).

Tom
  • 43,583
  • 4
  • 41
  • 61
  • 1
    Thanks for your super fast answer - though I am not sure what you mean regarding painting to a buffer - could you give me some sample code, or just some class/method names to lookup? – Zack Newsham Dec 01 '13 at 03:29