The Source for Java Technology Collaboration
User: Password:



Romain Guy

Romain Guy's Blog

Java2D Gradients Performance

Posted by gfx on September 26, 2006 at 05:07 PM | Comments (42)

I feel weird. My hands are sweaty and my throat is sore. My heart's beating too fast... What's happening to me?! Wait, I know. I haven't blogged here for the past 7 months! Dammit. Sorry for that folks.

Back to gradients. I use Java2D gradients a lot. I mean, A LOT. Most of the time, gradients work perfectly fine and performance are more than enough, especially when you honor the clipping rectangle. In some rare occasions, a gradients does take quite some time to be painted. This happens when you are painting large areas with a gradients as in the following screenshot:

Aerith

In this picture, the background gradient is about 700x500 pixels large. That's a lot of pixels. Since we were drawing full frame animations (60fps) on top of this gradient, the JVM wa a tad CPU hungry. We optimized this by painting the gradient in a buffer image that we use instead every time a repaint event occurs. Problem solved.

Or is it? Using a large picture was fine in this case because it was after all just a demo. But is it worth wasting 1,5MB or so of RAM? I think not. There's a very nifty trick you can use to optimize the drawing of large vertical and horizontal gradients: image resizing.

Instead of painting the gradient in a picture large enough to cover the area you want to repaint, paint it in a 1 pixel wide picture for vertical gradients or in a 1 pixel high picture for horizontal gradients. Then, at runtime, paint the picture with drawImage(image, x, y, w, h, null). By passing the size of the component to this method call, you will get the result you wanted quickly and without wasting memory.

