Obsah
1. Vykreslení bitmap typu BufferedImage
2. Demonstrační příklad – vykreslení bitmapy typu BufferedImage do okna
3. Výsledky běhu demonstračního příkladu na systému Linux (X)
5. Proč je práce s některými bitmapami výrazně pomalejší?
6. Zdrojový kód nativní funkce sun.java2d.windows.GDIBlitLoops()
7. „Kompatibilní“ bitmapy typu BufferedImage
10. Možné stavy bitmap typu VolatileImage
11. Repositář se zdrojovými soubory dnešního demonstračního příkladu
1. Vykreslení bitmap typu BufferedImage
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se budeme zabývat problematikou vykreslování bitmap na obrazovku. Částečně jsme se tímto problémem zabývali již minule a předminule při popisu možností, jak zapisovat pixely do bitmap i při vysvětlování principu takzvaných exkluzivních celoobrazovkových grafických režimů. Ve skutečnosti se ovšem problém vykreslování bitmap týká i aplikací, které svůj grafický výstup provádějí do běžného okna a nikoli v celoobrazovkovém režimu (fullscreen mode).
Především z historických důvodů je v Javě možné pracovat s rastrovými obrázky (bitmapami) třemi různými způsoby založenými na třech modelech. Nejstarší je takzvaný „push model“ založený na třídách a rozhraních java.awt.Image (abstraktní třída), java.awt.image.ImageProducer, java.awt.image.ImageConsumer a java.awt.image.ImageObserver. Tento model byl navržen především s ohledem na to, že celé obrázky (velké bitmapy) nemusí být vždy dostupné již ve chvíli, kdy dochází k jejich konstrukci, typicky z toho důvodu, že se pixely tvořící obrázek začínají přenášet po síti.
Pro potřeby her je však tento model zbytečně složitý, takže se většinou dává přednost takzvanému „immediate modelu“ založeného především na třídě java.awt.image.BufferedImage. Tento model je na použití nejjednodušší a navíc mohou být některé operace s bitmapami akcelerovány, což je problematika vysvětlená v dalších kapitolách. Existuje ještě třetí „pull model“, kterým se budeme zabývat někdy příště.
Instance třídy java.awt.image.BufferedImage mohou reprezentovat obrázky libovolných rozměrů a několika typů, v závislosti na tom, jakým způsobem je bitmapa vytvořena. Tu lze načíst z externího souboru s využitím statické metody javax.imageio.ImageIO.read(), vytvořit prázdnou bitmapu přes konstruktor java.awt.image.BufferedImage(parametry) či je možné (což je, jak si ukážeme, mnohdy nejvýhodnější) využít jednu z metod java.awt.GraphicsConfiguration.createCompatibleImage(int, int) či java.awt.GraphicsConfiguration.createCompatibleImage(int, int, int). Důležité je, že způsob vzniku bitmapy má velký vliv na to, jak rychle bude bitmapa vykreslována na obrazovku.
2. Demonstrační příklad – vykreslení bitmapy typu BufferedImage do okna
Způsob tvorby a následného vykreslování bitmap typu BufferedImage si ukážeme na dnešním (jediném) demonstračním příkladu, v němž je nejdříve rastrový obrázek vytvořen buď konstruktorem new BufferedImage(šířka, výška,typ) nebo voláním metody java.awt.GraphicsConfiguration.createCompatibleImage(šířka,výška). Rozměry bitmapy i způsob jejího vytvoření lze řídit konstantami IMAGE_WIDTH, IMAGE_HEIGHT a CREATE_COMPATIBLE_IMAGE. Do bitmapy se vykreslí jednoduchý obrazec a následně je vytvořeno okno bez dekorací (okrajů, titulkového pruhu, ikon) a do tohoto okna je bitmapa ve smyčce vykreslena v překryté metodě public void paint(Graphics g), která existuje pro každou komponentu grafického uživatelského rozhraní (zavoláním metody super.paint() je mj. zaručeno vymazání původního obsahu okna barvou pozadí – ostatně můžete se sami pokusit tuto metodu nevolat, což se někdy ve hrách skutečně využívá).
Výpis zdrojového kódu dnešního demonstračního příkladu:
import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.WindowConstants; /** * Jednoduchy test metody Graphics.drawImage(). */ public class BlitTest1 extends JFrame { /** * Generated serial version UID. */ private static final long serialVersionUID = -6455857608125974217L; /** * Ma se volat metoda super.paint(Graphics)? */ private static final boolean CALL_SUPER_PAINT = true; /** * Ma se pouzit GraphicsConfiguration().createCompatibleImage() * namisto new BufferedImage()? */ private static final boolean CREATE_COMPATIBLE_IMAGE = false; /** * Sirka testovaciho obrazku. */ private static final int IMAGE_WIDTH = 512; /** * Vyska testovaciho obrazku. */ private static final int IMAGE_HEIGHT = 512; /** * Sirka okna. */ private static final int FRAME_WIDTH = 500; /** * Vyska okna. */ private static final int FRAME_HEIGHT = 500; /** * Obrazek, ktery se bude prenaset do okna. */ BufferedImage image; /** * Horizontalni souradnice obrazku. */ private int imageX; /** * Vertikalni souradnice obrazku. */ private int imageY; /** * Pocet skutecnych volani metody paint() */ private int repaintCount = 0; /** * Spusteni testu. */ private void run() { createImage(); showJFrame(); runBenchmark(); this.dispose(); System.out.println("Repaint count: " + this.repaintCount); System.exit(0); } /** * Spusteni vlastniho benchmarku. */ private void runBenchmark() { for (int y = 0; y < FRAME_HEIGHT; y += 10) { for (int x = 0; x < FRAME_WIDTH; x += 10) { setImageX(x); setImageY(y); repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Vytvoreni testovaciho obrazku. */ private void createImage() { if (CREATE_COMPATIBLE_IMAGE) { this.image = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT); } else { this.image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_3BYTE_BGR); } drawIntoImage(this.image); } /** * Vykresleni vzorku do obrazku. */ private void drawIntoImage(BufferedImage image) { final int width = image.getWidth(); final int height = image.getHeight(); Graphics2D g = image.createGraphics(); // smazeme pozadi obrazku g.setColor(Color.white); g.fillRect(0, 0, width-1, height-1); // vykresleni cerveneho ctverce g.setColor(Color.red); g.drawRect(0, 0, width-1, height-1); // vykresleni krize g.setColor(Color.blue); g.drawLine(width >> 1, height/3, width >> 1, 2*height/3); g.drawLine(width/3, height >> 1, 2*width/3, height >> 1); g.dispose(); } /** * Nastaveni parametru okna a zobrazeni. */ private void showJFrame() { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(100, 100); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.setVisible(true); } @Override public void paint(Graphics g) { if (CALL_SUPER_PAINT) { super.paint(g); } final BufferedImage img = getImage(); final int imageX = getImageX() - img.getWidth()/2; final int imageY = getImageY() - img.getHeight()/2; g.drawImage(img, imageX, imageY, null); g.dispose(); this.repaintCount ++; } /** * Nastaveni horizontalni souradnice obrazku. * * @param x */ void setImageX(int x) { this.imageX = x; } /** * Nastaveni vertikalni souradnice obrazku. * * @param y */ void setImageY(int y) { this.imageY = y; } /** * Getter pro atribut imageX. */ int getImageX() { return this.imageX; } /** * Getter pro atribut imageY. */ int getImageY() { return this.imageY; } /** * Getter pro atribut image. */ BufferedImage getImage() { return this.image; } /** * Spusteni testu. * @param args */ public static void main(String[] args) { new BlitTest1().run(); } }
3. Výsledky běhu demonstračního příkladu na systému Linux (X)
U dnešního demonstračního příkladu nás bude zajímat především informace o tom, které metody a nativní funkce jsou spuštěny při vykreslování bitmap. Tuto informaci lze zjistit mnoha způsoby, přičemž nejjednodušší bude využití profileru, jenž je přímo součástí virtuálního stroje Javy. Aby byl profiler spuštěn, postačuje při startu JVM zadat přepínač -Xprof, který způsobí, že si virtuální stroj bude vytvářet statistiku volání metod a nativních funkcí pro každé vlákno aplikace a následně se (těsně před ukončením JVM) tyto informace vypíšou na standardní výstup. Podívejme se nyní, jaké informace vypíše profiler při spuštění našeho demonstračního příkladu na Linuxu s X Window:
Výsledky pro OpenJDK6 (Ubuntu):
Flat profile of 26.14 secs (2514 total ticks): AWT-EventQueue-0 Interpreted + native Method 23.2% 0 + 22 sun.awt.X11.XInputMethod.openXIMNative 7.4% 0 + 7 sun.misc.Unsafe.unpark 5.3% 0 + 5 sun.java2d.loops.Blit.Blit 2.1% 2 + 0 java.util.ArrayList.remove 2.1% 2 + 0 javax.swing.LayoutFocusTraversalPolicy.accept 2.1% 2 + 0 java.util.HashMap.removeEntryForKey 1.1% 0 + 1 java.lang.Object.clone 1.1% 0 + 1 java.lang.Class.getComponentType 1.1% 0 + 1 sun.awt.X11.XlibWrapper.XFlush 1.1% 1 + 0 javax.swing.JLayeredPane.isOptimizedDrawingEnabled 1.1% 1 + 0 javax.swing.JFrame.getGraphics 1.1% 1 + 0 java.awt.im.InputContext.getInstance 1.1% 1 + 0 sun.java2d.x11.X11PMBlitLoops.nativeBlit 1.1% 1 + 0 java.util.ArrayList.rangeCheck 1.1% 1 + 0 sun.reflect.Label.<init> 1.1% 1 + 0 sun.reflect.UnsafeFieldAccessorImpl.ensureObj 1.1% 1 + 0 java.util.HashSet.remove 1.1% 1 + 0 java.util.ArrayList.add 1.1% 1 + 0 sun.awt.ComponentAccessor.getForeground 1.1% 1 + 0 sun.awt.im.CompositionAreaHandler.<clinit> 1.1% 1 + 0 java.util.ArrayList.<init> 1.1% 1 + 0 javax.swing.RepaintManager$ProcessingRunnable.run 1.1% 1 + 0 java.awt.EventQueue.getAccessControlContextFrom 1.1% 1 + 0 java.awt.Container.paint 1.1% 1 + 0 javax.swing.BufferStrategyPaintManager$BufferInfo.getBufferStrategy 75.8% 35 + 37 Total interpreted (including elided) Compiled + native Method 3.2% 2 + 1 java.awt.EventQueue.getNextEvent 1.1% 1 + 0 sun.java2d.pipe.ValidatePipe.validate 1.1% 1 + 0 sun.java2d.SurfaceData.pixelFor 1.1% 1 + 0 sun.java2d.x11.X11SurfaceData$X11WindowSurfaceData.getBounds 1.1% 1 + 0 java.util.concurrent.ConcurrentHashMap.get 1.1% 0 + 1 java.awt.Component$BltBufferStrategy.showSubRegion 1.1% 1 + 0 javax.swing.JComponent.paintChildren 9.5% 7 + 2 Total compiled Stub + native Method 4.2% 0 + 4 java.lang.Object.clone 3.2% 0 + 3 java.security.AccessController.getStackAccessControlContext 3.2% 0 + 3 sun.java2d.x11.X11Renderer.XFillRect 10.5% 0 + 10 Total stub Thread-local ticks: 96.2% 2419 Blocked (of total) 4.2% 4 Class loader Flat profile of 26.21 secs (2518 total ticks): AWT-Shutdown Stub + native Method 100.0% 0 + 1 java.lang.Object.wait 100.0% 0 + 1 Total stub Thread-local ticks: 100.0% 2517 Blocked (of total) Flat profile of 26.77 secs (2554 total ticks): AWT-XAWT Interpreted + native Method 99.0% 0 + 2497 sun.awt.X11.XToolkit.waitForEvents 0.4% 0 + 9 sun.awt.X11.XlibWrapper.XGetInputFocus 0.1% 0 + 3 sun.awt.X11.XlibWrapper.getScreenOfWindow 0.0% 0 + 1 sun.misc.Unsafe.unpark 0.0% 1 + 0 sun.reflect.NativeMethodAccessorImpl.invoke0 0.0% 1 + 0 sun.awt.X11.XToolkit.getDisplay 0.0% 1 + 0 java.util.Vector.iterator 0.0% 1 + 0 java.util.logging.Logger.finest 0.0% 1 + 0 sun.awt.X11.XWindowPeer.removeTransientForHint 0.0% 1 + 0 java.lang.Class.searchMethods 99.8% 6 + 2510 Total interpreted Thread-local ticks: 1.3% 33 Blocked (of total) 0.2% 5 Class loader Flat profile of 27.41 secs (2606 total ticks): Java2D Disposer Thread-local ticks: 100.0% 2606 Blocked (of total) Flat profile of 28.04 secs (2648 total ticks): main Interpreted + native Method 6.7% 0 + 9 java.lang.ClassLoader$NativeLibrary.load 5.2% 0 + 7 java.io.UnixFileSystem.checkAccess 5.2% 0 + 7 java.io.FileInputStream.readBytes 4.5% 0 + 6 java.io.UnixFileSystem.canonicalize0 3.0% 0 + 4 sun.font.FontManager.getFontConfig 1.5% 2 + 0 java.awt.Component.initIDs 1.5% 0 + 2 java.lang.ClassLoader.findBootstrapClass 1.5% 2 + 0 java.lang.Class.forName0 1.5% 0 + 2 java.util.zip.ZipFile.read 1.5% 2 + 0 java.util.Arrays.mergeSort 0.7% 0 + 1 sun.java2d.loops.GraphicsPrimitiveMgr.registerNativeLoops 0.7% 0 + 1 sun.awt.X11.XlibWrapper.XSupportsLocale 0.7% 0 + 1 sun.font.FontManager.getFontPath 0.7% 0 + 1 sun.font.NativeFont.fontExists 0.7% 0 + 1 java.lang.Object.hashCode 0.7% 0 + 1 java.lang.Class.getClassLoader0 0.7% 0 + 1 sun.java2d.pipe.SpanClipRenderer.initIDs 0.7% 1 + 0 sun.awt.X11GraphicsConfig.pGetBounds 0.7% 0 + 1 java.lang.Class.getDeclaredMethods0 0.7% 0 + 1 java.lang.Class.getDeclaredFields0 0.7% 0 + 1 java.lang.ClassLoader$NativeLibrary.find 0.7% 0 + 1 sun.misc.Unsafe.unpark 0.7% 0 + 1 java.lang.Thread.sleep 0.7% 1 + 0 java.net.URL.getRef 0.7% 1 + 0 java.lang.ref.WeakReference.<init> 82.8% 61 + 50 Total interpreted (including elided) Compiled + native Method 0.7% 1 + 0 java.io.UnixFileSystem.normalize 0.7% 1 + 0 Total compiled Thread-local ticks: 94.9% 2514 Blocked (of total) 16.4% 22 Class loader Global summary of 28.04 seconds: 100.0% 2655 Received ticks 0.2% 6 Received GC ticks 3.2% 84 Compilation 1.2% 31 Class loader
Vlastní vykreslování je zde realizováno v následujících blocích:
5.3% 0 + 5 sun.java2d.loops.Blit.Blit 1.1% 1 + 0 sun.java2d.x11.X11PMBlitLoops.nativeBlit
Vymazání plochy okna zajišťuje metoda:
3.2% 0 + 3 sun.java2d.x11.X11Renderer.XFillRect
Výsledky pro OpenJDK7 (Fedora 19):
Flat profile of 25.62 secs (2519 total ticks): AWT-EventQueue-0 Interpreted + native Method 13.3% 0 + 27 java.lang.Object.clone 10.3% 0 + 21 sun.java2d.x11.XSurfaceData.XSetClip 8.4% 0 + 17 sun.java2d.x11.X11Renderer.XFillRect 6.9% 0 + 14 java.lang.Object.hashCode 5.9% 5 + 7 java.security.AccessController.doPrivileged 5.9% 0 + 12 java.lang.Object.getClass 5.4% 0 + 11 java.security.AccessController.getStackAccessControlContext 4.9% 8 + 2 sun.java2d.x11.X11PMBlitLoops.nativeBlit 2.5% 5 + 0 java.util.ArrayList.<init> 2.0% 0 + 4 java.lang.Class.getInterfaces 2.0% 0 + 4 java.lang.System.identityHashCode 1.5% 0 + 3 sun.awt.X11.XInputMethod.openXIMNative 1.0% 0 + 2 java.lang.reflect.Array.newArray 1.0% 0 + 2 sun.misc.Unsafe.compareAndSwapInt 1.0% 0 + 2 sun.misc.Unsafe.putObject 1.0% 2 + 0 java.util.Arrays.copyOf 1.0% 2 + 0 javax.swing.JComponent.paint 0.5% 0 + 1 java.lang.Thread.isInterrupted 0.5% 0 + 1 java.lang.Thread.currentThread 0.5% 0 + 1 java.security.AccessController.getInheritedAccessControlContext 0.5% 0 + 1 java.lang.Class.getComponentType 0.5% 1 + 0 java.util.concurrent.atomic.AtomicInteger.get 0.5% 0 + 1 sun.misc.Unsafe.park 0.5% 1 + 0 sun.java2d.pipe.Region.getLoX 0.5% 0 + 1 sun.java2d.loops.Blit.Blit 81.3% 31 + 134 Total interpreted (including elided) Stub + native Method 9.9% 0 + 20 java.lang.Thread.currentThread 5.4% 0 + 11 java.lang.Object.clone 3.4% 0 + 7 java.lang.Object.hashCode 18.7% 0 + 38 Total stub Thread-local ticks: 91.9% 2316 Blocked (of total) Flat profile of 25.63 secs (2520 total ticks): AWT-Shutdown Thread-local ticks: 100.0% 2520 Blocked (of total) Flat profile of 25.67 secs (2524 total ticks): AWT-XAWT Interpreted + native Method 99.8% 1 + 2514 sun.awt.X11.XToolkit.waitForEvents 0.1% 0 + 3 sun.awt.X11.XlibWrapper.XEventsQueued 0.0% 0 + 1 sun.awt.X11.XlibWrapper.XGetInputFocus 0.0% 0 + 1 sun.awt.X11.XlibWrapper.XFilterEvent 0.0% 1 + 0 java.awt.EventDispatchThread.<init> 100.0% 2 + 2519 Total interpreted Thread-local ticks: 0.1% 3 Blocked (of total) Flat profile of 25.68 secs (2525 total ticks): Java2D Disposer Thread-local ticks: 100.0% 2525 Blocked (of total) Flat profile of 25.74 secs (2530 total ticks): main Interpreted + native Method 26.7% 0 + 8 java.lang.System.identityHashCode 13.3% 0 + 4 java.lang.Object.hashCode 6.7% 0 + 2 java.security.AccessController.getStackAccessControlContext 6.7% 0 + 2 sun.misc.Unsafe.compareAndSwapObject 3.3% 0 + 1 java.lang.ClassLoader$NativeLibrary.load 3.3% 0 + 1 sun.misc.Unsafe.unpark 3.3% 0 + 1 sun.awt.X11.XlibWrapper.XSupportsLocale 3.3% 0 + 1 java.security.AccessController.getInheritedAccessControlContext 3.3% 0 + 1 java.lang.System.currentTimeMillis 3.3% 1 + 0 sun.java2d.Disposer.addRecord 3.3% 1 + 0 java.util.Arrays.copyOfRange 3.3% 1 + 0 javax.swing.JFrame.frameInit 3.3% 1 + 0 sun.java2d.loops.GraphicsPrimitiveMgr.<clinit> 3.3% 1 + 0 java.util.Arrays.binarySearch0 3.3% 1 + 0 sun.awt.AWTAutoShutdown.notifyThreadBusy 3.3% 1 + 0 java.awt.Font.<clinit> 93.3% 7 + 21 Total interpreted Thread-local ticks: 98.8% 2500 Blocked (of total) 6.7% 2 Class loader Global summary of 25.74 seconds: 100.0% 2530 Received ticks 0.8% 19 Compilation 0.1% 2 Class loader
Opět zde můžeme nalézt trojici zajímavých metod a nativních funkcí:
8.4% 0 + 17 sun.java2d.x11.X11Renderer.XFillRect 4.9% 8 + 2 sun.java2d.x11.X11PMBlitLoops.nativeBlit 0.5% 0 + 1 sun.java2d.loops.Blit.Blit
4. Výsledky běhu demonstračního příkladu na systému Windows při použití šířky obrázku 511 a 512 pixelů
Profiler si vyzkoušíme spustit i na systému Windows (opět na starých Windows XP bez updatů, ovšem s nejnovější verzí JDK 7:-), a to konkrétně pro bitmapu s šířkou 511 pixelů a 512 pixelů. Nejprve si prohlédněte oba výpisy profileru s vyznačenými zajímavými metodami a pokuste se sami uhodnout, proč je vykreslení bitmapy s šířkou 511 pixelů značně pomalejší, než je tomu v případě větší (!) bitmapy s šířkou 512 pixelů. Vysvětlení tohoto problému si ukážeme v následujících dvou kapitolách:
Šířka bitmapy 512 pixelů
Flat profile of 26.05 secs (2554 total ticks): AWT-Windows Interpreted + native Method 99.9% 0 + 2552 sun.awt.windows.WToolkit.eventLoop 0.0% 1 + 0 sun.awt.PostEventQueue.postEvent 0.0% 0 + 1 sun.awt.windows.WToolkit.init 100.0% 1 + 2553 Total interpreted Flat profile of 0.01 secs (1 total ticks): Thread-0 Interpreted + native Method 100.0% 0 + 1 sun.awt.windows.WToolkit.shutdown 100.0% 0 + 1 Total interpreted Flat profile of 25.89 secs (2542 total ticks): AWT-EventQueue-0 Interpreted + native Method 17.3% 0 + 9 sun.java2d.windows.GDIBlitLoops.nativeBlit 3.8% 0 + 2 sun.awt.windows.WInputMethod.getConversionStatus 1.9% 1 + 0 java.util.HashMap$KeySet.iterator 1.9% 0 + 1 sun.java2d.loops.FillRect.FillRect 1.9% 0 + 1 sun.awt.windows.WComponentPeer.hide 1.9% 1 + 0 sun.java2d.windows.GDIWindowSurfaceData.getBounds 1.9% 1 + 0 sun.java2d.SunGraphics2D.clipRect 1.9% 1 + 0 java.awt.image.DataBufferInt.<init> 1.9% 1 + 0 sun.util.locale.LanguageTag.parseLanguage 1.9% 1 + 0 javax.swing.JComponent.fetchRectangle 1.9% 1 + 0 java.util.ArrayList.remove 1.9% 1 + 0 java.awt.Component$BltBufferStrategy.getDrawGraphics 1.9% 1 + 0 javax.swing.RepaintManager.paint 1.9% 1 + 0 sun.java2d.loops.RenderCache.get 1.9% 1 + 0 java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await 1.9% 1 + 0 javax.swing.BufferStrategyPaintManager.flushAccumulatedRegion 1.9% 1 + 0 sun.java2d.SurfaceData.getSourceSurfaceData 1.9% 1 + 0 sun.java2d.SunGraphics2D.setFont 1.9% 1 + 0 sun.java2d.pipe.DrawImage.copyImage 1.9% 1 + 0 sun.java2d.SurfaceData.getTextPipe 1.9% 1 + 0 java.awt.DefaultKeyboardFocusManager.sendMessage 1.9% 1 + 0 sun.awt.SunGraphicsCallback.runOneComponent 1.9% 1 + 0 java.awt.EventDispatchThread.pumpOneEventForFilters 1.9% 1 + 0 javax.swing.BufferStrategyPaintManager.prepare 1.9% 1 + 0 javax.swing.JComponent.paint 67.3% 22 + 13 Total interpreted (including elided) Stub + native Method 21.2% 0 + 11 sun.java2d.windows.GDIBlitLoops.nativeBlit 9.6% 0 + 5 sun.java2d.loops.FillRect.FillRect 30.8% 0 + 16 Total stub Thread-local ticks: 98.0% 2490 Blocked (of total) 1.9% 1 Unknown: no last frame Flat profile of 26.06 secs (2556 total ticks): AWT-Shutdown Thread-local ticks: 100.0% 2556 Blocked (of total) Flat profile of 26.06 secs (2556 total ticks): Java2D Disposer Thread-local ticks: 100.0% 2556 Blocked (of total) Flat profile of 26.19 secs (2565 total ticks): main Interpreted + native Method 40.0% 0 + 24 sun.misc.Unsafe.unpark 1.7% 0 + 1 java.io.WinNTFileSystem.canonicalize0 1.7% 0 + 1 sun.awt.windows.WFramePeer.createAwtFrame 1.7% 0 + 1 sun.awt.windows.WWindowPeer.setFocusableWindow 1.7% 0 + 1 sun.awt.windows.WComponentPeer.pShow 1.7% 0 + 1 sun.awt.windows.WDesktopProperties.getWindowsParameters 1.7% 0 + 1 sun.font.SunFontManager.initIDs 1.7% 0 + 1 java.io.WinNTFileSystem.canonicalizeWithPrefix0 1.7% 1 + 0 sun.misc.URLClassPath$3.run 1.7% 0 + 1 java.lang.System.arraycopy 1.7% 1 + 0 java.util.concurrent.atomic.AtomicLong.get 1.7% 1 + 0 java.util.LinkedList.<init> 1.7% 1 + 0 sun.awt.windows.WToolkit.createKeyboardFocusManagerPeer 1.7% 1 + 0 sun.awt.windows.WToolkit.getInputMethodAdapterDescriptor 1.7% 1 + 0 java.util.ResourceBundle.getBundle 1.7% 0 + 1 sun.java2d.loops.GraphicsPrimitiveMgr.initIDs 1.7% 1 + 0 java.util.HashMap.keySet 1.7% 1 + 0 sun.java2d.loops.GraphicsPrimitiveMgr$1.compare 1.7% 1 + 0 sun.awt.windows.WPanelPeer.initialize 1.7% 1 + 0 java.lang.ClassLoader.findNative 1.7% 1 + 0 java.util.HashMap$HashIterator.nextEntry 1.7% 1 + 0 java.lang.ProcessEnvironment.<clinit> 1.7% 1 + 0 sun.nio.cs.FastCharsetProvider.lookup 1.7% 1 + 0 sun.net.www.ParseUtil.encodePath 1.7% 1 + 0 sun.awt.FontConfiguration.initAllComponentFonts 83.3% 17 + 33 Total interpreted (including elided) Stub + native Method 11.7% 0 + 7 sun.misc.Unsafe.unpark 11.7% 0 + 7 Total stub Thread-local ticks: 97.7% 2505 Blocked (of total) 5.0% 3 Class loader Global summary of 26.19 seconds: 100.0% 2565 Received ticks 0.1% 3 Compilation 0.1% 3 Class loader 0.0% 1 Unknown code
Šířka bitmapy 511 pixelů
Flat profile of 35.72 secs (3006 total ticks): AWT-Windows Interpreted + native Method 100.0% 0 + 3005 sun.awt.windows.WToolkit.eventLoop 0.0% 0 + 1 sun.awt.windows.WToolkit.init 100.0% 0 + 3006 Total interpreted Flat profile of 0.01 secs (1 total ticks): Thread-1 Thread-local ticks: 100.0% 1 Unknown: no last frame Flat profile of 35.58 secs (2998 total ticks): AWT-EventQueue-0 Interpreted + native Method 30.4% 0 + 664 sun.java2d.windows.GDIBlitLoops.nativeBlit 0.6% 0 + 13 sun.java2d.loops.FillRect.FillRect 0.2% 4 + 0 sun.awt.AWTAutoShutdown.notifyThreadFree 0.2% 4 + 0 java.util.HashMap.removeEntryForKey 0.1% 3 + 0 sun.awt.PostEventQueue.flush 0.1% 2 + 0 java.util.Random.next 0.1% 2 + 0 sun.java2d.SurfaceData.getSourceSurfaceData 0.1% 0 + 2 sun.misc.Unsafe.park 0.1% 2 + 0 java.lang.Thread.isInterrupted 0.1% 2 + 0 sun.awt.EventQueueDelegate.getDelegate 0.0% 1 + 0 java.util.Random.nextInt 0.0% 0 + 1 sun.misc.Unsafe.putObject 0.0% 1 + 0 sun.misc.VM.isBooted 0.0% 0 + 1 sun.misc.Unsafe.compareAndSwapInt 0.0% 1 + 0 java.util.Collections.emptyIterator 0.0% 1 + 0 java.util.concurrent.atomic.AtomicLong.get 0.0% 1 + 0 java.util.IdentityHashMap$IdentityHashMapIterator.<init> 0.0% 1 + 0 java.util.HashMap$KeySet.<init> 0.0% 1 + 0 java.util.HashMap$KeySet.iterator 0.0% 1 + 0 java.awt.Component.isLightweight 0.0% 1 + 0 java.util.AbstractCollection.<init> 0.0% 1 + 0 java.lang.Thread.interrupted 0.0% 1 + 0 java.util.ArrayList.contains 0.0% 1 + 0 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node.<init> 0.0% 1 + 0 java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread 34.2% 64 + 683 Total interpreted (including elided) Compiled + native Method 0.1% 2 + 0 java.awt.EventQueue.getNextEventPrivate 0.1% 2 + 0 java.util.IdentityHashMap$KeySet.iterator 0.0% 1 + 0 java.util.concurrent.locks.ReentrantLock.unlock 0.0% 1 + 0 java.util.IdentityHashMap$KeyIterator.next 0.0% 1 + 0 java.util.IdentityHashMap.clear 0.0% 1 + 0 java.util.Random.next 0.0% 1 + 0 javax.swing.RepaintManager.prePaintDirtyRegions 0.0% 1 + 0 javax.swing.RepaintManager.updateWindows 0.0% 1 + 0 sun.awt.windows.WComponentPeer.getGraphics 0.5% 11 + 0 Total compiled Stub + native Method 63.9% 0 + 1398 sun.java2d.windows.GDIBlitLoops.nativeBlit 1.0% 0 + 22 sun.java2d.loops.FillRect.FillRect 0.2% 1 + 3 java.security.AccessController.doPrivileged 0.1% 0 + 2 java.lang.Object.clone 0.0% 0 + 1 java.lang.Thread.isInterrupted 0.0% 0 + 1 java.lang.Thread.currentThread 65.3% 1 + 1427 Total stub Thread-local ticks: 27.1% 811 Blocked (of total) 0.0% 1 Unknown: no last frame Flat profile of 35.73 secs (3008 total ticks): AWT-Shutdown Thread-local ticks: 100.0% 3008 Blocked (of total) Flat profile of 35.73 secs (3008 total ticks): Java2D Disposer Thread-local ticks: 100.0% 3008 Blocked (of total) Flat profile of 35.85 secs (3017 total ticks): main Interpreted + native Method 94.6% 0 + 457 sun.misc.Unsafe.unpark 0.2% 0 + 1 sun.awt.windows.WComponentPeer.pShow 0.2% 0 + 1 java.lang.ClassLoader$NativeLibrary.load 0.2% 0 + 1 sun.awt.windows.WFramePeer.createAwtFrame 0.2% 0 + 1 sun.awt.windows.WWindowPeer.setFocusableWindow 0.2% 0 + 1 sun.awt.windows.WDesktopProperties.getWindowsParameters 0.2% 1 + 0 sun.font.SunFontManager.initIDs 0.2% 1 + 0 java.lang.Short.hashCode 0.2% 0 + 1 sun.awt.windows.WWindowPeer.setIconImagesData 0.2% 1 + 0 sun.awt.windows.WToolkit.createFrame 0.2% 1 + 0 sun.net.www.URLConnection.<init> 0.2% 1 + 0 java.util.Hashtable$Entry.<init> 0.2% 1 + 0 java.lang.String.charAt 0.2% 1 + 0 sun.java2d.SurfaceDataProxy.<init> 0.2% 1 + 0 java.util.IdentityHashMap$IdentityHashMapIterator.hasNext 0.2% 1 + 0 java.util.Arrays.copyOfRange 0.2% 1 + 0 java.util.HashMap.getEntry 0.2% 1 + 0 java.lang.String.substring 0.2% 1 + 0 sun.awt.AWTAutoShutdown.notifyThreadBusy 0.2% 1 + 0 java.lang.CharacterData.of 0.2% 1 + 0 java.awt.KeyboardFocusManager.<clinit> 0.2% 1 + 0 java.util.TimSort.binarySort 0.2% 1 + 0 java.io.Win32FileSystem.resolve 0.2% 1 + 0 java.awt.Color.<clinit> 0.2% 1 + 0 sun.java2d.SurfaceData.<clinit> 99.8% 19 + 463 Total interpreted (including elided) Thread-local ticks: 84.0% 2534 Blocked (of total) 0.2% 1 Class loader Global summary of 35.85 seconds: 100.0% 3017 Received ticks 0.1% 3 Compilation 0.0% 1 Class loader 0.1% 2 Unknown code
5. Proč je práce s některými bitmapami výrazně pomalejší?
Při prozkoumání výsledku profileru lze snadno zjistit, že vykreslení bitmapy s šířkou 511 pixelů je znatelně pomalejší, než je tomu v případě bitmapy s šířkou 512 pixelů (dokonce lze zjistit i pohledem, že i po vizuální stránce se bitmapa s šířkou 511 pixelů vykresluje pomaleji a navíc bez synchronizace s obrazem). Jedná se konkrétně o následující dva rozdílné údaje:
/* 512 pixelů */ 17.3% 0 + 9 sun.java2d.windows.GDIBlitLoops.nativeBlit 21.2% 0 + 11 sun.java2d.windows.GDIBlitLoops.nativeBlit /* 511 pixelů */ 30.4% 0 + 664 sun.java2d.windows.GDIBlitLoops.nativeBlit 63.9% 0 + 1398 sun.java2d.windows.GDIBlitLoops.nativeBlit
Pozorný čtenář patrně již tuší, že problém spočívá v zarovnání resp. přesněji řečeno v nezarovnání obrazového řádku na násobky čtyř bajtů. Bitmapa je totiž typu TYPE_3BYTE_BGR, což znamená, že při šířce 511 pixelů má obrazový řádek 511×3=1533 bajtů, což zcela jistě není číslo dělitelné čtyřmi (a samotné JVM s tímto faktem nedokáže nic dělat, dokonce i vkládání bajtů na konec obrazového řádku nemá velký význam). Z jakého důvodu je však vhodné mít obrazové řádky zarovnané na násobky čtyř bajtů? To nám prozradí pohled do zdrojového kódu OpenJDK, protože měření sice bylo provedeno na Oracle JDK, nicméně v tomto případě platí známá hláška „stejné chyby-stejné známky“ (neboli zdrojové kódy obou JDK jsou prakticky shodné).
6. Zdrojový kód nativní funkce sun.java2d.windows.GDIBlitLoops()
Profiler nám přesně prozradil, kde se s velkou pravděpodobností bude skrývat problém s výkonností vykreslování bitmap s šířkou nedělitelnou čtyřmi: jedná se o nativní funkci sun.java2d.windows.GDIBlitLoops(). Tato funkce při vykreslování používá volání WinAPI SetDIBitsToDevice, což je funkce, která může potenciálně využívat možností grafické akcelerace. DI v názvu této funkce znamená „Device Independent“, což značí bitmapy nezávislé na konkrétním formátu používaném grafickou kartou. Je zde ovšem jeden problém – v případě, že délka obrazového řádku vyjádřená v bajtech není dělitelná čtyřmi, rozhodli se autoři JDK, že se bude bitmapa přenášet a zobrazovat po jednotlivých řádcích, což je velmi neefektivní způsob, který nemá s případnou grafickou akcelerací příliš společného! Ostatně se o tom můžeme přesvědčit sami pohledem do zdrojových kódů:
/* * Class: sun_java2d_windows_GDIBlitLoops * Method: nativeBlit * Signature: (Lsun/java2d/SurfaceData;Lsun/java2d/SurfaceData;IIIIIIZ)V */ JNIEXPORT void JNICALL Java_sun_java2d_windows_GDIBlitLoops_nativeBlit (JNIEnv *env, jobject joSelf, jobject srcData, jobject dstData, jobject clip, jint srcx, jint srcy, jint dstx, jint dsty, jint width, jint height, jint rmask, jint gmask, jint bmask, jboolean needLut) { J2dTraceLn(J2D_TRACE_INFO, "GDIBlitLoops_nativeBlit"); SurfaceDataRasInfo srcInfo; SurfaceDataOps *srcOps = SurfaceData_GetOps(env, srcData); GDIWinSDOps *dstOps = GDIWindowSurfaceData_GetOps(env, dstData); jint lockFlags; srcInfo.bounds.x1 = srcx; srcInfo.bounds.y1 = srcy; srcInfo.bounds.x2 = srcx + width; srcInfo.bounds.y2 = srcy + height; if (needLut) { lockFlags = (SD_LOCK_READ | SD_LOCK_LUT); } else { lockFlags = SD_LOCK_READ; } // This method is used among other things for on-screen copyArea, in which // case the source and destination surfaces are the same. It is important // to first lock the source and then get the hDC for the destination // surface because the same per-thread hDC will be used for both // and we need to have the correct clip set to the hDC // used with the SetDIBitsToDevice call. if (srcOps->Lock(env, srcOps, &srcInfo, lockFlags) != SD_SUCCESS) { return; } SurfaceDataBounds dstBounds = {dstx, dsty, dstx + width, dsty + height}; // Intersect the source and dest rects. Note that the source blit bounds // will be adjusted to the surfaces's bounds if needed. SurfaceData_IntersectBlitBounds(&(srcInfo.bounds), &dstBounds, dstx - srcx, dsty - srcy); srcx = srcInfo.bounds.x1; srcy = srcInfo.bounds.y1; dstx = dstBounds.x1; dsty = dstBounds.y1; width = srcInfo.bounds.x2 - srcInfo.bounds.x1; height = srcInfo.bounds.y2 - srcInfo.bounds.y1; if (width > 0 && height > 0) { BmiType bmi; // REMIND: A performance tweak here would be to make some of this // data static. For example, we could have one structure that is // always used for ByteGray copies and we only change dynamic data // in the structure with every new copy. Also, we could store // structures with Ops or with the Java objects so that surfaces // could retain their own DIB info and we would not need to // recreate it every time. // GetRasInfo implicitly calls GetPrimitiveArrayCritical // and since GetDC uses JNI it needs to be called first. HDC hDC = dstOps->GetDC(env, dstOps, 0, NULL, clip, NULL, 0); if (hDC == NULL) { SurfaceData_InvokeUnlock(env, srcOps, &srcInfo); return; } srcOps->GetRasInfo(env, srcOps, &srcInfo); if (srcInfo.rasBase == NULL) { dstOps->ReleaseDC(env, dstOps, hDC); SurfaceData_InvokeUnlock(env, srcOps, &srcInfo); return; } void *rasBase = ((char *)srcInfo.rasBase) + srcInfo.scanStride * srcy + srcInfo.pixelStride * srcx; // If scanlines are DWORD-aligned (scanStride is a multiple of 4), // then we can do the work much faster. This is due to a constraint // in the way DIBs are structured and parsed by GDI jboolean fastBlt = ((srcInfo.scanStride & 0x03) == 0); bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = srcInfo.scanStride/srcInfo.pixelStride; // fastBlt copies whole image in one call; else copy line-by-line LONG dwHeight = srcInfo.bounds.y2 - srcInfo.bounds.y1; bmi.bmiHeader.biHeight = (fastBlt) ? -dwHeight : -1; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = (WORD)srcInfo.pixelStride * 8; // 1,3,4 byte use BI_RGB, 2 byte use BI_BITFIELD... // 4 byte _can_ use BI_BITFIELD, but this seems to cause a performance // penalty. Since we only ever have one format (xrgb) for 32-bit // images that enter this function, just use BI_RGB. // Could do BI_RGB for 2-byte 555 format, but no perceived // performance benefit. bmi.bmiHeader.biCompression = (srcInfo.pixelStride != 2) ? BI_RGB : BI_BITFIELDS; bmi.bmiHeader.biSizeImage = (bmi.bmiHeader.biWidth * dwHeight * srcInfo.pixelStride); bmi.bmiHeader.biXPelsPerMeter = 0; bmi.bmiHeader.biYPelsPerMeter = 0; bmi.bmiHeader.biClrUsed = 0; bmi.bmiHeader.biClrImportant = 0; if (srcInfo.pixelStride == 1) { // Copy palette info into bitmap for 8-bit image if (needLut) { memcpy(bmi.colors.palette, srcInfo.lutBase, srcInfo.lutSize * sizeof(RGBQUAD)); if (srcInfo.lutSize != 256) { bmi.bmiHeader.biClrUsed = srcInfo.lutSize; } } else { // If no LUT needed, must be ByteGray src. If we have not // yet created the byteGrayPalette, create it now and copy // it into our temporary bmi structure. // REMIND: byteGrayPalette is a leak since we do not have // a mechansim to free it up. This should be fine, since it // is only 256 bytes for any process and only gets malloc'd // when using ByteGray surfaces. Eventually, we should use // the new Disposer mechanism to delete this native memory. if (byteGrayPalette == NULL) { // assert (256 * sizeof(RGBQUAD)) <= SIZE_MAX byteGrayPalette = (RGBQUAD *)safe_Malloc(256 * sizeof(RGBQUAD)); for (int i = 0; i < 256; ++i) { byteGrayPalette[i].rgbRed = i; byteGrayPalette[i].rgbGreen = i; byteGrayPalette[i].rgbBlue = i; } } memcpy(bmi.colors.palette, byteGrayPalette, 256 * sizeof(RGBQUAD)); } } else if (srcInfo.pixelStride == 2) { // For 16-bit case, init the masks for the pixel depth bmi.colors.dwMasks[0] = rmask; bmi.colors.dwMasks[1] = gmask; bmi.colors.dwMasks[2] = bmask; } if (fastBlt) { // Window could go away at any time, leaving bits on the screen // from this GDI call, so make sure window still exists if (::IsWindowVisible(dstOps->window)) { // Could also call StretchDIBits. Testing showed slight // performance advantage of SetDIBits instead, so since we // have no need of scaling, might as well use SetDIBits. SetDIBitsToDevice(hDC, dstx, dsty, width, height, 0, 0, 0, height, rasBase, (BITMAPINFO*)&bmi, DIB_RGB_COLORS); } } else { // Source scanlines not DWORD-aligned - copy each scanline individually for (int i = 0; i < height; i += 1) { if (::IsWindowVisible(dstOps->window)) { SetDIBitsToDevice(hDC, dstx, dsty+i, width, 1, 0, 0, 0, 1, rasBase, (BITMAPINFO*)&bmi, DIB_RGB_COLORS); rasBase = (void*)((char*)rasBase + srcInfo.scanStride); } else { break; } } } dstOps->ReleaseDC(env, dstOps, hDC); SurfaceData_InvokeRelease(env, srcOps, &srcInfo); } SurfaceData_InvokeUnlock(env, srcOps, &srcInfo); return; }
Závěr: došlo zde ke kolizi dvou světů – Javovského a Windowsovského (omezení funkce SetDIBitsToDevice(), přesněji řečeno omezení formátu DIB se zarovnáním na 32bitů), ovšem potenciálním poraženým zde může být jen uživatel :-)
7. „Kompatibilní“ bitmapy typu BufferedImage
Jedním ze způsobů, jak urychlit vykreslování bitmap typu BufferedImage je vytvoření a použití takzvaných „kompatibilních bitmap“. Jedná se o bitmapy, jejichž formát (přesněji řečeno způsob uložení pixelů) je shodný s formátem používaným v aktuálně nastaveném grafickém režimu. V praxi to znamená, že se bitmapa nevytváří pomocí konstruktoru BufferedImage(šířka, výška, typ), ale s využitím následujících dvou metod nabízených třídou GraphicsConfiguration:
Metoda | Popis |
---|---|
GraphicsConfiguration.createCompatibleImage(int width, int height) | základní metoda pro vytvoření bitmapy o zadaných rozměrech |
GraphicsConfiguration.createCompatibleImage(int width, int height, int transparency) | dtto ale s volbou, jak se má reprezentovat průhlednost/průsvitnost |
Způsob použití je ukázán v demonstračním příkladu:
/** * Vytvoreni testovaciho obrazku. */ private void createImage() { if (CREATE_COMPATIBLE_IMAGE) { this.image = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT); } else { this.image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_3BYTE_BGR); } drawIntoImage(this.image); }
8. Výsledky běhu upraveného demonstračního příkladu na systému Windows při použití šířky obrázku 511 a 512 pixelů
Zkusme si opět spustit profiler, tentokrát při použití „kompatibilních bitmap“ s šířkou 512 a 511 pixelů:
Šířka bitmapy 512 pixelů
Flat profile of 25.87 secs (2544 total ticks): AWT-Windows Interpreted + native Method 99.9% 0 + 2542 sun.awt.windows.WToolkit.eventLoop 0.0% 1 + 0 sun.awt.ExtendedKeyCodes.<clinit> 0.0% 1 + 0 sun.awt.windows.WToolkit.registerShutdownHook 100.0% 2 + 2542 Total interpreted Flat profile of 25.71 secs (2533 total ticks): AWT-EventQueue-0 Interpreted + native Method 10.3% 0 + 4 sun.java2d.windows.GDIBlitLoops.nativeBlit 5.1% 2 + 0 javax.swing.BufferStrategyPaintManager.prepare 2.6% 1 + 0 java.text.AttributedString.getIterator 2.6% 1 + 0 java.util.HashMap$HashIterator.hasNext 2.6% 0 + 1 sun.awt.windows.WComponentPeer.hide 2.6% 1 + 0 java.awt.Image.getAccelerationPriority 2.6% 0 + 1 sun.java2d.loops.FillRect.FillRect 2.6% 1 + 0 sun.awt.image.SurfaceManager.getManager 2.6% 1 + 0 sun.java2d.SunGraphics2D.translate 2.6% 1 + 0 java.awt.Component$BltBufferStrategy.getDrawGraphics 2.6% 1 + 0 javax.swing.BufferStrategyPaintManager$BufferInfo.getBufferStrategy 2.6% 1 + 0 sun.java2d.SunGraphics2D.setFont 2.6% 1 + 0 sun.java2d.loops.Blit.getFromCache 2.6% 1 + 0 java.awt.Component$BltBufferStrategy.showSubRegion 2.6% 1 + 0 java.awt.EventDispatchThread.pumpOneEventForFilters 2.6% 1 + 0 javax.swing.RepaintManager.collectDirtyComponents 2.6% 1 + 0 javax.swing.JComponent.paint 2.6% 1 + 0 sun.util.locale.LanguageTag.<clinit> 2.6% 1 + 0 javax.swing.JComponent.paintChildren 2.6% 1 + 0 sun.java2d.SurfaceData.validatePipe 61.5% 18 + 6 Total interpreted Compiled + native Method 2.6% 0 + 1 javax.swing.JComponent.paintComponent 2.6% 0 + 1 Total compiled Stub + native Method 20.5% 0 + 8 sun.java2d.windows.GDIBlitLoops.nativeBlit 10.3% 0 + 4 sun.java2d.loops.FillRect.FillRect 2.6% 0 + 1 java.lang.Object.clone 33.3% 0 + 13 Total stub Thread-local ticks: 98.5% 2494 Blocked (of total) 2.6% 1 Unknown: no last frame Flat profile of 25.87 secs (2545 total ticks): AWT-Shutdown Thread-local ticks: 100.0% 2545 Blocked (of total) Flat profile of 25.87 secs (2545 total ticks): Java2D Disposer Thread-local ticks: 100.0% 2545 Blocked (of total) Flat profile of 26.00 secs (2555 total ticks): main Interpreted + native Method 37.3% 0 + 19 sun.misc.Unsafe.unpark 3.9% 0 + 2 sun.awt.windows.WFramePeer.createAwtFrame 2.0% 0 + 1 sun.awt.windows.WComponentPeer.pShow 2.0% 0 + 1 java.io.WinNTFileSystem.canonicalize0 2.0% 1 + 0 sun.awt.windows.WColor.getDefaultColor 2.0% 0 + 1 sun.awt.windows.WDesktopProperties.getWindowsParameters 2.0% 0 + 1 java.security.AccessController.doPrivileged 2.0% 1 + 0 java.awt.Component.initIDs 2.0% 1 + 0 java.lang.Class.forName0 2.0% 1 + 0 sun.nio.cs.SingleByte$Decoder.decode 2.0% 1 + 0 sun.awt.image.PixelConverter$Ushort555Rgb.<clinit> 2.0% 1 + 0 java.lang.String.lastIndexOf 2.0% 1 + 0 sun.java2d.loops.GraphicsPrimitiveMgr.initIDs 2.0% 1 + 0 java.util.Arrays.sort 2.0% 1 + 0 javax.swing.JComponent.getDefaultLocale 2.0% 1 + 0 javax.swing.plaf.basic.BasicPanelUI.installDefaults 2.0% 1 + 0 java.awt.Component.getSiblingIndexAbove 2.0% 1 + 0 java.awt.image.DataBufferInt.<init> 2.0% 1 + 0 java.lang.System.getenv 2.0% 1 + 0 java.lang.String.equalsIgnoreCase 2.0% 1 + 0 java.lang.String.indexOf 2.0% 1 + 0 java.awt.Window$Type.<clinit> 2.0% 1 + 0 sun.util.CoreResourceBundleControl.<init> 2.0% 1 + 0 java.util.concurrent.ConcurrentHashMap.ensureSegment 2.0% 1 + 0 java.io.BufferedReader.readLine 90.2% 21 + 25 Total interpreted (including elided) Stub + native Method 7.8% 0 + 4 sun.misc.Unsafe.unpark 7.8% 0 + 4 Total stub Thread-local ticks: 98.0% 2504 Blocked (of total) 2.0% 1 Class loader Global summary of 26.00 seconds: 100.0% 2556 Received ticks 0.0% 1 Received GC ticks 0.0% 1 Compilation 0.0% 1 Class loader 0.0% 1 Unknown code
Šířka bitmapy 511 pixelů
Flat profile of 25.85 secs (2543 total ticks): AWT-Windows Interpreted + native Method 99.9% 0 + 2540 sun.awt.windows.WToolkit.eventLoop 0.0% 0 + 1 sun.java2d.windows.GDIBlitLoops.nativeBlit 0.0% 0 + 1 sun.awt.windows.WToolkit.init 100.0% 0 + 2542 Total interpreted Thread-local ticks: 0.0% 1 Class loader Flat profile of 25.72 secs (2535 total ticks): AWT-EventQueue-0 Interpreted + native Method 18.9% 0 + 7 sun.java2d.windows.GDIBlitLoops.nativeBlit 2.7% 0 + 1 sun.awt.windows.WInputMethod.getOpenStatus 2.7% 1 + 0 sun.awt.image.SunVolatileImage.getHeight 2.7% 0 + 1 sun.awt.windows.WWindowPeer.requestWindowFocus 2.7% 0 + 1 sun.awt.windows.WComponentPeer.hide 2.7% 1 + 0 sun.awt.image.SurfaceManager.getManager 2.7% 1 + 0 java.util.Random.next 2.7% 1 + 0 sun.java2d.pipe.Region.isInsideQuickCheck 2.7% 1 + 0 java.util.ArrayList.indexOf 2.7% 1 + 0 javax.swing.RepaintManager.endPaint 2.7% 1 + 0 java.awt.geom.Rectangle2D.intersects 2.7% 1 + 0 sun.java2d.SurfaceData.getSourceSurfaceData 2.7% 1 + 0 sun.java2d.SunGraphics2D.setFont 2.7% 1 + 0 java.awt.EventDispatchThread.pumpOneEventForFilters 2.7% 1 + 0 javax.swing.BufferStrategyPaintManager.prepare 2.7% 1 + 0 javax.swing.JComponent.paint 2.7% 1 + 0 javax.swing.JComponent.paintChildren 2.7% 1 + 0 sun.java2d.SurfaceData.validatePipe 64.9% 14 + 10 Total interpreted Stub + native Method 27.0% 0 + 10 sun.java2d.windows.GDIBlitLoops.nativeBlit 2.7% 0 + 1 sun.java2d.loops.FillRect.FillRect 2.7% 0 + 1 java.lang.Object.clone 32.4% 0 + 12 Total stub Thread-local ticks: 98.5% 2498 Blocked (of total) 2.7% 1 Unknown: no last frame Flat profile of 25.87 secs (2544 total ticks): AWT-Shutdown Thread-local ticks: 100.0% 2544 Blocked (of total) Flat profile of 25.87 secs (2544 total ticks): Java2D Disposer Thread-local ticks: 100.0% 2544 Blocked (of total) Flat profile of 25.99 secs (2554 total ticks): main Interpreted + native Method 36.0% 0 + 18 sun.misc.Unsafe.unpark 4.0% 0 + 2 java.lang.System.arraycopy 2.0% 0 + 1 sun.awt.windows.WWindowPeer.setFocusableWindow 2.0% 0 + 1 sun.awt.windows.WFramePeer.createAwtFrame 2.0% 0 + 1 java.lang.ClassLoader$NativeLibrary.load 2.0% 0 + 1 sun.awt.windows.WComponentPeer.pShow 2.0% 0 + 1 sun.awt.windows.WWindowPeer.setIconImagesData 2.0% 0 + 1 sun.misc.Unsafe.compareAndSwapLong 2.0% 1 + 0 sun.font.Font2DHandle.<init> 2.0% 1 + 0 sun.awt.SunToolkit.checkAndSetPolicy 2.0% 1 + 0 sun.awt.windows.WToolkit.createFrame 2.0% 1 + 0 sun.nio.cs.ThreadLocalCoders.<clinit> 2.0% 1 + 0 sun.java2d.loops.GraphicsPrimitive.makeUniqueID 2.0% 1 + 0 sun.java2d.loops.GraphicsPrimitiveMgr$1.compare 2.0% 1 + 0 java.util.Vector.addElement 2.0% 1 + 0 sun.util.logging.PlatformLogger$LoggerProxy.<init> 2.0% 1 + 0 java.lang.Short$ShortCache.<clinit> 2.0% 1 + 0 java.net.URLStreamHandler.getHostAddress 2.0% 1 + 0 java.awt.EventQueue.cacheEQItem 2.0% 1 + 0 sun.java2d.loops.GraphicsPrimitiveMgr.<clinit> 2.0% 1 + 0 sun.awt.windows.WDesktopProperties.setSoundProperty 2.0% 1 + 0 java.awt.Container.<clinit> 2.0% 1 + 0 java.awt.Color.<clinit> 2.0% 1 + 0 java.awt.Cursor.<clinit> 2.0% 1 + 0 sun.java2d.SurfaceData.<clinit> 90.0% 19 + 26 Total interpreted (including elided) Stub + native Method 10.0% 0 + 5 sun.misc.Unsafe.unpark 10.0% 0 + 5 Total stub Thread-local ticks: 98.0% 2504 Blocked (of total) Global summary of 25.99 seconds: 100.0% 2554 Received ticks 0.0% 1 Compilation 0.0% 1 Class loader 0.0% 1 Unknown code
Opět zde můžeme vidět rozdílnou rychlost vykreslování, ovšem v tomto případě se nejedná o tak závažné zpomalení, jako v případě použití „nekompatibilních bitmap“:
10.3% 0 + 4 sun.java2d.windows.GDIBlitLoops.nativeBlit 20.5% 0 + 8 sun.java2d.windows.GDIBlitLoops.nativeBlit 18.9% 0 + 7 sun.java2d.windows.GDIBlitLoops.nativeBlit 27.0% 0 + 10 sun.java2d.windows.GDIBlitLoops.nativeBlit
9. Bitmapy typu VolatileImage
Největší předností bitmap typu BufferedImage je snadnost jejich použití, zejména v případě, že se do bitmapy vykreslují obrazce s využitím tříd Graphics či Graphics2D. S těmito bitmapami lze taktéž provádět různé rastrové operace typu rozmazání či naopak zvýraznění hran apod. Samotné vykreslování bitmap může být v některých případech akcelerováno, ovšem v dalších případech (týká se to opět her) můžeme zjistit, že BufferedImage nejsou vhodné pro všechny operace. Jeden z problémů spočívá v tom, že do bitmapy lze neustále vykreslovat, což ovšem znamená, že bitmapa je (většinou) uložena v operační paměti a nikoli ve video paměti – jakákoli operace, která změní obsah bitmapy ji automaticky ve video paměti znevalidní, což při požadavku na vykreslení bitmapy znamená nutnost přenosu dat po sběrnici.
Snadno lze vypočítat, kolik dat by se muselo přenést v případě, že by hra vždy vykreslovala na pozadí obrázek o rozměrech 1024×768 pixelů. Při 30 snímcích za sekundu a bitové hloubce 24bpp by bylo nutné za sekundu přenést:
30×24×1024×768/8=67,5 MB
Řešením jsou takzvané „volatilní bitmapy“ reprezentované třídou VolatileImage. Tyto bitmapy jsou navrženy a implementovány takovým způsobem, aby mohly být uloženy přímo ve video paměti, čímž se omezí nutné přenosy dat (pixelů) po sběrnici. Ovšem za tento luxus musíme něco zaplatit – volatilní bitmapy získaly svůj název z toho důvodu, že jejich obsah může být v jakémkoli okamžiku přemazán, což znamená nutnost obnovy obsahu této bitmapy. To ovšem znamená i nutnost změny logiky v programu, protože nelze předpokládat, že obrazec/graf jednou zapsaný do bitmapy již zůstane beze změny a bude možné ho kdykoli znovu vykreslit. Navíc je problematické i samotné vykreslování do volatilních bitmap, které by mělo být omezeno jen na operace typu Graphics.drawImage() (opět z důvodu minimalizace přenosů po sběrnici).
10. Možné stavy bitmap typu VolatileImage
Volatilní bitmapy se typicky používají společně s celoobrazovkovými grafickými režimy popsanými v předchozí části tohoto seriálu. Ovšem i při použití exkluzivního celoobrazovkového režimu mohou nastat situace, kdy dojde k poškození obrazových dat (a v důsledku toho i volatilních bitmap). Může se například jednat o zobrazení zprávy z task manageru (ten má vysokou prioritu), spuštění screensaveru, přechodu systému z/do režimu spánku či režimu uspání, spuštění či přepnutí do další aplikace vyžadující celoobrazovkový režim atd. Z tohoto důvodu JVM sleduje stav volatilních bitmap a ukládá tento stav do atributu dostupného přes metodu validate() (ta má ještě další význam). Volatilní bitmapa se může nacházet v jednom ze tří stavů:
- IMAGE_OK: validní bitmapa, obsah se neztratil.
- IMAGE_INCOMPATIBLE: pravděpodobně se změnil grafický režim (i ukončení celoobrazovkového režimu).
- IMAGE_RESTORED: bitmapu je nutno překreslit.
Jak konkrétně se s volatilními bitmapami pracuje si řekneme a na demonstračních příkladech ukážeme příště.
11. Repositář se zdrojovými soubory dnešního demonstračního příkladu
Následují již tradiční odkazy na zdrojové kódy uložené do Mercurial repositáře. V následující tabulce najdete linky na prozatím nejnovější verzi dnes popsaného demonstračního příkladu vykreslujícího bitmapy typu BufferedImage:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | BlitTest1.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/3acaa89b0dc7/jvm/gfx/BlitTest1.java |
12. Odkazy na Internetu
- Javadoc – třída GraphicsDevice
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsDevice.html - Javadoc – třída GraphicsEnvironment
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsEnvironment.html - Javadoc – třída GraphicsConfiguration
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsConfiguration.html - Javadoc – třída DisplayMode
http://docs.oracle.com/javase/7/docs/api/java/awt/DisplayMode.html - Lesson: Full-Screen Exclusive Mode API
http://docs.oracle.com/javase/tutorial/extra/fullscreen/ - Full-Screen Exclusive Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/exclusivemode.html - Display Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/displaymode.html - Using the Full-Screen Exclusive Mode API in Java
http://www.developer.com/java/other/article.php/3609776/Using-the-Full-Screen-Exclusive-Mode-API-in-Java.htm - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html