views:

173

answers:

2

I'm trying to squeeze some more performance out of my rendering pipeline. The (by far) slowest part is performing a java.awt.imaging.LookupOp on a large image.

The image size is about 2048x2048.

I have figured out that having the filter done along with the drawing operation is much faster than calling the filter method. However this still leaves us with lookup ops that take around 250ms (or 4 fps). Does anyone have any rendering tips?

Heres essentially what we are doing:

public void paint(Graphics g)
{
 if(recalcLUT)
 {
  Graphics2D g2d = (Graphics2D) displayImage.getGraphics();
  g2d.drawImage(srcImage, lut, 0, 0);
 }

 Graphics2D g2d = (Graphics2D) g;
 g2d.clearRect(0, 0, this.getWidth(), this.getHeight());
 AffineTransform at = new AffineTransform();
 at.setToIdentity();
 at.scale(scale, scale);
 g2d.drawImage(displayImage, at, null);
}

the lut variable is a LookupOp usually a ShortLookupOp, the image a 16bit grayscale image

Thanks

Ryan Bucher:

I know there are some other obvious performance optimizations that could be done here. But the major problem is just doing the LookupOp operation so im looking for advice with regard to that.

Lookup Ops are essentially where you create an array and instead of rendering each pixel of the image as its color, you use the color as an index into the array and render the color as the value in the index. In this particular example Im using it to do some simple brightness/contrast operations. You can also implement this using a rescaleOp which is essentially a way to apply a linear function to the value of all the pixels. But this turns out to be slower.

+1  A: 

I've not used java in close to eight years so some of the syntax may be irrelevant.

The key to any kind of performance regarding a loop is to push as many things as possible outside the loop. If you can't then only perform calculations when they change. Often it is better to wait until the last minute to recalculate so that you can cache multiple changes.

  1. Move all object construction outside of your render loop. If you know how many objects you need beforehand, then pass them in; if you don't then use a object pool and have the factory create objects outside of the renderloop. This will save you the construction/destruction time.

  2. Calculate your AffineTransform only when the scale changes. Push this outside the paint loop and pass it in (as a const reference... do they even exist in Java? I've been in C++ for too long)

  3. You may not need to call at.setToIdentity() as your AffineTransform should default to the Identity matrix (check this)

  4. Does recalcLUT need to get called every frame? Does it make sense to set recalcLUT to false after executing the if statement?

I'm not sure what the goal of the LookupOp is. If you give me a bit more information about what it is doing, I may be able to suggest more.

Hopefully this helps.

Ryan Boucher
A: 

Do you have the ability to push this processing on to the GPU? Multiple hardware pipelines will significantly speed up the processing.

If you can't then you should look into parallelising the LookupOp on the CPU. From what I understand of it each lookup can be done in isolation. Therefore you can spin up multiple threads (how many depends on your CPU Arch and whatever else is going on at the same time) and have each thread do a look up on a portion of the image. You will need a semaphore at the of each update run to wait for all threads to finish.

In either case you should do the following to trade off some memory consumption for every frame that doesn't have a change. Unless your framerate is capped then the more frames you have the more chance that nothing has changed. This will save you doing unnecessary work.

//Has filter or source image changed?
//   yes, recalculate filter and cache
//   no, render cached image

Finally, do you have visibility into the implementation of LookupOp? What is it doing, are there any performance enhancements that can be gleaned by writing the implementation yourself (this should be a last resort).

Ryan Boucher