Pohled pod kapotu JVM – vykreslování bitmap typu BufferedImage a VolatileImage

Pavel Tišnovský 10. 12. 2013

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si řekneme, jaké výkonnostní problémy mohou doprovázet použití bitmap typu BufferedImage při vykreslování na obrazovku a jak lze výkonnost grafické aplikace (typicky hry) v některých případech vylepšit použitím bitmap typu VolatileImage.

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)

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ů

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

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ů

9. Bitmapy typu VolatileImage

10. Možné stavy bitmap typu VolatileImage

11. Repositář se zdrojovými soubory dnešního demonstračního příkladu

12. Odkazy na Internetu

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.ImageConsumerjava.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.BufferedIma­ge(parametry) či je možné (což je, jak si ukážeme, mnohdy nejvýhodnější) využít jednu z metod java.awt.GraphicsConfigura­tion.createCompatibleImage(in­t, int) či java.awt.GraphicsConfigura­tion.createCompatibleImage(in­t, 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.GraphicsConfigura­tion.createCompatibleImage(šíř­ka,výška). Rozměry bitmapy i způsob jejího vytvoření lze řídit konstantami IMAGE_WIDTH, IMAGE_HEIGHTCREATE_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.cre­ateCompatibleImage(int width, int height) základní metoda pro vytvoření bitmapy o zadaných rozměrech
GraphicsConfiguration.cre­ateCompatibleImage(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ů:

widgety

  1. IMAGE_OK: validní bitmapa, obsah se neztratil.
  2. IMAGE_INCOMPATIBLE: pravděpodobně se změnil grafický režim (i ukončení celoobrazovkového režimu).
  3. 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.or­g/people/ptisnovs/jvm-tools/file/3acaa89b0dc7/jvm/gfx/Blit­Test1.java

12. Odkazy na Internetu

  1. Javadoc – třída GraphicsDevice
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsDevice.html
  2. Javadoc – třída GraphicsEnvironment
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsEnvironment.html
  3. Javadoc – třída GraphicsConfiguration
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsConfiguration.html
  4. Javadoc – třída DisplayMode
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Dis­playMode.html
  5. Lesson: Full-Screen Exclusive Mode API
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/
  6. Full-Screen Exclusive Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/exclusivemode.html
  7. Display Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/displaymode.html
  8. Using the Full-Screen Exclusive Mode API in Java
    http://www.developer.com/ja­va/other/article.php/3609776/U­sing-the-Full-Screen-Exclusive-Mode-API-in-Java.htm
  9. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  10. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  11. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  12. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  13. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  14. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  15. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  16. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  17. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  18. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  19. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  20. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  21. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  22. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  23. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  24. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  25. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  26. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  27. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  28. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  29. ASM Home page
    http://asm.ow2.org/
  30. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  31. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  32. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  33. BCEL Home page
    http://commons.apache.org/bcel/
  34. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  35. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  36. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  37. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  38. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  39. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  40. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  41. Javassist
    http://www.jboss.org/javassist/
  42. Byteman
    http://www.jboss.org/byteman
  43. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  44. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  45. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  46. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  47. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  48. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  49. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  50. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  51. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  52. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  53. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  54. Cobertura
    http://cobertura.sourceforge.net/
  55. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html
Našli jste v článku chybu?
Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Vitalia.cz: Opuncie je plod kaktusu. Pozor na trny

Opuncie je plod kaktusu. Pozor na trny

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

Vitalia.cz: 5 pravidel proti infekci močových cest

5 pravidel proti infekci močových cest

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Vitalia.cz: Jsou vegani a vyrábějí nemléko

Jsou vegani a vyrábějí nemléko

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?

Podnikatel.cz: Chystá se smršť legislativních novinek

Chystá se smršť legislativních novinek