Java2D a dokonalé vykreslování
V minulé lekci, JTable v Java Swing, jsme se naučili reprezentovat tabulková data.
Tenhle článek bude pojednávat o stylech vykreslování a jak z Javy
vyždímat to nejlepší Budu
předpokládat, že již něco máte v hlavě, tudíž nebudu základní věci
rozebírat dopodrobna.
Tipy na lepší výkon
Akcelerujeme
Jako první věc co bych udělal pro zvýšení výkonu naší aplikace při vykreslování, je zapnutí opengl. Defaultně je toto vypnuté, ale dokáže to zvýšit fps o stovky, i když fps většinou u her omezujeme z jistých důvodů, ale o tomhle tento článek nepojednává. Takže jak na to? Stačí při spouštění aplikace dopsat někam tento řádek:
System.setProperty("sun.java2d.opengl","True");
Poznámka
Tohle funguje na linuxech hlavně, u Windowsů je potřeba stejným způsobem vypnout Direct3D a DirectDraw. (d3d a noddraw)
Antialiasing
Další věc co stojí za zmínku je antialiasing. Kdo neví co je antialiasing tak si to vygooglí, v kostce to text, obrazce a jiné věci pěkně vyhladí a není to pak takové kostrbaté. Opět stačí na to jeden řádek, který využívá metodu třídy Graphics2D/Graphics.
instanceTridyGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Ukázka(nahoře s antialiasingem a dole bez):

