Obsah
1. Pohled pod kapotu JVM – bitmapy typu BufferedImage a VolatileImage v celoobrazovkových režimech
2. První demonstrační příklad: vykreslení pozadí a spritu v celoobrazovkovém režimu
3. Zamezení poblikávání s využitím bitmapy typu BufferedImage ve funkci offscreen bufferu
5. Využití bitmap typu VolatileImage
6. Detekce změny stavu grafické konfigurace a zneplatnění obsahu volatilních bitmap
8. Problematika průhledných obrázků
10. Repositář se zdrojovými soubory dnešních demonstračních příkladů
1. Pohled pod kapotu JVM – bitmapy typu BufferedImage a VolatileImage v celoobrazovkových režimech
V předchozí části tohoto seriálu jsme si vysvětlili a na demonstračním příkladu (benchmarku) ukázali, jakým způsobem současné virtuální stroje jazyka Java vykreslují rastrové obrázky (bitmapy) typu BufferedImage popř. VolatileImage. Taktéž jsme si řekli základní rozdíly mezi těmito dvěma typy bitmap. Dnes se budeme zabývat poněkud praktičtější problematikou; konkrétně možnostmi použití bitmap BufferedImage i VolatileImage při práci ve vybraném celoobrazovkovém exkluzivním grafickém režimu (fullscreen exclusive graphics mode). Připomeňme si, že JVM umožňuje na většině systémů nastavení celoobrazovkového grafického režimu se zvoleným rozlišením (například 800×600 pixelů či 1600×1200 pixelů), zvolenou bitovou hloubkou (většinou 8bpp, 16bpp, 24bpp a 32bpp) a taktéž vybranou obnovovací frekvencí obrazu. Následně je možné provádět „exkluzivní“ vykreslování přes předem nastavené a maximalizované okno (popř. rámec, dialog či applet), které má k dispozici celou plochu obrazovky.
Slovem „exkluzivní“ je v předchozím odstavci myšlen fakt, že okna a dialogy ostatních aplikací nebudou viditelná; při zapnutí celoobrazovkového režimu se tak vlastně vracíme do doby před rozšířením správců oken (což ostatně není při pohledu na směr, kterým se vyvíjí moderní desktop, až tak špatná myšlenka :-). Nabídka grafických režimů je závislá jak na vlastnostech grafické karty či grafického akcelerátoru, tak i na konfiguraci monitorů. My dnes budeme pro jednoduchost předpokládat, že je ke grafické kartě/akcelerátoru připojen pouze jediný monitor, popř. že všechny monitory budou zobrazovat stejný snímek. JVM sice umožňuje práci i na systémech s více monitory (a dokonce i větším množstvím grafických karet), to je však mnohdy poměrně komplikovaná problematika, kterou se budeme zabývat v samostatném článku (navíc mnoho aplikací nepracuje při použití více monitorů korektně, například nerespektuje nastavení primárního displeje, zobrazuje dialogy centrované uprostřed mezi monitory atd. atd.).
2. První demonstrační příklad: vykreslení pozadí a spritu v celoobrazovkovém režimu
V dnešním prvním demonstračním příkladu je ukázáno vykreslování bitmap v celoobrazovkovém režimu. Tento příklad po svém spuštění nejprve inicializuje rámec (JFrame), u nějž jsou vypnuty okraje a titulkový pruh. Následně je pro tento rámec povolen celoobrazovkový režim:
/** * Nastaveni parametru okna a zobrazeni. * @param gd */ private void showJFrame(GraphicsDevice gd) { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(0, 0); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.createBufferStrategy(1); this.setVisible(true); gd.setFullScreenWindow(this); }
Posléze je nastaven celoobrazovkový grafický režim s rozlišením 1024×768 pixelů a bitovou hloubkou 16bpp. Tuto část programu si můžete sami upravit na základě vlastností konkrétního počítače, ostatní části programu se novému rozlišení dokážou přizpůsobit (pozor: na některých systémech není možné specifikovat barvovou hloubku; můžete zde však zadat hodnotu –1 a JVM samo vybere vhodný grafický režim):
/** * Nastaveni grafickeho rezimu. */ private void setDisplayMode(GraphicsDevice gd) { DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN); gd.setDisplayMode(dm); }
Při vykreslování se používá dvojice bitmap nazvaná pozadí (background) a sprite. Pozadí je neměnné a rozměry této bitmapy jsou stejné jako rozlišení nastaveného grafického režimu. Sprite se naproti tomu neustále pohybuje nad pozadím. Vlastní vykreslovací rutina využívá metodu Graphics.drawImage(), která je na většině současných počítačů akcelerovaná (pokud tedy nedojde k problémům s nezarovnanými řádky, což jsme si vysvětlili minule):
@Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; // vykreslime pozadi g.drawImage(background, 0, 0, null); // nad nej vykreslime pohybujici se sprite g.drawImage(sprite, spriteX, spriteY, null); // neni zcela nutne g.dispose(); this.repaintCount++; }
Následuje výpis celého zdrojového kódu demonstračního příkladu BlitTest2:
import java.awt.Color; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.WindowConstants; /** * Jednoduchy test metody Graphics.drawImage(). * Pri vykreslovani se nebude pouzivat double buffering. */ public class BlitTest2 extends JFrame { /** * Generated serial version UID. */ private static final long serialVersionUID = -6455857608125974217L; /** * Sirka testovaciho obrazku. */ private static final int IMAGE_WIDTH = 256; /** * Vyska testovaciho obrazku. */ private static final int IMAGE_HEIGHT = 256; /** * Sirka okna ve fullscreen rezimu. */ private static final int FRAME_WIDTH = 1024; /** * Vyska okna ve fullscreen rezimu. */ private static final int FRAME_HEIGHT = 768; /** * Bitova hloubka nastavovaneho grafickeho rezimu. */ private static final int BPP = 16; /** * Horizontalni souradnice obrazku. */ private int imageX; /** * Vertikalni souradnice obrazku. */ private int imageY; /** * Pocet skutecnych volani metody paint() */ private int repaintCount = 0; /** * Obrazek, ktery se bude prenaset do okna. */ private BufferedImage spriteImage = null; /** * Obrazek zobrazeny na pozadi okna. */ private BufferedImage backgroundImage = null; /** * Spusteni testu. */ private void run() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); showJFrame(gd); setDisplayMode(gd); createSpriteImage(); createBackgroundImage(); runBenchmark(); gd.setFullScreenWindow(null); this.dispose(); System.out.println("Repaint count: " + this.repaintCount); System.exit(0); } /** * Nastaveni grafickeho rezimu. */ private void setDisplayMode(GraphicsDevice gd) { DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN); gd.setDisplayMode(dm); } /** * 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); // vynutime si prekresleni okna repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Vytvoreni testovaciho obrazku. */ private void createSpriteImage() { this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT); drawIntoSpriteImage(this.spriteImage); } /** * Vytvoreni obrazku pozadi. */ private void createBackgroundImage() { this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); drawIntoBackgroundImage(this.backgroundImage); } /** * Vykresleni vzorku do obrazku. */ private void drawIntoSpriteImage(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(); } /** * Vykresleni vzorku do obrazku na pozadi. */ private void drawIntoBackgroundImage(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 zelene sachovnice g.setColor(Color.green); for (int y = 0; y < height; y += 20) { g.drawLine(0, y, width - 1, y); } g.setColor(Color.green); for (int x = 0; x < width; x += 20) { g.drawLine(x, 0, x, height - 1); } g.dispose(); } /** * Nastaveni parametru okna a zobrazeni. * @param gd */ private void showJFrame(GraphicsDevice gd) { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(0, 0); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.createBufferStrategy(1); this.setVisible(true); gd.setFullScreenWindow(this); } @Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; // vykreslime pozadi g.drawImage(background, 0, 0, null); // nad nej vykreslime pohybujici se sprite g.drawImage(sprite, spriteX, spriteY, null); // neni zcela nutne 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 spriteImage. */ BufferedImage getSpriteImage() { return this.spriteImage; } /** * Getter pro atribut backgroundImage. */ BufferedImage getBackgroundImage() { return this.backgroundImage; } /** * Spusteni testu. * @param args */ public static void main(String[] args) { new BlitTest2().run(); } }
3. Zamezení poblikávání s využitím bitmapy typu BufferedImage ve funkci offscreen bufferu
Pokud jste si dnešní první demonstrační příklad spustili, pravděpodobně jste zaznamenali nepříjemné blikání celé vykreslované scény. To je způsobeno tím, že se bitmapa pozadí a bitmapa spritu vykresluje přímo na obrazovku, takže uživatel vidí nejdříve vykreslení pozadí (a tím pádem i přemazání spritu z předchozího snímku) s následným vykreslením spritu na novou pozici. Toto chování je samozřejmě pro většinu aplikací neakceptovatelné, takže je nutné zvolit jiné řešení. V průběhu přibližně čtyřiceti let vývoje grafických subsystémů se ukázalo, že vhodné a současně i dostatečně obecné řešení tohoto problému spočívá v použití takzvaného offscreen bufferu, tj. bitmapy (rastrového obrázku), do níž se postupně vykreslují jednotlivé grafické objekty a na závěr – když je již vše řádně vykresleno, je offscreen buffer buď přenesen na obrazovku, nebo je jednoduše prohozen (operace typu flip) s bufferem, který je v daném okamžiku viditelný na obrazovce (double buffering).
My si dnes představíme první možnost – vykreslování do offscreen bufferu s následným blokovým vykreslením již připraveného bufferu na obrazovku (double buffering bude popsán příště). Kromě obou již připravených bitmap (pozadí a spritu) vytvoříme ještě další bitmapu, jejíž rozměry budou stejné jako rozlišení nastaveného grafického režimu. Bitmapa bude typu BufferedImage a vytvoříme ji voláním metody GraphicsConfiguration.createCompatibleImage(). Vytvoření této bitmapy může probíhat až v metodě paint(), a to z toho důvodu, že zde máme zaručeno, že se vytvoří bitmapa skutečně kompatibilní (se stejným formátem pixelů) s nastaveným grafickým režimem:
@Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; // konstrukce offscreen bufferu, pokud jeste nebyl vytvoren if (this.offscreen == null) { GraphicsConfiguration gc = ((Graphics2D)g).getDeviceConfiguration(); this.offscreen = gc.createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykreslime pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // nad nej vykreslime pohybujici se sprite this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null); // ted jiz konecne vykreslujeme na obrazovku g.drawImage(this.offscreen, 0, 0, null); g.dispose(); this.repaintCount++; }
4. Druhý demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu BufferedImage
Způsob využití bitmapy typu BufferedImage ve funkci offscreen bufferu je ukázán v dnešním druhém demonstračním příkladu nazvaném BlitTest3, jehož zdrojový kód je zobrazen pod tímto odstavcem:
import java.awt.Color; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.WindowConstants; /** * Jednoduchy test metody Graphics.drawImage(). * Pri vykreslovani se nebude pouzivat double buffering. * Predbezne vykreslovani se bude provadet do nevolatilniho obrazku. */ public class BlitTest3 extends JFrame { /** * Generated serial version UID. */ private static final long serialVersionUID = -6455857608125974217L; /** * Sirka testovaciho obrazku. */ private static final int IMAGE_WIDTH = 256; /** * Vyska testovaciho obrazku. */ private static final int IMAGE_HEIGHT = 256; /** * Sirka okna ve fullscreen rezimu. */ private static final int FRAME_WIDTH = 1024; /** * Vyska okna ve fullscreen rezimu. */ private static final int FRAME_HEIGHT = 768; /** * Bitova hloubka nastavovaneho grafickeho rezimu. */ private static final int BPP = 16; /** * Horizontalni souradnice obrazku. */ private int imageX; /** * Vertikalni souradnice obrazku. */ private int imageY; /** * Pocet skutecnych volani metody paint() */ private int repaintCount = 0; /** * Obrazek, ktery se bude prenaset do okna. */ private BufferedImage spriteImage = null; /** * Obrazek zobrazeny na pozadi okna. */ private BufferedImage backgroundImage = null; /** * Obrazek pouzity pro kresleni mimo obrazovku. */ private Image offscreen; /** * Spusteni testu. */ private void run() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); showJFrame(gd); setDisplayMode(gd); createSpriteImage(); createBackgroundImage(); runBenchmark(); gd.setFullScreenWindow(null); this.dispose(); System.out.println("Repaint count: " + this.repaintCount); System.exit(0); } /** * Nastaveni grafickeho rezimu. */ private void setDisplayMode(GraphicsDevice gd) { DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN); gd.setDisplayMode(dm); } /** * 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); // vynutime si prekresleni okna repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Vytvoreni testovaciho obrazku. */ private void createSpriteImage() { this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT); drawIntoSpriteImage(this.spriteImage); } /** * Vytvoreni obrazku pozadi. */ private void createBackgroundImage() { this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); drawIntoBackgroundImage(this.backgroundImage); } /** * Vykresleni vzorku do obrazku. */ private void drawIntoSpriteImage(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(); } /** * Vykresleni vzorku do obrazku na pozadi. */ private void drawIntoBackgroundImage(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 zelene sachovnice g.setColor(Color.green); for (int y = 0; y < height; y += 20) { g.drawLine(0, y, width - 1, y); } g.setColor(Color.green); for (int x = 0; x < width; x += 20) { g.drawLine(x, 0, x, height - 1); } g.dispose(); } /** * Nastaveni parametru okna a zobrazeni. * @param gd */ private void showJFrame(GraphicsDevice gd) { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(0, 0); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.createBufferStrategy(1); this.setVisible(true); gd.setFullScreenWindow(this); } @Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; if (this.offscreen == null) { GraphicsConfiguration gc = ((Graphics2D)g).getDeviceConfiguration(); this.offscreen = gc.createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykreslime pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // nad nej vykreslime pohybujici se sprite this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null); g.drawImage(this.offscreen, 0, 0, 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 spriteImage. */ BufferedImage getSpriteImage() { return this.spriteImage; } /** * Getter pro atribut backgroundImage. */ BufferedImage getBackgroundImage() { return this.backgroundImage; } /** * Spusteni testu. * @param args */ public static void main(String[] args) { new BlitTest3().run(); } }
5. Využití bitmap typu VolatileImage
V předchozím demonstračním příkladu byla pro implementaci offscreen bufferu použita bitmapa typu BufferedImage. Ovšem když si uvědomíme, jakým způsobem je tento buffer použit, můžeme dojít k závěru, že výhodnější by bylo použití bitmapy typu VolatileImage, a to především z toho důvodu, že buffer používáme pouze v jediné metodě paint() a navíc nám nebude vadit, pokud se v intervalu mezi voláním této metody obsah volatilní bitmapy ztratí (což se může stát, ostatně právě z této vlastnosti se odvozuje název tohoto typu rastrových obrázků). Úprava demonstračního příkladu tak, aby pro implementaci offscreen bufferu používal volatilní rastrové obrazy, je však poněkud komplikovanější, neboť je nutné správně reagovat na změnu stavu grafické konfigurace i na zneplatnění obsahu volatilní bitmapy (zneplatněním je zde myšlen přepis hodnot pixelů jinými údaji).
6. Detekce změny stavu grafické konfigurace a zneplatnění obsahu volatilních bitmap
Již v předchozí části tohoto seriálu jsme si řekli, že nezávisle na použití či nepouž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.
Kromě volání metody validate() je ještě nutné volat metodu contentsLost(), která vrací pravdivostní hodnotu true/false podle toho, zda došlo z nějakého důvodu k poškození obrazových dat (pixelů) uložených do volatilního obrázku.
Ukažme si nyní, jak by mohla vypadat kostra metody paint() naprogramované takovým způsobem, aby se volatilní bitmapa správně použila a/nebo obnovila jak ve chvíli, kdy se změní grafický režim (resp. formát ukládaných pixelů), tak i ve chvíli, kdy se obsah bitmapy přemaže jinými daty:
@Override public void paint(Graphics g) { GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(); do { int valid = offscreen.validate(graphicsConfiguration); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { // zmena grafickeho rezimu? offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykresleni cehokoli do volatilni bitmapy "offscreen" ... ... ... // prenos volatilni bitmapy na obrazovku g.drawImage(offscreen, 0, 0, null); g.dispose(); } while (offscreen.contentsLost()); }
Výše uvedený kód skutečně řeší obě situace (a to navíc s použitím málo využívané programové smyčky typu do-while): pokud se změní grafický režim, dojde k vytvoření nové „kompatibilní“ bitmapy, pokud se v průběhu vykreslování obsah volatilní bitmapy přepíše jinými daty, bude se celá smyčka znovu opakovat. Zde ve skutečnosti může dojít k vizuálním chybám, reagovat na přepis hodnoty pixelů lze pro jistotu ještě před zavoláním metody g.drawImage() (přidáním nové podmínky, schválně ponechávám úpravu na váženém čtenáři).
Podívejme se nyní, jak lze upravit metodu paint() z předchozího demonstračního příkladu tak, aby se pro offscreen buffer používala bitmapa typu VolatileImage namísto BufferedImage:
@Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(); do { if (this.offscreen == null) { this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } int valid = this.offscreen.validate(graphicsConfiguration); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { // zmena grafickeho rezimu? this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykresleni pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // vykresleni spritu this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null); // prenos na obrazovku g.drawImage(this.offscreen, 0, 0, null); g.dispose(); } while (this.offscreen.contentsLost()); this.repaintCount ++; }
7. Třetí demonstrační příklad: vykreslení pozadí a spritu do bufferu tvořeného bitmapou typu VolatileImage
Následuje výpis zdrojového kódu dnešního třetího demonstračního příkladu, v němž se již používají volatilní bitmapy. Zda skutečně dojde ke snížení zátěže mikroprocesoru a/nebo grafického akcelerátoru, lze opět zjistit s využitím profileru:
import java.awt.Color; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import javax.swing.JFrame; import javax.swing.WindowConstants; /** * Jednoduchy test metody Graphics.drawImage(). * Pri vykreslovani se nebude pouzivat double buffering. * Predbezne vykreslovani se bude provadet do volatilniho obrazku. */ public class BlitTest4 extends JFrame { /** * Generated serial version UID. */ private static final long serialVersionUID = -6455857608125974217L; /** * Sirka testovaciho obrazku. */ private static final int IMAGE_WIDTH = 256; /** * Vyska testovaciho obrazku. */ private static final int IMAGE_HEIGHT = 256; /** * Sirka okna ve fullscreen rezimu. */ private static final int FRAME_WIDTH = 1024; /** * Vyska okna ve fullscreen rezimu. */ private static final int FRAME_HEIGHT = 768; /** * Bitova hloubka nastavovaneho grafickeho rezimu. */ private static final int BPP = 16; /** * Horizontalni souradnice obrazku. */ private int imageX; /** * Vertikalni souradnice obrazku. */ private int imageY; /** * Pocet skutecnych volani metody paint() */ private int repaintCount = 0; /** * Obrazek, ktery se bude prenaset do okna. */ private BufferedImage spriteImage = null; /** * Obrazek zobrazeny na pozadi okna. */ private BufferedImage backgroundImage = null; /** * Volatilni obrazek pouzity pro kresleni mimo obrazovku. */ private VolatileImage offscreen = null; /** * Spusteni testu. */ private void run() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); showJFrame(gd); setDisplayMode(gd); createSpriteImage(); createBackgroundImage(); runBenchmark(); gd.setFullScreenWindow(null); this.dispose(); System.out.println("Repaint count: " + this.repaintCount); System.exit(0); } /** * Nastaveni grafickeho rezimu. */ private void setDisplayMode(GraphicsDevice gd) { DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN); gd.setDisplayMode(dm); } /** * 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); // vynutime si prekresleni okna repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Vytvoreni testovaciho obrazku. */ private void createSpriteImage() { this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT); drawIntoSpriteImage(this.spriteImage); } /** * Vytvoreni obrazku pozadi. */ private void createBackgroundImage() { this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); drawIntoBackgroundImage(this.backgroundImage); } /** * Vykresleni vzorku do obrazku. */ private void drawIntoSpriteImage(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(); } /** * Vykresleni vzorku do obrazku na pozadi. */ private void drawIntoBackgroundImage(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 zelene sachovnice g.setColor(Color.green); for (int y = 0; y < height; y += 20) { g.drawLine(0, y, width - 1, y); } g.setColor(Color.green); for (int x = 0; x < width; x += 20) { g.drawLine(x, 0, x, height - 1); } g.dispose(); } /** * Nastaveni parametru okna a zobrazeni. * @param gd */ private void showJFrame(GraphicsDevice gd) { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(0, 0); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.createBufferStrategy(1); this.setVisible(true); gd.setFullScreenWindow(this); } @Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(); do { if (this.offscreen == null) { this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } int valid = this.offscreen.validate(graphicsConfiguration); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { // zmena grafickeho rezimu? this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykresleni pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // vykresleni spritu this.offscreen.getGraphics().drawImage(sprite, spriteX, spriteY, null); // prenos na obrazovku g.drawImage(this.offscreen, 0, 0, null); g.dispose(); } while (this.offscreen.contentsLost()); 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 spriteImage. */ BufferedImage getSpriteImage() { return this.spriteImage; } /** * Getter pro atribut backgroundImage. */ BufferedImage getBackgroundImage() { return this.backgroundImage; } /** * Spusteni testu. * @param args */ public static void main(String[] args) { new BlitTest4().run(); } }
8. Problematika průhledných obrázků
Pro zajímavost se ještě podívejme na problematiku vykreslení průhledných (resp. přesněji řečeno poloprůhledných) obrázků. Předchozí testovací příklad upravíme takovým způsobem, aby byl sprite vykreslen poloprůhledně, tj. aby pod ním částečně prosvítalo pozadí. Vykreslování poloprůhledných obrázků je již v současnosti realizováno funkcemi nabízenými grafickými akcelerátory, takže by vlastní práce s těmito obrázky neměla příliš zatěžovat mikroprocesor. Nejdříve se podívejme, jak se vytvoří bitmapa typu BufferedImage s podporou (polo)průhlednosti. Je to snadné, musíme pouze přidat další parametr do metody GraphicsConfiguration.createCompatibleImage():
/** * Vytvoreni testovaciho obrazku. */ private void createSpriteImage() { this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT, Transparency.TRANSLUCENT); drawIntoSpriteImage(this.spriteImage); }
Zajímavější je způsob vykreslení poloprůhledné bitmapy. Zde využijeme metodu Graphics2D.setComposite() pro nastavení operace prováděné při vykreslování, které se předá instance třídy AlphaComposite. Důležité je, že operace typu SRC_OVER je (na rozdíl od některých dalších rastrových operací) skutečně akcelerovaná. Druhým parametrem metody AlphaComposite.getInstance() je hodnota průhlednosti, což je reálné číslo v rozsahu 0..1 odpovídající 0 až 100%:
// vykresleni polopruhledneho spritu float transparency = 0.8f; Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics(); g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency)); g2.drawImage(sprite, spriteX, spriteY, null);
Nová varianta překreslovací rutiny nyní vypadá následovně:
@Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(); do { if (this.offscreen == null) { this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } int valid = this.offscreen.validate(graphicsConfiguration); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { // zmena grafickeho rezimu? this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykresleni pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // vykresleni polopruhledneho spritu float transparency = 0.8f; Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics(); g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency)); g2.drawImage(sprite, spriteX, spriteY, null); // prenos na obrazovku g.drawImage(this.offscreen, 0, 0, null); g.dispose(); } while (this.offscreen.contentsLost()); this.repaintCount ++; }
9. Čtvrtý demonstrační příklad: vykreslení pozadí a poloprůhledného spritu do bufferu tvořeného bitmapou typu VolatileImage
Vykreslení průhledného spritu na neprůhledném pozadí je realizováno v dnešním čtvrtém a současně i posledním demonstračním příkladu, jehož úplný zdrojový kód je vypsán pod tímto odstavcem:
import java.awt.Color; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import javax.swing.JFrame; import javax.swing.WindowConstants; /** * Jednoduchy test metody Graphics.drawImage(). * Pri vykreslovani se nebude pouzivat double buffering. * Predbezne vykreslovani se bude provadet do volatilniho obrazku. * Sprite bude vykreslen polopruhledne. */ public class BlitTest5 extends JFrame { /** * Generated serial version UID. */ private static final long serialVersionUID = -6455857608125974217L; /** * Sirka testovaciho obrazku. */ private static final int IMAGE_WIDTH = 256; /** * Vyska testovaciho obrazku. */ private static final int IMAGE_HEIGHT = 256; /** * Sirka okna ve fullscreen rezimu. */ private static final int FRAME_WIDTH = 1024; /** * Vyska okna ve fullscreen rezimu. */ private static final int FRAME_HEIGHT = 768; /** * Bitova hloubka nastavovaneho grafickeho rezimu. */ private static final int BPP = 16; /** * Horizontalni souradnice obrazku. */ private int imageX; /** * Vertikalni souradnice obrazku. */ private int imageY; /** * Pocet skutecnych volani metody paint() */ private int repaintCount = 0; /** * Obrazek, ktery se bude prenaset do okna. */ private BufferedImage spriteImage = null; /** * Obrazek zobrazeny na pozadi okna. */ private BufferedImage backgroundImage = null; /** * Volatilni obrazek pouzity pro kresleni mimo obrazovku. */ private VolatileImage offscreen = null; /** * Spusteni testu. */ private void run() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); showJFrame(gd); setDisplayMode(gd); createSpriteImage(); createBackgroundImage(); runBenchmark(); gd.setFullScreenWindow(null); this.dispose(); System.out.println("Repaint count: " + this.repaintCount); System.exit(0); } /** * Nastaveni grafickeho rezimu. */ private void setDisplayMode(GraphicsDevice gd) { DisplayMode dm = new DisplayMode(FRAME_WIDTH, FRAME_HEIGHT, BPP, DisplayMode.REFRESH_RATE_UNKNOWN); gd.setDisplayMode(dm); } /** * 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); // vynutime si prekresleni okna repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Vytvoreni testovaciho obrazku. */ private void createSpriteImage() { this.spriteImage = getGraphicsConfiguration().createCompatibleImage(IMAGE_WIDTH, IMAGE_HEIGHT, Transparency.TRANSLUCENT); drawIntoSpriteImage(this.spriteImage); } /** * Vytvoreni obrazku pozadi. */ private void createBackgroundImage() { this.backgroundImage = getGraphicsConfiguration().createCompatibleImage(FRAME_WIDTH, FRAME_HEIGHT); drawIntoBackgroundImage(this.backgroundImage); } /** * Vykresleni vzorku do obrazku. */ private void drawIntoSpriteImage(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(); } /** * Vykresleni vzorku do obrazku na pozadi. */ private void drawIntoBackgroundImage(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 zelene sachovnice g.setColor(Color.green); for (int y = 0; y < height; y += 20) { g.drawLine(0, y, width - 1, y); } g.setColor(Color.green); for (int x = 0; x < width; x += 20) { g.drawLine(x, 0, x, height - 1); } g.dispose(); } /** * Nastaveni parametru okna a zobrazeni. * @param gd */ private void showJFrame(GraphicsDevice gd) { // vypneme okraje a dalsi zbytecnosti :) this.setUndecorated(true); this.setLocation(0, 0); this.setSize(FRAME_WIDTH, FRAME_HEIGHT); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.createBufferStrategy(1); this.setVisible(true); gd.setFullScreenWindow(this); } @Override public void paint(Graphics g) { final BufferedImage sprite = getSpriteImage(); final BufferedImage background = getBackgroundImage(); if (sprite == null || background == null) return; final int spriteX = getImageX() - sprite.getWidth() / 2; final int spriteY = getImageY() - sprite.getHeight() / 2; GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsConfiguration graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(); do { if (this.offscreen == null) { this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } int valid = this.offscreen.validate(graphicsConfiguration); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { // zmena grafickeho rezimu? this.offscreen = graphicsConfiguration.createCompatibleVolatileImage(FRAME_WIDTH, FRAME_HEIGHT); } // vykresleni pozadi this.offscreen.getGraphics().drawImage(background, 0, 0, null); // vykresleni polopruhledneho spritu float transparency = 0.8f; Graphics2D g2 = (Graphics2D)this.offscreen.getGraphics(); g2.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, transparency)); g2.drawImage(sprite, spriteX, spriteY, null); // prenos na obrazovku g.drawImage(this.offscreen, 0, 0, null); g.dispose(); } while (this.offscreen.contentsLost()); 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 spriteImage. */ BufferedImage getSpriteImage() { return this.spriteImage; } /** * Getter pro atribut backgroundImage. */ BufferedImage getBackgroundImage() { return this.backgroundImage; } /** * Spusteni testu. * @param args */ public static void main(String[] args) { new BlitTest5().run(); } }
10. Repositář se zdrojovými soubory dnešních demonstračních příkladů
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ší verze všech čtyř dnes popsaných demonstračních příkladů vykreslujícího bitmapy typu BufferedImage a VolatileImage v celoobrazovkovém grafickém režimu:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | BlitTest2.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/083421bb26ff/jvm/gfx/BlitTest2.java |
2 | BlitTest3.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/083421bb26ff/jvm/gfx/BlitTest3.java |
3 | BlitTest4.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/083421bb26ff/jvm/gfx/BlitTest4.java |
4 | BlitTest5.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/083421bb26ff/jvm/gfx/BlitTest5.java |
11. 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