Here is an example of how you can use this trick:

    @Override
    protected void instrumentedPaintComponent(Graphics2D g2) {
        if (cache == null || cache.getHeight() != getHeight()) {
            cache = new BufferedImage(2, getHeight(),
                BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = cache.createGraphics();
            
            GradientPaint paint = new GradientPaint(0, 0, Color.BLACK,
                0, getHeight(), Color.WHITE);
            g2d.setPaint(paint);
            g2d.fillRect(0, 0, 2, getHeight());
            g2d.dispose();
        }
        g2.drawImage(cache, 0, 0, getWidth(), getHeight(), null);
    }

But how does this trick compare to the two previous techniques? Well, here I have made surprising discoveries. I wrote a simple app to test the speed difference between the 3 approaches on Windows and Mac OS X. I first ran the tests on Windows at two different sizes (small gradients, about 200x200 pixels, versus large gradients, about 300x800) with various command line flags to change the rendering pipeline. I then ran the tests on Mac OS X at two different sizes. All tests were using Java SE 6 b88. On the following diagrams, smaller numbers (even negatives due to the logarithmic scales) are better.

Windows Performance (Small)

Windows Performance (Large)

Mac OS X Performance

From these diagrams, two things are for sure on Windows: images are faster than regular gradients and the new OpenGL pipeline in Mustang is freakingly good. On Mac OS X though, all techniques are roughly the same. The main advantages of pictures over regular gradients is that whatever rendering pipeline you use, the results are almost the same on Windows.

Even though using 1 pixel wide/tall pictures is a bit slower than full size pictures, this technique is the best in terms of speed and memory usage. At least for horizontal and vertical gradients.

P.S: By the way, this is only a benchmark that continuously paints gradients using all three techniques for a given number of seconds and finally spits out the time spent in each implementation. It might not (and surely does not) reflect real world use at all :)


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Nice post, this is indeed a useful technique. That's one more topic I can cross off my todo list; I love it when other people do my work for me :) What kind of GPU do you have in that MacBook Pro of yours? I assume it's an ATI something or other? I'm surprised D3D is beating OGL for things like simple image copies; I'll have to figure out what's going on there.
    Thanks, Chris.

    Posted by: campbell on September 26, 2006 at 05:39 PM

  • I have an ATI Radeon X1600 with 256 MB of VRAM in the MacBook Pro.

    Posted by: gfx on September 26, 2006 at 05:46 PM

  • Is 2nd diagram refer to large gradient performance on Windows? It make me confuse ;)

    Posted by: tmky2k on September 26, 2006 at 07:51 PM

  • Doh! Yes indeed. It's for the large gradients. I have updated the diagram accordingly ;-)

    Posted by: gfx on September 26, 2006 at 07:58 PM

  • Does the benchmark also say: 'Better don' use the OpenGL pipeline'?

    Posted by: herkules on September 26, 2006 at 11:47 PM

  • You might want to try running your benchmark with the -Dapple.awt.graphics.UseQuartz=true option, and see if the Quartz renderer shows different performance characteristics.

    Posted by: swingler01 on September 27, 2006 at 12:06 AM

  • Hi, I notice you show four pipelines in the graphs..what are the switches/methods to enable the different pipelines. I assume the opengl is sun.java2d.opengl=true, and "software" is simply the standard pipeline, but what about directdraw and direct3d? Cheers

    Posted by: fred34 on September 27, 2006 at 01:53 AM

  • Actually, DirectDraw is the standard pipeline. You can use sun.java2d.noddraw=true and sun.java2d.d3d=true.

    Posted by: gfx on September 27, 2006 at 04:25 AM

  • Good tips, and a must for large gradients (and any other vertically/horzontally repeating painting), I use them throughout my code as I sometimes have full screen gradients on a 1600 x 1200 display. There is also an additional technique for painting the same effect using TexturePaint:
    g2.setPaint(new TexturePaint(tallAndThinBuffer, new Rectangle(0, 0, 1 HEIGHT));
    g2.fillRect(0, 0, WIDTH, HEIGHT);
    
    It could also be an idea to test these painting methods on an image with an alpha channel (if you didn't do that already) as this can affect the performance of the different methods of painting in different ways.

    Posted by: mattnathan on September 27, 2006 at 05:43 AM

  • How does TexturePaint compare with using a stretched BufferedImage or gradient int terms of memory? I was under the impression that it was not particularly efficient.

    Posted by: fred34 on September 27, 2006 at 06:19 AM

  • I dont know anything about the memory performance of the TexturePaint but in a small test i just ran i found that the image stretching method of painting a LARGE area (2000x2000) constantly allocated more memory so much so that after 100 iterations it required the GC clean up 3 times due to lack of heap memory. Both the full gradient paint and the TexturePaint methods worked in constant memory as fas as my system monitor was concerned. From the simple case:
       public static void main(String[] args) {
          final int WIDTH = 2000;
          final int HEIGHT = 2000;     
          final int TIMES = 100;
          
          System.out.println("Testing image at " + WIDTH + "x" + HEIGHT + " (" + TIMES + " Times)");
          
          GradientPaint grad = new GradientPaint(0, 0, Color.BLACK, 0, HEIGHT, Color.WHITE); 
          
          BufferedImage dest = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
          Graphics2D destg = dest.createGraphics();
          
          long ns = 0, t = 0;
          
          // full paint test
          destg.setPaint(grad);
          ns = System.nanoTime();
          for (int i = 0; i < TIMES; i++) {
             destg.fillRect(0, 0, WIDTH, HEIGHT);
          }
          t = System.nanoTime() - ns;
          System.out.printf("Full Gradient Paint in %.3fns%n", t / (double) TIMES);
                
          // setup source image      
          BufferedImage source = new BufferedImage(1, HEIGHT, BufferedImage.TYPE_INT_ARGB);
          Graphics2D sourceg = source.createGraphics();
          sourceg.setPaint(grad);
          sourceg.fillRect(0, 0, 1, HEIGHT);
          sourceg.dispose();
    
          // stretch
          ns = System.nanoTime();
          for (int i = 0; i < TIMES; i++) {
             destg.drawImage(source, 0, 0, WIDTH, HEIGHT, null);
          }
          t = System.nanoTime() - ns;
          System.out.printf("Stretch Image Paint in %.3fns%n", t / (double) TIMES);
    
          // tile
          destg.setPaint(new TexturePaint(source, new Rectangle(0, 0, 1, HEIGHT)));
          ns = System.nanoTime();
          for (int i = 0; i < TIMES; i++) {
             destg.fillRect(0, 0, WIDTH, HEIGHT);
          }
          t = System.nanoTime() - ns;
          System.out.printf("Full Texture Paint  in %.3fns%n", t / (double) TIMES);
    
          destg.dispose();
       }
    
    I got the performance stats:
    java version "1.5.0_06"
    Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
    Java HotSpot(TM) Server VM (build 1.5.0_06-b05, mixed mode)
    
    Testing image at 2000x2000 (100 Times)
    Full Gradient Paint in 81367610.000ns
    Stretch Image Paint in 162612910.000ns
    Full Texture Paint  in 142554850.000ns
    
    Testing image at 200x200 (100 Times)
    Full Gradient Paint in 1363860.000ns
    Stretch Image Paint in 1648180.000ns
    Full Texture Paint  in 2500270.000ns
    
    NOTE: These use a INT_ARGB image, results for INT_RGB are as follows:
    Testing image at 2000x2000 (100 Times)
    Full Gradient Paint in 61894350.000ns
    Stretch Image Paint in 44667620.000ns
    Full Texture Paint  in 77432000.000ns
    
    Testing image at 200x200 (100 Times)
    Full Gradient Paint in 1167680.000ns
    Stretch Image Paint in 426080.000ns
    Full Texture Paint  in 1451760.000ns
    
    Not sure what this means in terms of real world cases but it seems that there is less of a performance gradient (no pun intended) for the full GradientPaint method than there is for the image stretching and TexturePaint options. Would be interesting to see at what size the lines cross

    Posted by: mattnathan on September 27, 2006 at 06:43 AM

  • Chris Campbell, from the Java2D team, kindly reminded me that timing the painting operation should be performed by calling Toolkit.sync() after each paint to ensure the drawing commands are flushed to the graphics card. My charts have been updated shortly after I posted this blog entry to reflect this missing piece. Could you update your benchmark and see if it makes any difference?

    Posted by: gfx on September 27, 2006 at 06:53 AM

  • Well, didnt realise you had to do that, what a diference a line makes. Adding the sync both removed the memory problem and made the results match the ones you show in your graphs. I guess I'd better go through my code and remove any useage of the TexturePaint tiling method.

    Testing image at 200x200 INT_RGB (100 Times) 
    Full Gradient Paint in     1882690.000ns
    Stretch Image Paint in      502000.000ns
    Full Texture Paint  in     1734610.000ns
    
    Testing image at 2000x2000 INT_RGB (100 Times) 
    Full Gradient Paint in    63098940.000ns
    Stretch Image Paint in    44769360.000ns
    Full Texture Paint  in    82898490.000ns
    
    Testing image at 200x200 INT_ARGB (100 Times) 
    Full Gradient Paint in     1915800.000ns
    Stretch Image Paint in      608820.000ns
    Full Texture Paint  in     1524270.000ns
    
    Testing image at 2000x2000 INT_ARGB (100 Times) 
    Full Gradient Paint in    62981050.000ns
    Stretch Image Paint in    45354120.000ns
    Full Texture Paint  in    83845190.000nss
    

    One thing that i also found while writing this test case was that if i performed all 4 tests one after another in the same JVM instance the INT_ARGB type tests performed better than the INT_RGB giving results more like this:

    Testing image at 200x200 INT_RGB (100 Times) 
    Full Gradient Paint in     1808660.000ns
    Stretch Image Paint in      460960.000ns
    Full Texture Paint  in     1781730.000ns
    
    Testing image at 2000x2000 INT_RGB (100 Times) 
    Full Gradient Paint in    63995800.000ns
    Stretch Image Paint in    44659490.000ns
    Full Texture Paint  in    82070070.000ns
    
    Testing image at 200x200 INT_ARGB (100 Times) 
    Full Gradient Paint in      662080.000ns
    Stretch Image Paint in      446660.000ns
    Full Texture Paint  in      877060.000ns
    
    Testing image at 2000x2000 INT_ARGB (100 Times) 
    Full Gradient Paint in    62850680.000ns
    Stretch Image Paint in    44535410.000ns
    Full Texture Paint  in    82438960.000ns
    

    Does anyone know what overhead/caching there is to give these results?

    Posted by: mattnathan on September 27, 2006 at 07:33 AM

  • hmm, I thought that texturedpaint would give these kinds of results. So it seems that stretching a 1px grad paint is way better than texturing. Also, from what I can remember, using the Graphics.drawImage(x,y,w,h,null) caches the scaled image, but the full version Graphics..drawImage(dx1,dy1,dx2,dy2,sx1,sy1,sx2,sy2,null) gives even faster results in all the tests I've done because it scales on the fly.

    Posted by: fred34 on September 27, 2006 at 08:22 AM

  • @mattnathan: Hmm, in your test code you're rendering to a BufferedImage, so I'd be really surprised if simply adding a call to Toolkit.sync() would affect your numbers. That method flushes the native pipelines, but if you're just rendering to a BufferedImage, then it should have no real effect. Romain on the other hand was rendering to the Swing backbuffer (a hardware surface), which is why Toolkit.sync() makes a difference.

    Re: "Does anyone know what overhead/caching there is to give these results?"

    That's probably just an effect of HotSpot (the Server VM) compiling more optimized code after crossing the compile threshold. When writing microbenchmarks, it often helps to include a few warmup runs and to run the test for a significant enough time to help reduce variance like what you're seeing.

    I'd suggest checking out J2DBench, our benchmarking tool for Java 2D. It already includes tests for Gradient/TexturePaint, and you could easily hack it to add tests for the "stretch image" approach. This would give you a reliable mechanism for comparing these scores, for both software and hardware paths.

    @fred34: Internally the two scaling variants of drawImage() you mention do the same thing. The former does not "cache the scaled image", nor does the latter.

    Thanks, Chris.

    Posted by: campbell on September 27, 2006 at 08:59 AM

  • 2mattnathan: a few comments regarding your benchmarking code.

    First, 100 iterations is way to small to measure anything meaningful - there's just too much variance. Also, we typically use a different technique for measuring (similar to what Romain used) - you first run the benchmaked code for some preset amount of time (like a couple of seconds) to measure roughly how many iterations it can do, then do another loop and time how much time it takes to make that many iterations - this makes the benchmark more predictable (you know exactly how long it would take).

    Second - you need to "warm up" the vm before doing the benchmarking by running your code for a while (a couple of seconds or more) and discarding the results.

    If you're interested, there's a Java2D benchmark in JDK sources you can get from http://jdk6.dev.java.net (it's in j2se/src/share/demo/java2d/J2DBench) - take a look at the graphcis benchmarking techniques it uses. We use this benchmark internally for measuring performance..

    Thanks,
    Dmitri
    Java2D Team

    Posted by: trembovetski on September 27, 2006 at 09:02 AM

  • Oops, was just about to post how fast the 'full' version was as I was getting results in the 4-60000 range, then realised that I had my source and destination dimensions the wrong way around :/

    Thanks to Romain and Chris for the Toolkit.sync() pointer and the non-caching drawImage version, you learn something new (and hopefully useful) every day.

    If only I could find the benchmark I used when I decided to use TexturePaint in my code. Oh well.

    Posted by: mattnathan on September 27, 2006 at 09:20 AM

  • @Chris: I will look into J2DBench in the future and using the techniques set out in there.

    @Dimitri: thanks for the hints about the timing, always just leave it at iterations (i usually do more iterations and some dry runs but am at work atm and should probably be spending my time doing something productive in python or some other inferior language ;-))

    Anyway, its home time for me now so thanks for the pointers and as ever proving that theres always something new to learn

    Posted by: mattnathan on September 27, 2006 at 09:31 AM

  • I just fiddled together a small micro-benchmark, which can be used to compare diagonal gradients with horizontal gradients. Here is the applet: http://www.randelshofer.ch/oop/graphics/index.html On the applet, below "Run individual tests" choose "fillRect 250 times with GradientPaint" or "fillRect 250 times with horiz. GradientPaint". Then choose the renderer "Java2D" and click at the Start button. On my MacBook, both tests run at about 5 to 6 frames per second with Java 5 using the Quartz rendering engine. I looked at class java.awt.GradientPaintContext, I noticed, that it does not appear to have a performance optimization for vertical and horizontal gradients. (It has very well done optimizations for diagonal gradients though!.) Just for fun, I implemented an optimization in the drawing pipeline for horizontal gradients. On the applet, below "Run individual tests" choose "fillRect 250 times with horiz. GradientPaint". Then choose the renderer "Single Threaded" and click at the Start button. On my MacBook, I am getting now kicking ass 50 frames per second. I don't know how common vertical and horizontal gradients are in general. But on Mac OS X they are quite often used. Doing a optimization in GradientPaintContent for these two special cases might give a nice performance boost to many applications. What do you think?

    Posted by: wrandelshofer on September 27, 2006 at 12:02 PM

  • Oh my. I forgot that I have to use HTML for line breaks.
    Btw, with "diagonal gradients" I mean all non-horizontal and non-vertical gradients. Not just the 45 degree special case.

    Posted by: wrandelshofer on September 27, 2006 at 12:04 PM

  • I know you won't listen, but just you can't say no one told you later. I know doing "cool" things is much more fun than keeping your house in order. But sometimes keeping the house in order is neccessary. Lets be blunt. The stuff you presented is a waste of time. It is procrastinating to avoid having to deal with some real, but boring, problems in the Java API.

    An example? Sure. When was the collection framework introduced? With Java 1.2, eight years ago. But still, until now Sun's Swing developers couldn't be bothered to adapt the existing Swing API to use these "new" interfaces. Compare

    http://java.sun.com/j2se/1.5.0/docs/api/java/util/class-use/Vector.html#javax.swing

    and

    http://java.sun.com/j2se/1.5.0/docs/api/java/util/class-use/List.html#javax.swing

    for example.

    So every day you force GUI developers to copy data into old-school Vectors and other ancient data structures just so it can be displayed.

    I wish you wold skip one stupid demo application, just one, one stupid piece of eye candy, I wish one Java Swing developer would get his or her arse up and finally do the homework first instead of writing yet another piece of junk code not relevant for almost all practical, real-world applications.

    Posted by: ewin on September 27, 2006 at 12:53 PM

  • ewin,

    It's very thoughful of you to use all those nice words about this blog entry but here is one thing you need to know:I am NOT a developer at Sun, working on Swing. I worked there for one year, that is true, as an intern.

    And guess what? I am now just a regular french student who'd rather say in bed rather than go to class every morning.

    So maybe you should do your homework before insulting other people's work :-)

    Posted by: gfx on September 27, 2006 at 03:50 PM

  • ewin - why be so angry in the wrong direction? Romain is no longer Sun employee, so... Did you apply for a contributor role on Mustang to make this happen? Are you planning to do it for Dolphin? Personally, i'd prefer bug fixing and support for transparent / arbitrarily shaped windows in Dolphin over refactoring of something that already works, although using some rarely-used API calls. Yes, in real life you don't use default models based on vectors. In real life you have your own models that allow deleting / moving / whatever.

    Posted by: kirillcool on September 27, 2006 at 04:03 PM

  • i'd prefer bug fixing and support for transparent / arbitrarily shaped windows in Dolphin

    Me too! (And I know Joshua Marinacci wants it as well :)

    Posted by: gfx on September 27, 2006 at 04:07 PM

  • Do you have any information on whether it's going to make it in and the amount of development needed on all supported platforms?

    Posted by: kirillcool on September 27, 2006 at 04:47 PM

  • None :( You should ask Joshua, he might know more about it.

    Posted by: gfx on September 27, 2006 at 04:51 PM

  • Yes. I admit, I really really like doing drawing code performance comparisons. :)

    So, I took now a look at the code of javax.awt.TexturePaintContext.
    Like java.awt.GradientPaintContext, this code is highly optimized for general cases, but it does not do a special treatment for the case when there is 1) no need to scale the texture image, 2) no need to do a scaling or rotating transformation.
    Again, I diddled up some comparison code for my applet. Choose "Run individual tests", choose Test "fillRect 250 times with TexturePaint" and then either Renderer "Java2D" or "Single Threaded" and then click at the "Start" button.
    On my MacBook with Java 1.5, I am getting 13-14 frames with Java2D, and 58-59 frames with the comparison code.
    On Mac OS X, texture paints are even more used than gradients (e.g. all window backgrounds are textured). In most cases, there is no need for scaling or rotating the texture tiles. So, unless my testing code is not lying to me, I should go and implement my own optimized TexturePaint and GradientPaint classes in order to speed up the special cases that are common on OS X.

    It would be nice, if someone at the Java2D team, would give a look at these two Paint classes as well. Maybe it is possible to implement optimizations for these special cases, without compromising the excellent general purpose optimizations that are already implemented in these classes.

    Posted by: wrandelshofer on September 27, 2006 at 10:48 PM

  • If I understand the charts right, stretching the one pixel image in itself has a time penalty on some pipelines/hardware (with the notable exception of Windows/large/Direct3D, where the scaled image seems a lot faster.) I wonder, what would be the effect of a 'halfway' solution: rather than going from one extreme (a full width buffer) to the other (a one pixel buffer), instead using a narrow buffer and tiling the background across the window using unscaled 'blits'.

    Would plotting ten unscaled 100 pixel wide tiles across a 800 pixel wide window be faster than stretching a 1 pixel wide image across the entire 800 pixels? What about if we used larger/fewer or narrower/more tiles? Is there a 'sweet-spot' where memory usage and performance are both satisfactory?

    My gut instinct tells me that, what with the speed with which modern hardware can scale and resample images, I doubt the fact that the tiles aren't scaled when drawn would matter -- indeed the performance would probably be quite poor in most circumstances. But I'd be curious to see some figures, particularly on how the solution scales (excuse the pun) in terms of memory and speed when the destination window grows to thousands of pixels wide/high.

    Posted by: javakiddy on September 28, 2006 at 03:07 AM

  • @javakiddy

    I wouldn't already search now for a 'sweet-spot'. IMO, its possible to squeeze out a lot of performance by optimizing the implementation of the Paint classes for this special case. An optimzed algorith will (hopefully) give much more consistent results accross different computer models, than a tradeoff that has been found by measuring performance on a specific computer model.

    A TexturePaint does currently not perform as well as a stretched image, because the current implementation of TexturePaintContext does not fully take advantage of textures that don't need scaling and rotating. (I have looked again at the code of TexturePaintContext, and found that there is indeed an optimization for this case, but it does appear to be much slower than the comparison code I made for my applet ).

    About the stretching of a 1 pixel wide image vs. a tiled image:
    An approach to stretch a 1 pixel wide image horizontally using C, is to perform for each scanline a "memfill()" with the color value of the 1 pixel wide image into the target image.

    An approach to apply a tiled image using C, is to perform for each scanline a "memcpy()" of a scanline of the tile into the target image. Here we can take advantage of redundancy in tiles, and say, after having done this with one row of tiles, we can efficiently memcpy() complete scanlines.

    memfill() is faster than memcpy(), so stretching ought to befaster than tiling. Unfortunately, Java does not support memfill(), that's why we have to use System.arraycopy in both cases.

    Depending on the graphics pipeline in use, the stretching or tiling might be implemented in Java, in C, or using a GPU programming language. All giving different results. That's why the 'sweet-spot' can differ a lot between Mac OS X and Windows, as well as between different computer models (e.g. my PC laptop has very different performance characteristics than my PC tower).

    Posted by: wrandelshofer on September 28, 2006 at 03:57 AM

  • I have implemented now an optimized version of GradientPaint.

    GradientPaintX only renders a single scanline for horizontal gradients, and then replicates this line using System.arraycopy.

    For vertical gradients, GradientPaintX only renders a single pixel column, and then progressively fills each scanline using System.arraycopy, i.e. performs filling by progressively doubling the number of pixels being copied.

    Run the applet on a computer that uses Sun's Java2D renderer. (Apple's Quartz renderer runs always at the same speed regardless of what algorithm I use - it appears to render gradients using its own gradient code, completely skipping the code in my class GradientPaintX). Choose the "Java2D" Renderer and then run the test "fillRect 250 times with horiz. GradientPaint", and the test "fillRect 250 times with horiz. GradientPaintX".

    GradientPaint yields on my MacBook about 7 frames per second, whereas the optimized GradientPaintX code yields about 20 frames per second. This is 3 times faster!

    There are also tests for vertical gradient paint speed comparison. Unfortunately the optimized code does not appear to make much of a difference. I need to work on it more.

    Overall, the performance gain using optimized Paint algorithms could even be higher, if there was an Interface, that could be implemented by PaintContext, that lets the graphics pipeline determine the number of horizontal and vertical pixels after which a Paint repeats. Run the "fillRect 250 times with horiz. GradientPaint" tests with the "Single Threaded" renderer. This renderer optimizes for vertical repeats in a Paint. It yields 56 frames per second. This is 8 times faster!

    Posted by: wrandelshofer on September 28, 2006 at 12:34 PM

  • I didn't notice you are no longer working for Sun. You still exebit the typical Sun GUI developer attitude: We don't care of the real world. As kirillcool has also been so kind to demonstrate. So the rest of this posting will address kirillcool.

    To make it clear, kirillcool, you have no fscking clue about real-live Java applications. Just take JComboBox as an example. If you think people should use explicit models, or even implement ComboBoxModel, to get data into a JComboBox you are very wrong In real life you have your own models? HA, HA, HA!

    I have not singned up for any of the Sun developer licenses, because I am not stupid. I have written about the details elsewhere. As long as Sun is having these brain-dead contracts which would require me to hire a lawyer to get it sorted out I will not consider it.

    Further, I am not that stupid to work for Sun for free. It is your job, not mine, to make desktop Java a success. Sun pays you, not me. If you want to know one reason why desktop Java isn't, it is Sun's lack of attention to detail. For Pete's sake, drop your arrogant attitude, get the details right and relieve desktop developers from the daily pain and horror of having to work with the Swing, and 2D (and AWT) APIs.

    Posted by: ewin on September 29, 2006 at 02:07 PM

  • ewin: You are still being insulting for the wrong reasons. Kirill does not work for Sun either. Kirill and I are both using *a lot* of our spare time trying to help people who are using Swing and Java2D for a living. This is not our job. If you don't like what we're doing, fine by me. Just stop being ignorant and demandin, we owe you nothing.

    So I guess Kirill and I are both stupid working for Sun and other people for free. I think we need more stupid people then.

    Posted by: gfx on September 29, 2006 at 02:17 PM

  • @kirill and @gfx

    Thanks for posting these interesting blogs and code. Though they don't fill the mundane need of having better List integration with Swing, it is still a good thing that there are folks pushing things in new directions.

    leouser

    Posted by: leouser on September 29, 2006 at 04:19 PM

  • I did some more work on gradient paint and texture paint on my applet.

    I still can't implement a gradient paint that draws a vertical gradient faster than java.awt.GradientPaint. To get it faster, I would have to do an optimization further down the graphics pipeline. Probably doing what Romain suggested: rendering a one pixel wide gradient, and then stretching it horizontally. So I think, Romain did indeed hit a sweet point here! :)

    With texture paint, its an unfortunate story. Apparently, providing a subclass of java.awt.TexturePoint to the Sun Java2D rendering engine, makes it choose a very slow rendering path. I am getting down from 12 fps to 1 fps with my fillRect test. And, as with subclasses of GradientPaint, Apple's Quartz rendering engine happily ignores subclasses and does all the rendering on its own.
    Luckily, I have got my own pure Java implementation of Graphics2D ;). Optimizing the implementation of java.awt.TesturePaint with more calls to System.arraycopy makes it jump from 17 fps to 32 fps. And then, making the rendering pipeline aware of when a Paint repeats vertically, makes it leap to 76 fps. So, I am concluding, that one should not mess with TexturePaint unless one is willing to fully implement a Graphics2D pipeline on ones own. :(

    Posted by: wrandelshofer on September 30, 2006 at 02:37 AM

  • ewin - ??? The job of making Java desktop a success if both Sun's, mine and yours (if you want of course). As Romain said, i'm not a Sun employee (nor i ever was), so Sun doesn't pay me - this is done on my own free time instead of other things i might be doing. The issue of combobox "real-world" models is irrelevant here. I'm saying that there are things that don't work properly and they should be fixed before refactoring things that do work. My guess is that this is lower priority for Sun's engineers as well. So - if you don't want to "waste" your time by helping fixing something that bothers you, it's your choice. And nobody says that you should work for Sun for free - if your fix makes it in JDK, then you get what you wanted without making hoops in your code. If you hate Sun and Java so much - why not use some other language, and if the boss doesn't let you - why not change the job to something more fulfilling and less aggravating? Who's forcing you - we're not in a prison, you know :)

    Posted by: kirillcool on September 30, 2006 at 05:42 PM

  • @erwin: My friend, you look like a Window$ user posting in a Linux forum! If you do-not like this, why are you here? As a Linux user, I'm tired of poser posting things like "Linux is so difficult, I prefer the simply things..." And of course, I r-e-a-l-l-y dont care 'bout real-world... I'm tired of the "real-world", really tired... And can you answer me this question: Why Java is growing so high that it will touch the sky in any moment? H-e-l-l-o... 'Cause there are people like Romain, or Richard or Joshy that use their spare time to do great things for the community of developers!! Well, lets do really important things... I'm testing the BasicGradientPainter and I think it's GREAT! I've tested the static methods in the BasicGradientPainter, and I have to say that you are doing a kick-a** job! Excellent! I was using the SwingX since august (I'm a baby :-D), and I'm really, really beginner with this wonderful lib... I read above you talk in the third paragraph 'bout buffering the image, can you tell me how can you buffer the gradient image? Thanks in advance my friend!! Keep on rockin'!!!

    Posted by: templarknight on October 10, 2006 at 10:30 AM

  • templatknight: Buffering a gradient is done this way:

    // 1 pixel wide buffer for a vertical gradient
    BufferedImage buffer = new BufferedImage(1, getHeight(), BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = buffer.createGraphics();
    g2.setPaint(myGradient);
    g2.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
    g2.dispose();
    

    Then to draw this buffer onto your component:

    protected void paintComponent(Graphics g) {
      // stretches the picture
      g.drawImage(buffer, 0, 0, getWidth(), getHeight());
    }
    

    Posted by: gfx on October 10, 2006 at 10:35 AM

  • Hey Romain! I just want to point out another major performace improvement that can be made for many instances of using GradientPaint. GradientPaint has a "cyclic" property that controls whether or not the gradient cycles between the two colors. This property defaults to false.

    In the majority of cases, however, the visual behavior of GradientPaint is not affected by the value of this property. In particular, if the GradientPaint specifies an area that fully covers the area you're rendering to, then it doesn't have a chance to cycle.

    With this in mind, I'll introduce the fact that GradientPaint performs much faster with the cyclic property set to true! I've spoken with a Java 2D team member to discover the reason for this and it turns out that it's simply a matter of the difference between a simple lookup (cyclic) and constantly checking to see if you're past the area defined in the GradientPaint (non-cyclic). And it can be a major difference.

    Consider this simple test case that fills a 1024x768 area 500 times using both a non-cyclic and then a cyclic GradientPaint. REMEMBER, the visual result is the same:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.*;
    
    public class GradientTest {
        public static void main(final String[] args) {
            BufferedImage image =
                new BufferedImage(1024, 768,
                                  BufferedImage.TYPE_INT_RGB);
    
            Graphics2D g2d = image.createGraphics();
    
            long time = System.currentTimeMillis();
            
            for (int i = 0; i < 500; i++) {
                // non-cyclic
                GradientPaint gp
                    = new GradientPaint(0, 0, Color.RED,
                                        1024, 768, Color.BLUE);
                g2d.setPaint(gp);
                g2d.fillRect(0, 0, 1024, 768);
            }
            
            System.out.println("Using non-cyclic: " +
                               (System.currentTimeMillis() - time) +
                               "ms");
    
            time = System.currentTimeMillis();
            
            for (int i = 0; i < 500; i++) {
                GradientPaint gp
                    = new GradientPaint(0, 0, Color.RED,
                                        1024, 768, Color.BLUE,
                                        // specify cyclic
                                        true);
                g2d.setPaint(gp);
                g2d.fillRect(0, 0, 1024, 768);
            }
            
            System.out.println("Using cyclic: " +
                               (System.currentTimeMillis() - time) +
                               "ms");
            
            g2d.dispose();
        }
    }
    

    On my machine the results are:
    Using non-cyclic: 6969ms
    Using cyclic: 3813ms

    Over 3 seconds difference! Conclusion: For the most common uses, the value of the "cyclic" parameter doesn't change the visual output. But it's a huge performance win. As such, always consider explicitly setting it to true.

    Posted by: shan_man on October 17, 2006 at 01:45 PM

  • The more I read about Swing, Blogs, Advanced UI, .. the more I think we need to have a single place for all the tricks & tips for advanced UI. Why not having a section in the Java Tutorial for this? I find it not easy to look for some tips & tricks if I want to do something fancy, "extreme list", "extreme table", "jogl + swing", .... Swing is facing competition, what we now need is clarity. So my suggestion is to start to add an "Extreme UI" section to the Java Tutorial - like a preview of an upcoming book on Extreme Swing GUI

    Posted by: janaudy on October 18, 2006 at 07:05 AM

  • Shannon: You just made my day. :)
    That's a very neat and easy to use performance tip. I think this nicely complements Romains stretching image tip. I think I will go through all my gradient rendering code and apply these practices.
    Oh my. I like this so much. :)

    Posted by: wrandelshofer on October 18, 2006 at 01:48 PM

  • Hi Romain, im trying to hunt down any information about building AWT or SWING java apps in non square frames. Im interested in building a little application that is round, and not constrained to a box. I still want it to behave like a normal desktop window, and be draggable, minimize etc. I was wondering if you could point me in the right direction. Sorry if this is not related to your blog post, but it was the only way I could get in contact with you. Nice work with all your GUI modifications as well.

    Posted by: catalyst on November 10, 2006 at 07:34 AM

  • catalyst: You'll need native code for that :( It's not a lot of code mind you, but still, you can't do that in pure Java. The look and feel SkinLF contains a class that does that for you by the way. Check it out.

    Posted by: gfx on November 10, 2006 at 07:38 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds