Obsah
1. Pohled pod kapotu JVM – zpracování událostí v knihovně SDLJava (dokončení)
2. Událost typu sdljava.event.SDLResizeEvent
3. Demonstrační příklad SDLTest36: detekce změny velikosti okna aplikace
4. Vykreslovací plocha při změně velikosti okna aplikace
5. Demonstrační příklad SDLTest37: vykreslování do okna s měnitelnými rozměry
6. Korektní způsob reakce na změnu velikosti okna aplikace
7. Demonstrační příklad SDLTest38: korektní způsob reakce na změnu velikosti okna aplikace
8. Událost typu sdljava.event.SDLActiveEvent
10. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
1. Pohled pod kapotu JVM – zpracování událostí v knihovně SDLJava (dokončení)
V předchozích částech seriálu o programovacím jazyku Java i o virtuálním stroji tohoto jazyka jsme si vysvětlili způsob práce s událostmi implementovanými v knihovně SDLJava. Připomeňme si, že události, tj. instance třídy sdljava.event.SDLEvent popř. tříd od sdljava.event.SDLEvent odvozených, vznikají například při stisku klávesy, posunu kurzoru myši v okně aplikace, uzavření okna aplikace atd. Typicky se události postupně načítají v nekonečné smyčce (event loop) z takzvané fronty událostí (event queue) a aplikace na ně odpovídajícím způsobem reaguje změnou stavu svých objektů – posunem spritu hráče na obrazovce apod. Typická smyčka událostí v sobě zahrnuje i test na událost typu sdljava.event.SDLQuitEvent, takže je možné korektně reagovat na žádost o ukončení aplikace. Podívejme se na jednoduchý příklad, který jsme již v mnoha obdobách mohli vidět v předchozích částech seriálu:
/** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } if (event instanceof SDLActiveEvent) { // adekvátní reakce na tento typ události } if (event instanceof SDLMouseButtonEvent) { // adekvátní reakce na tento typ události } if (event instanceof SDLMouseMotionEvent) { // adekvátní reakce na tento typ události } } }
2. Událost typu sdljava.event.SDLResizeEvent
Knihovna SDL a zprostředkovaně i knihovna SDLJava se poměrně často používá pro implementaci her. Většinou hry využívají vybraný exkluzivní celoobrazovkový grafický režim, což hráčům umožňuje se plně soustředit na vlastní hru a nenechat se rozptylovat stále komplikovanějším desktopovým prostředím :-) Ovšem v některých případech může být preferováno spuštění aplikace využívající SDL/SDLJava v samostatném okně. Na tomto místě se musí programátor rozhodnout, zda bude mít toto okno pevnou velikost či zda uživatelům umožní velikost (libovolně) měnit. Pokud má být velikost primárního okna aplikace neměnitelná, použije se následující typický způsob inicializace grafického subsystému SDLJava:
/** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo() throws SDLException { final long flags = SDLVideo.SDL_DOUBLEBUF; SDLVideo.setVideoMode(GFX_WIDTH, GFX_HEIGHT, GFX_BPP, flags); }
Sami si můžete vyzkoušet, že velikost okna by nemělo být možné měnit. V případě, že má být velikost okna měnitelná, je nutné při inicializaci grafického subsystému knihovny SDL/SDLJava přidat příznak SDLVideo.SDL_RESIZABLE, a to například následujícím způsobem:
/** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo(int width, int height) throws SDLException { // potrebujeme vytvorit okno, u ktereho je mozne menit jeho velikost. final long flags = SDLVideo.SDL_DOUBLEBUF | SDLVideo.SDL_RESIZABLE; SDLVideo.setVideoMode(width, height, GFX_BPP, flags); }
Po této inicializaci je však nutné, aby aplikace začala korektně reagovat na změnu velikosti okna, což konkrétně znamená, že musí zpracovávat události typu sdljava.event.SDLResizeEvent, které jsou do fronty událostí vloženy po každé změně velikosti okna (závisí na mnoha faktorech, zda je tato událost zapsána pouze při ukončení změny velikosti okna či v jeho průběhu – typicky je chování závislé na nastavení správce oken).
3. Demonstrační příklad SDLTest36: detekce změny velikosti okna aplikace
V dnešním prvním demonstračním příkladu pojmenovaném SDLTest36 je ukázáno základní zpracování události typu sdljava.event.SDLResizeEvent. Nejprve je provedena inicializace grafického subsystému s využitím příznaku SDLVideo.SDL_RESIZABLE a posléze se ve smyčce událostí detekuje událost typu sdljava.event.SDLResizeEvent. Pokud je tato událost ze smyčky událostí přečtena, jsou na standardní výstup vypsány nové rozměry okna zjištěné metodami SDLResizeEvent.getWidth() a SDLResizeEvent.getHeight() – tyto hodnoty přitom reprezentují rozměry vnitřní plochy okna, což je hodnota, která je pro programátora mnohem důležitější, než vnější rozměry okna (dekorace oken je totiž záležitostí správce oken a nikoli vlastní aplikace). Smyčka událostí implementovaná v tomto demonstračním příkladu vypadá následovně:
/** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } // zmena velikosti okna if (event instanceof SDLResizeEvent) { // pretypovani final SDLResizeEvent resizeEvent = (SDLResizeEvent)event; // precist novou velikost okna final int width = resizeEvent.getWidth(); final int height = resizeEvent.getHeight(); // vypsat informaci o zmene velikosti okna System.out.format("Window resized to: %dx%d pixels.\n", width, height); } } }
Podívejme se nyní na úplný zdrojový kód demonstračního příkladu SDLTest36:
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.event.SDLEvent; import sdljava.event.SDLKeyboardEvent; import sdljava.event.SDLResizeEvent; import sdljava.event.SDLKey; import sdljava.event.SDLQuitEvent; import sdljava.video.SDLVideo; import sdljava.x.swig.SDLPressedState; /** * Tricaty sesty demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prace s udalosti typu SDLResizeEvent. * * @author Pavel Tisnovsky */ public class SDLTest36 { /** * Horizontalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_WIDTH = 320; /** * Vertikalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_HEIGHT = 240; /** * Bitova hloubka vybraneho grafickeho rezimu. * (0 znamena automaticky vyber, ovsem lze samozrejme pouzit * i hodnoty 8, 16, 24 ci 32, podle vlastnosti graficke karty) */ private static final int GFX_BPP = 0; /** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo() throws SDLException { // potrebujeme vytvorit okno, u ktereho je mozne menit jeho velikost. final long flags = SDLVideo.SDL_DOUBLEBUF | SDLVideo.SDL_RESIZABLE; SDLVideo.setVideoMode(GFX_WIDTH, GFX_HEIGHT, GFX_BPP, flags); } /** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } // zmena velikosti okna if (event instanceof SDLResizeEvent) { // pretypovani final SDLResizeEvent resizeEvent = (SDLResizeEvent)event; // precist novou velikost okna final int width = resizeEvent.getWidth(); final int height = resizeEvent.getHeight(); // vypsat informaci o zmene velikosti okna System.out.format("Window resized to: %dx%d pixels.\n", width, height); } } } /** * Spusteni osmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_EVERYTHING); // inicializace grafickeho rezimu ci otevreni okna pro vykreslovani initVideo(); // smycka pro zpracovani udalosti eventLoop(); } catch (Exception e) { e.printStackTrace(); } finally { // musime obnovit puvodni graficky rezim // i v tom pripade, ze nastane nejaka vyjimka SDLMain.quit(); } } }
Skript pro překlad na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest36.java
Dávkový soubor pro překlad na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest36.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest36
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest36
4. Vykreslovací plocha při změně velikosti okna aplikace
Při změně velikosti okna aplikace je sice korektně vytvořena událost typu sdljava.event.SDLResizeEvent, která je následně vložena do fronty událostí, ovšem další operace za programátora knihovna SDL ani knihovna SDLJava automaticky neudělá. Na toto chování je zapotřebí si dávat pozor, protože dokonce ani nedojde k modifikaci primární vykreslovací plochy (SDLSurface) vytvořené dříve voláním SDLVideo.setVideoMode(width, height, bpp, flags);. To mj. znamená, že rozměry primární vykreslovací plochy zůstanou stále stejné, což ve výsledku povede k tomu, že se při změně velikosti okna buď nevykreslí celá scéna (část jí bude zakryta), či se naopak v části okna zobrazí jen neměnné pozadí. Takové – většinou neočekávané – chování aplikace je ukázáno na následující trojici obrázků, v nichž je původní velikost okna i původní velikost primární vykreslovací plochy rovna 320×240 pixelům:
Obrázek 1: Originální scéna vykreslená do okna o vnitřních rozměrech 320×200 pixelů.
Obrázek 2: Tatáž scéna, ovšem při změně vnitřních rozměrů okna na 442×248 pixelů.
Obrázek 3: Stále tatáž scéna, ovšem při změně vnitřních rozměrů okna na 204×303 pixelů.
5. Demonstrační příklad SDLTest37: vykreslování do okna s měnitelnými rozměry
Chování popsané v předchozí kapitole je názorně ukázáno na dnešním druhém demonstračním příkladu nazvaném SDLTest37. Po spuštění tohoto příkladu se provede inicializace grafického režimu v metodě initVideo(). Vykreslení scény je provedeno v další metodě drawOnScreen(), která sice korektně vypočte velikosti vykreslovaných obdélníků z rozměrů primární kreslicí plochy, ovšem jak již z předchozí kapitoly víme, nejsou tyto rozměry automaticky aktualizovány. To mj. znamená, že se v metodě drawOnScreen() scéna stále vykresluje do plochy o rozměrech 320×240 pixelů:
/** * Vykresleni obdelniku na obrazovku. */ private static void drawOnScreen() throws SDLException { // ziskat rozmery obrazovky final int width = screen.getWidth(); final int height = screen.getHeight(); // barva vykreslovani long color; // vymazat pozadi color = screen.mapRGB(0, 100, 0); rect.x = BORDER; rect.y = BORDER; rect.width = width - BORDER * 2; rect.height = height - BORDER * 2; screen.fillRect(rect, color); // vykreslit obdelnik na obrazovku color = screen.mapRGB(255, 255, 64); rect.x = width >> 2; rect.y = height >> 2; rect.width = width >> 1; rect.height = height >> 1; screen.fillRect(rect, color); // nutno volat i v pripade, ze neni pouzit double buffering screen.flip(); }
Za zmínku zde stojí upozornění na to, že objekt typu SDLRect je v celé aplikaci vytvořen jen jedenkrát při inicializaci třídy, což znamená, že se při překreslení scény nebude zbytečně vytvářet další objekt, který by bylo následně nutné dealokovat správcem paměti (GC). Podobným způsobem, který ovšem poněkud odporuje principům tvorby programů se zajištěním lokality proměnných, lze zajistit bezproblémový běh aplikace ve virtuálním stroji Javy, který nebude nucen často spouštět správce paměti (ani knihovna SDLJava zbytečně nevytváří nové objekty, a to dokonce ani při vzniku události).
Následuje úplný výpis zdrojového kódu demonstračního příkladu SDLTest37:
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.event.SDLEvent; import sdljava.event.SDLKeyboardEvent; import sdljava.event.SDLResizeEvent; import sdljava.event.SDLKey; import sdljava.event.SDLQuitEvent; import sdljava.video.SDLSurface; import sdljava.video.SDLRect; import sdljava.video.SDLVideo; import sdljava.x.swig.SDLPressedState; /** * Tricaty sedmy demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prace s udalosti typu SDLResizeEvent - vykreslovani do okna s menitelnymi rozmery. * * @author Pavel Tisnovsky */ public class SDLTest37 { /** * Horizontalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_WIDTH = 320; /** * Vertikalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_HEIGHT = 240; /** * Bitova hloubka vybraneho grafickeho rezimu. * (0 znamena automaticky vyber, ovsem lze samozrejme pouzit * i hodnoty 8, 16, 24 ci 32, podle vlastnosti graficke karty) */ private static final int GFX_BPP = 0; /** * Okraje uvnitr okna, ktere zustanou cerne. */ private static final int BORDER = 10; /** * Objekt predsavujici kreslici plochu. */ private static SDLSurface screen = null; /** * Promenna vyuzivana pri prekreslovani - nema smysl zbytecne trapit GC. */ private static SDLRect rect = new SDLRect(); /** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo() throws SDLException { // potrebujeme vytvorit okno, u ktereho je mozne menit jeho velikost. final long flags = SDLVideo.SDL_DOUBLEBUF | SDLVideo.SDL_RESIZABLE; screen = SDLVideo.setVideoMode(GFX_WIDTH, GFX_HEIGHT, GFX_BPP, flags); } /** * Vykresleni obdelniku na obrazovku. */ private static void drawOnScreen() throws SDLException { // ziskat rozmery obrazovky final int width = screen.getWidth(); final int height = screen.getHeight(); // barva vykreslovani long color; // vymazat pozadi color = screen.mapRGB(0, 100, 0); rect.x = BORDER; rect.y = BORDER; rect.width = width - BORDER * 2; rect.height = height - BORDER * 2; screen.fillRect(rect, color); // vykreslit obdelnik na obrazovku color = screen.mapRGB(255, 255, 64); rect.x = width >> 2; rect.y = height >> 2; rect.width = width >> 1; rect.height = height >> 1; screen.fillRect(rect, color); // nutno volat i v pripade, ze neni pouzit double buffering screen.flip(); } /** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } // zmena velikosti okna if (event instanceof SDLResizeEvent) { // pretypovani final SDLResizeEvent resizeEvent = (SDLResizeEvent)event; // precist novou velikost okna final int width = resizeEvent.getWidth(); final int height = resizeEvent.getHeight(); // vypsat informaci o zmene velikosti okna System.out.format("Window resized to: %dx%d pixels.\n", width, height); } } } /** * Spusteni osmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_EVERYTHING); // inicializace grafickeho rezimu ci otevreni okna pro vykreslovani initVideo(); // vykresleni sceny drawOnScreen(); // smycka pro zpracovani udalosti eventLoop(); } catch (Exception e) { e.printStackTrace(); } finally { // musime obnovit puvodni graficky rezim // i v tom pripade, ze nastane nejaka vyjimka SDLMain.quit(); } } }
Skript pro překlad na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest37.java
Dávkový soubor pro překlad na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest37.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest37
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest37
6. Korektní způsob reakce na změnu velikosti okna aplikace
Aby aplikace dokázala korektně reagovat na změnu velikosti okna, je nutné, aby se po každém přijetí události typu sdljava.event.SDLResizeEvent znovu inicializoval grafický režim a aby se tím pádem vytvořila nová primární kreslicí plocha. Metodu initVideo() je tedy nutné upravit tak, aby jako své parametry akceptovala požadované rozměry kreslicí plochy:
/** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo(int width, int height) throws SDLException { // potrebujeme vytvorit okno, u ktereho je mozne menit jeho velikost. final long flags = SDLVideo.SDL_DOUBLEBUF | SDLVideo.SDL_RESIZABLE; screen = SDLVideo.setVideoMode(width, height, GFX_BPP, flags); }
Následně se musí ve smyčce událostí po změně velikosti okna vytvořit nová primární kreslicí plocha, přičemž je korektní původní plochu dealokovat metodou SDLScreen.freeSurface(). Ihned poté se obsah okna musí překreslit voláním metody drawOnScreen():
/** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } // zmena velikosti okna if (event instanceof SDLResizeEvent) { // pretypovani final SDLResizeEvent resizeEvent = (SDLResizeEvent)event; // precist novou velikost okna final int width = resizeEvent.getWidth(); final int height = resizeEvent.getHeight(); // vypsat informaci o zmene velikosti okna System.out.format("Window resized to: %dx%d pixels.\n", width, height); // vytvoreni nove kreslici plochy a prekresleni cele sceny screen.freeSurface(); initVideo(width, height); drawOnScreen(); } } }
Obrázek 4: Originální scéna vykreslená do okna o vnitřních rozměrech 320×200 pixelů.
Obrázek 5: Tatáž scéna, ovšem při změně vnitřních rozměrů okna na 468×214 pixelů.
Obrázek 6: Stále tatáž scéna, ovšem při změně vnitřních rozměrů okna na 204×380 pixelů.
7. Demonstrační příklad SDLTest38: korektní způsob reakce na změnu velikosti okna aplikace
Po úpravě zdrojového kódu demonstračního příkladu SDLTest37 podle popisu z předchozí kapitoly získáme novou aplikaci s odlišným chováním, která je zde pojmenována SDLTest38. Povšimněte si, že změny ve zdrojovém kódu aplikace jsou v tomto případě minimální, ovšem v reálných programech by bylo nutné provést mnohem větší zásahy, a to zejména v metodě, v níž se má překreslit obsah okna (tj. například kde se má vykreslit celý herní svět). Z tohoto důvodu je nutné pečlivě zvážit, zda se má vůbec povolit změna velikosti okna s vykreslovanou scénou či zda bude mít okno neměnné rozměry:
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.event.SDLEvent; import sdljava.event.SDLKeyboardEvent; import sdljava.event.SDLResizeEvent; import sdljava.event.SDLKey; import sdljava.event.SDLQuitEvent; import sdljava.video.SDLSurface; import sdljava.video.SDLRect; import sdljava.video.SDLVideo; import sdljava.x.swig.SDLPressedState; /** * Tricaty osmy demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prace s udalosti typu SDLResizeEvent - vykreslovani do okna s menitelnymi rozmery. * * @author Pavel Tisnovsky */ public class SDLTest38 { /** * Horizontalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_WIDTH = 320; /** * Vertikalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_HEIGHT = 240; /** * Bitova hloubka vybraneho grafickeho rezimu. * (0 znamena automaticky vyber, ovsem lze samozrejme pouzit * i hodnoty 8, 16, 24 ci 32, podle vlastnosti graficke karty) */ private static final int GFX_BPP = 0; /** * Okraje uvnitr okna, ktere zustanou cerne. */ private static final int BORDER = 10; /** * Objekt predstavujici kreslici plochu. */ private static SDLSurface screen = null; /** * Promenna vyuzivana pri prekreslovani - nema smysl zbytecne trapit GC. */ private static SDLRect rect = new SDLRect(); /** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo(int width, int height) throws SDLException { // potrebujeme vytvorit okno, u ktereho je mozne menit jeho velikost. final long flags = SDLVideo.SDL_DOUBLEBUF | SDLVideo.SDL_RESIZABLE; screen = SDLVideo.setVideoMode(width, height, GFX_BPP, flags); } /** * Vykresleni obdelniku na obrazovku. */ private static void drawOnScreen() throws SDLException { // ziskat rozmery obrazovky final int width = screen.getWidth(); final int height = screen.getHeight(); // barva vykreslovani long color; // vymazat pozadi color = screen.mapRGB(0, 100, 0); rect.x = BORDER; rect.y = BORDER; rect.width = width - BORDER * 2; rect.height = height - BORDER * 2; screen.fillRect(rect, color); // vykreslit obdelnik na obrazovku color = screen.mapRGB(255, 255, 64); rect.x = width >> 2; rect.y = height >> 2; rect.width = width >> 1; rect.height = height >> 1; screen.fillRect(rect, color); // nutno volat i v pripade, ze neni pouzit double buffering screen.flip(); } /** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } // zmena velikosti okna if (event instanceof SDLResizeEvent) { // pretypovani final SDLResizeEvent resizeEvent = (SDLResizeEvent)event; // precist novou velikost okna final int width = resizeEvent.getWidth(); final int height = resizeEvent.getHeight(); // vypsat informaci o zmene velikosti okna System.out.format("Window resized to: %dx%d pixels.\n", width, height); // vytvoreni nove kreslici plochy a prekresleni cele sceny screen.freeSurface(); initVideo(width, height); drawOnScreen(); } } } /** * Spusteni osmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_EVERYTHING); // inicializace grafickeho rezimu ci otevreni okna pro vykreslovani initVideo(GFX_WIDTH, GFX_HEIGHT); // vykresleni sceny drawOnScreen(); // smycka pro zpracovani udalosti eventLoop(); } catch (Exception e) { e.printStackTrace(); } finally { // musime obnovit puvodni graficky rezim // i v tom pripade, ze nastane nejaka vyjimka SDLMain.quit(); } } }
Skript pro překlad na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest38.java
Dávkový soubor pro překlad na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest38.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest38
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest38
8. Událost typu sdljava.event.SDLActiveEvent
Posledním typem události, s nímž se v dnešním článku seznámíme, je událost reprezentovaná instancí třídy sdljava.event.SDLActiveEvent. Tato událost může vzniknout v několika případech, zejména při získání či naopak ztrátě fokusu klávesnice (tj. při změně okna, do něhož budou přenášeny stisky kláves) a taktéž ve chvíli, kdy kurzor myši opustí plochu okna aplikace popř. se do této plochy vrátí. Způsob reakce na tento typ událostí závisí čistě na logice aplikace – může být poměrně rozumné pozastavit hru ve chvíli, kdy je ztracen fokus klávesnice, například vlivem zobrazení nějakého modálního dialogu nebo při ručním přepnutí aktivního okna atd. Aby aplikace správně rozeznala, jaký typ události přesně vznikl, je nutné použít následující dvojici příkazů pro získání přesného typu události:
final int type = activeEvent.getSwigActiveEvent().getState(); final boolean gain = activeEvent.getSwigActiveEvent().getGain() == 1;
Proměnná type může nabývat tří hodnot:
Type | Význam |
---|---|
SDLActiveEvent.SDL_APPACTIVE | aktivace či deaktivace celé aplikace (systémově závislé) |
SDLActiveEvent.SDL_APPINPUTFOCUS | získání či ztráta fokusu klávesnice |
SDLActiveEvent.SDL_APPMOUSEFOCUS | kurzor opustil plochu okna či se do okna naopak vrátil |
Rozlišení mezi získáním či ztrátou fokusu atd. je samozřejmě provedeno v závislosti na hodnotě proměnné gain.
9. Demonstrační příklad SDLTest39: detekce získání a ztracení fokusu a detekce přesunu kurzoru myši do plochy okna aplikace
V předchozí kapitole popsaný způsob přečtení informací o ztrátě nebo naopak o získání fokusu klávesnice/myši je implementován v dnešním posledním demonstračním příkladu nazvaném SDLTest39, jehož zdrojový kód je zobrazen pod tímto odstavcem. Pro jednoduchost je výpis přesného typu událostí implementován v rozhodovací konstrukci switch-case, která je v bajtkódu uložena efektivním způsobem a JIT překladač tuto konstrukci taktéž dokážou efektivně přeložit (většinou lépe než sekvenci příkazů if-then-else):
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.event.SDLActiveEvent; import sdljava.event.SDLEvent; import sdljava.event.SDLKeyboardEvent; import sdljava.event.SDLKey; import sdljava.event.SDLQuitEvent; import sdljava.video.SDLVideo; import sdljava.x.swig.SDL_ActiveEvent; import sdljava.x.swig.SDLPressedState; /** * Tricaty devaty demonstracni priklad vyuzivajici knihovnu SDLjava. * * Zaklad prace s udalosti typu SDLActiveEvent. * * @author Pavel Tisnovsky */ public class SDLTest39 { /** * Horizontalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_WIDTH = 320; /** * Vertikalni rozliseni vybraneho grafickeho rezimu ci okna. */ private static final int GFX_HEIGHT = 240; /** * Bitova hloubka vybraneho grafickeho rezimu. * (0 znamena automaticky vyber, ovsem lze samozrejme pouzit * i hodnoty 8, 16, 24 ci 32, podle vlastnosti graficke karty) */ private static final int GFX_BPP = 0; /** * Inicializace grafickeho rezimu ci otevreni okna pro vykreslovani. */ private static void initVideo() throws SDLException { final long flags = SDLVideo.SDL_DOUBLEBUF; SDLVideo.setVideoMode(GFX_WIDTH, GFX_HEIGHT, GFX_BPP, flags); } /** * Smycka pro zpracovani udalosti. */ private static void eventLoop() throws SDLException { while (true) { // precist udalost z fronty SDLEvent event = SDLEvent.waitEvent(); // vyskok ze smycky pro zpracovani udalosti pri vyskytu // udalosti typu SDLQuitEvent if (event instanceof SDLQuitEvent) { return; } // stisk ci pusteni klavesy if (event instanceof SDLKeyboardEvent) { // pretypovani final SDLKeyboardEvent keyEvent = (SDLKeyboardEvent)event; // symbol/kod klavesy final int symbol = keyEvent.getSym(); // ESC ukonci program if (symbol == SDLKey.SDLK_ESCAPE && keyEvent.getState() == SDLPressedState.PRESSED) { return; } } if (event instanceof SDLActiveEvent) { // pretypovani final SDLActiveEvent activeEvent = (SDLActiveEvent)event; // precist typ udalosti final int type = activeEvent.getSwigActiveEvent().getState(); // ziskala aplikace fokus/kurzor mysi ci ji ztratila? final boolean gain = activeEvent.getSwigActiveEvent().getGain() == 1; switch (type) { case SDLActiveEvent.SDL_APPACTIVE: System.out.println("The application is " + (gain ? "" : "in") + "active"); break; case SDLActiveEvent.SDL_APPINPUTFOCUS: System.out.println("The application has " + (gain ? "gain" : "lost") + " input focus"); break; case SDLActiveEvent.SDL_APPMOUSEFOCUS: System.out.println("The application has " + (gain ? "gain" : "lost") + " mouse coverage"); break; default: System.out.println("Unknown SDLActiveEvent type!"); break; } } } } /** * Spusteni osmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_VIDEO); // inicializace grafickeho rezimu ci otevreni okna pro vykreslovani initVideo(); // smycka pro zpracovani udalosti eventLoop(); } catch (Exception e) { e.printStackTrace(); } finally { // musime obnovit puvodni graficky rezim // i v tom pripade, ze nastane nejaka vyjimka SDLMain.quit(); } } }
Skript pro překlad na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest39.java
Dávkový soubor pro překlad na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest39.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest39
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest39
10. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
Všechny čtyři dnes popsané demonstrační příklady byly společně s podpůrnými skripty určenými pro jejich překlad a následné spuštění uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Podobně jako tomu bylo i v předchozích osmi dílech tohoto seriálu, i ke dnešním příkladům jsou přiloženy skripty využitelné pro jejich překlad a spuštění. Navíc byly přidány i skripty využitelné ve Windows:
11. Odkazy na Internetu
- glDrawArrays
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArrays.xml - glDrawElements
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElements.xml - glDrawArraysInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArraysInstanced.xml - glDrawElementsInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElementsInstanced.xml - Root.cz: Seriál Grafická knihovna OpenGL
http://www.root.cz/serialy/graficka-knihovna-opengl/ - Root.cz: Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
http://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/ - Best Practices for Working with Vertex Data
https://developer.apple.com/library/ios/documentation/3ddrawing/conceptual/opengles_programmingguide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html - SDL 1.2 Documentation: SDL_Surface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsurface.html - SDL 1.2 Documentation: SDL_PixelFormat
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpixelformat.html - SDL 1.2 Documentation: SDL_LockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdllocksurface.html - SDL 1.2 Documentation: SDL_UnlockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlunlocksurface.html - SDL 1.2 Documentation: SDL_LoadBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlloadbmp.html - SDL 1.2 Documentation: SDL_SaveBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsavebmp.html - SDL 1.2 Documentation: SDL_BlitSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlblitsurface.html - SDL 1.2 Documentation: SDL_VideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlvideoinfo.html - SDL 1.2 Documentation: SDL_GetVideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetvideoinfo.html - Class BufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferStrategy.html - Class Graphics
http://docs.oracle.com/javase/1.5.0/docs/api/java/awt/Graphics.html - Double Buffering and Page Flipping
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html - BufferStrategy and BufferCapabilities
http://docs.oracle.com/javase/tutorial/extra/fullscreen/bufferstrategy.html - Java:Tutorials:Double Buffering
http://content.gpwiki.org/index.php/Java:Tutorials:Double_Buffering - Double buffer in standard Java AWT
http://www.codeproject.com/Articles/2136/Double-buffer-in-standard-Java-AWT - Java 2D: Hardware Accelerating – Part 1 – Volatile Images
http://www.javalobby.org/forums/thread.jspa?threadID=16840&tstart=0 - Java 2D: Hardware Accelerating – Part 2 – Buffer Strategies
http://www.javalobby.org/java/forums/t16867.html - How does paintComponent work?
http://stackoverflow.com/questions/15544549/how-does-paintcomponent-work - A Swing Architecture Overview
http://www.oracle.com/technetwork/java/architecture-142923.html - Class javax.swing.JComponent
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html - Class java.awt.Component
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html - Class java.awt.Component.BltBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.BltBufferStrategy.html - Class java.awt.Component.FlipBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.FlipBufferStrategy.html - Metoda java.awt.Component.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html#isDoubleBuffered() - Metoda javax.swing.JComponent.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#isDoubleBuffered() - Metoda javax.swing.JComponent.setDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#setDoubleBuffered(boolean) - 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