Antialiasing
Další postřehy
Jako další věc co bych uvedl je metoda, která informuje hardware o tom, že je potřeba překreslení, tedy lépe synchronizuje komunikaci s HW.
Toolkit.getDefaultToolkit().sync();
Tuto řádku bych doporučil dávat na konec metody, která vykresluje.
Poznámka
Důležité hlavně u linuxů kde se obrazovka celého systému nepřekresluje nějak moc často, u Windowsů není potřeba používat.
Na vykreslování raději používejte metodu paintComponent, má větší prioritu než paint a tím pádem je i rychlejší.
Poznámka
Ještě rychlejší je použít aktivní renderování, což znamená že si uděláme vlastní metodu na kreslení, kde si přes metodu komponenty zavoláme o instanci Graphics a kreslíme co potřebujeme.
Při aktivním renderování je třeba použít metodu setIgnoreRepaint, která zapříčiní že se nebudou volat metody paint a paintComponent, které by mohli zapříčinit nějakou nekalost při vykreslování.
Pokud by Vás zajímalo víc takových "vychytávek", tak doporučuji tuto stránku: http://docs.oracle.com/…d/flags.html
Jak vykreslovat
Existuje spoustu postupů, možností jak vykreslovat, nejlepší budou nejspíš ty co jsou již ověřené a napsané. Já začnu od těch podle mého názoru nejpomalejších. Neberte to jako 100% pravdivé informace, jsou to jen moje postřehy.
Normální vykreslování
Toto snad bude každému jasné, prostě máme ve třídě přepsanou metodu paint, kterou voláme pomocí repaint(), a v ní nějaké ty příkazy na vykreslení. Na hry to sice není nejvhodnější, ale na jednoduché ukázkové programy/hry to stačí.
Page Flipping
Moc často se s tím nesetkáte, k dispozici je to většinou jen u FSEM (Full-Screen Exclusive Mode) a například bufferstrategy už to má v sobě, takže není třeba o tom něco vědět.
Double Buffering
Toto je jeden vůbec z nejrozšířenějších způsobů. Double Buffering bych rozdělil do pár menších podkapitol, protože způsobů jak to udělat je mnoho, takže uvedu aspoň pár z nich.
Jak to funguje?
Nejspíš nebude na škodu si nejdříve vysvětlit jak to vůbec funguje. Princip je vlastně jednoduchý, nejdřív si všechno vyrenderujeme(vykreslíme) na nějaký BufferedImage a pak jej teprve vykreslíme, tím předejdeme různým blikáním a podivnostem při vykreslování.
Vytvoření ideálního BufferedImage
Tady Vám poskytnu metodu, která ukazuje jak získat ideální obrázek na renderování pro každý počítač.
private BufferedImage toCompatibleImage(BufferedImage image) { GraphicsConfiguration cfg = GraphicsEnvironment. getLocalGraphicsEnvironment().getDefaultScreenDevice(). getDefaultConfiguration(); //pokud již je obrázek ideální tak ho prostě vrátí zpět if (image.getColorModel().equals(cfg.getColorModel())) return image; //pokud není tak ho vytvoříme BufferedImage new_image = cfg.createCompatibleImage( image.getWidth(), image.getHeight(), image.getTransparency()); //a vrátíme ho return new_image; }
Tuto metodu bych volal někde v konstruktoru GUIcka při inicializování BufferedImage.
Jednoduchý Double Buffering
Toto je úplně snadné, v nějaké smyčce která nám obstarává chod hry/aplikace, budeme volat 2 metody, render, kterou si vytvoříme a tam budeme vykreslovat celou hru do nějakého BufferedImage a repaint která nám zavolá paint nebo paintComponent která bude už jen vykreslovat ten náš narenderovaný obrázek.
Použití VolatileImage
Používání VolatileImage je trošku složitější (ukládá se do VRAM v grafické kartě) a je zakomponován v dalším příkladě, ale alespoň si ukážeme jak na to.
Nejdříve si musíme deklarovat dvě důležíté proměnné, které budeme hojně používat při validaci obrázku.
private static GraphicsEnvironment env=GraphicsEnvironment.getLocalGraphicsEnvironment(); private static GraphicsConfiguration conf=env.getDefaultScreenDevice().getDefaultConfiguration();
Dále deklarace obrázku pro render.
private VolatileImage renderImage;
Inicializovat budeme v konstruktoru GUIcka takto.
renderImage=createRenderImage(WIDTH,HEIGHT,Transparency.OPAQUE);
//WIDTH a HEIGHT si stanovíte sami ;)
Teď si budeme muset vytvořit tu metodu createRenderImage.
private VolatileImage createRenderImage(int width, int height, int transparency){ VolatileImage image=null; image=conf.createCompatibleVolatileImage(width, height, transparency); int valid=image.validate(conf); if(valid==VolatileImage.IMAGE_INCOMPATIBLE){ image=this.createRenderImage(width, height, transparency); return image; } return image; }
Nyní se podíváme jak napsat metodu pro render.
private void render(){ Graphics2D g2=null; do{ int valid=renderImage.validate(conf); if(valid==VolatileImage.IMAGE_INCOMPATIBLE){ renderImage=createRenderImage(WIDTH,HEIGHT,Transparency.OPAQUE); } try{ g2=renderImage.createGraphics(); g2.clearRect(0,0,WIDTH,HEIGHT); //nějaké to vykreslování } }finally{ g2.dispose(); } }while(renderImage.contentsLost()); //dělá dokud se to zprávně neuloží do obrázku }
No a nakonec si v metodě pro vykreslení přidáme toto a máme hotovo.
if(renderImage.validate(conf)!=VolatileImage.IMAGE_OK){ renderImage=createRenderImage(WIDTH,HEIGHT,Transparency.OPAQUE); render(); } if(renderImage!=null){ instanceTridyGraphics2D.drawImage(renderImage, 0, 0, getWidth(), getHeight(), null); }
Použití BufferStrategy
Jako poslední si ukážeme BufferStrategy, podle mého názoru i spoustu jiných je to nejjednodušší a zároveň i nejefektivnější cesta pro vykreslování.
Nejdříve musíme BufferStrategy vytvořit. To lze buď v Frame, JFrame nebo Canvas (možná lze v Dialog/JDialog ale nezkoušel jsem), pokud to budete chtít dostat do JPanel tak jedině přes parametr v konstruktoru to poslat.
createBufferStrategy(2); //dvojka znamená že budou 2 buffery, můžeme použít klidně i 3
Po vytvoření se lze k BS dostat přes metodu getBufferStrategy() a uložit si do nějaké proměnné abychom ji nemuseli furt volat.
Když máme BufferStrategy tak nepotřebujeme volat žádný repaint, nebo jiné blbosti. Stačí nám jedna metoda kde se udělá vše, vyrenderuje a vykreslí. Taková metoda by mohla vypadat nějak takhle. (volat se samozřejmně bude v nějaké smyčce)
private void updateGui() { Graphics2D g2 = (Graphics2D)bs.getDrawGraphics(); g2.setColor(Color.WHITE); g2.fillRect(0, 0, getWidth(), getHeight()); //vykreslení něčeho g2.dispose(); bs.show(); //zobrazení Toolkit.getDefaultToolkit().sync(); }
Konec
Tohle je ode mně vše. Doufám, že pro Vás bude článek užitečný,
sepsal jsem snad vše co umím ohledně tohoto tématu. Pokud si časem ještě
na něco vzpomenu udělám edit Jakékoliv připomínky, dotazy pište do komentů