S E R V E R B O O K ®


©ServerBook 21/10/2018


Selezionare la Funzione

256utenti totali, ultimo registrato: JOECASTLE_256
Giulio Castellano (JOECASTLE_256) 20/08/2018 10:45, Post 1
20/08/2018 10:45, JOECASTLE_256:

Accelerazione Hardware in Java
In questo articolo vedremo le diverse possibilità che Java offre ai programmatori per il management di superfici accelerate a fini di manipolazioni grafiche e ludiche. Saranno anche elencate le principali system properties per fare un fine tuning delle prestazioni grafiche e sarà introdotta la modalità di accelerazione OpenGL di J2SDK 5.0.


http://www.joecastle.it/public/cpu.asp

Breve storia dell’accelerazione hardware di Java
Nonostante detrattori e polemicisti, l’accelerazione hardware in Java è stata sempre presente sin quasi dagli albori del linguaggio (più realisticamente dal lancio di Java2). Il problema di fondo era che, seppure adattissima per GUI ad intensivo carico grafico, il supporto hardware si proponeva in maniera del tutto insoddisfacente alle esigenze degli sviluppatori di giochi. La creazione di una Image che fosse accelerata avveniva senza alcun controllo da parte dello sviluppatore, il metodo meno incerto era di creare una ImageIcon o utilizzare il metodo createImage di Component per sperare di ottenere una immagine accelerata.

Molte persone che hanno lavorato in Java non sono mai andate oltre la semplice Image ma ci sono oggetti ben più performanti per le manipolazioni grafiche, primo fra tutti BufferedImage che è, di fatto l’implementazione di riferimento per chi vuole realizzare giochi 2D in Java.
Con la release 1.4.2 e l’introduzione del fullscreen Sun ha anche iniziato a svincolare la creazione di superfici accelerate dall’utilizzo di componenti GUI, così da permetterne uno sfruttamento più immediato ed indipendente dal contesto.

In realtà la genesi della nuova architettura hardware non doveva vedere la luce prima di J2SDK 5.0 (previsto per settembre 2004), ma le nostre insistenze nel Java Game Development Forums hanno convinto Sun a fornirne una versione preliminare sin da java 1.4.2. Con il senno di poi la scelta non è stata eccessivamente indovinata. Da un punto di vista pratico, abbiamo avuto qualche mese di vantaggio sul resto del mondo per assaporare le nuove abilità di Java, d’altro canto gran parte del codice generato per usare l’hardware acceleration in 1.4.2 è ridondante e sarà del tutto inutile per l’implementazione definitiva di 5.0.

1.4.2, infatti, ancora prevede sistemi di accelerazione diversi per i diversi sistemi operativi. Per esempio su Windows l’accelerazione 2D è ottenuta tramite DirectDraw e quella delle shape tramite Direct3D, in Linux tutto si fa in accelerazione framebuffer vecchio stile.

5.0, invece, unifica tutti i sistemi operativi ad una pipeline unica anche dal punto di vista delle tecnologie usate: OpenGL. Mentre i binding ufficiali per il 3D saranno (ancora per poco) distribuiti separatamente nel progetto JOGL (ancora in evoluzione troppo repentina per adattarsi ai tempi lunghi di Java), il 2D è ormai perfettamente integrato con questa core tecnology. Le funzionalità di 2D avanzato (filtering, scaling e rotazioni) sono abilitabili all’utilizzo di OpenGL ma mantengono anche un layer di compatibilità con il passato, utilizzando esclusivamente il software mode.

Ottenere informazioni sull’hardware e lo schermo
Sun ha introdotto il fullscreen exclusive mode per favorire da un lato lo sviluppo di giochi e dall’altro supportare molto più a basso livello l’enorme parco di software embedded e dedicato che necessitava di una esecuzione esclusiva a video.

Per controllare il dispositivo di visualizzazione della macchina (schermi multipli compresi), Java introduce un oggetto chiamato GraphicsConfiguration. Gli oggetti GraphicsConfiguration astraggono le proprietà del display e il loro collegamento con l’hardware sottostante, sono il nostro bridge tra la Virtual Machine e le features di accelerazione e controllo hardware.

GraphicsConfiguration permette di fare query sul ColorModel del display, interrogare l’hardware sulle capacità di buffering, ottenere le metriche del display (anche su device logici come gli schermi virtuali), creare immagini compatibili con l’hardware (vedremo questa feature in seguito) e ottenere un oggetto che descriva direttamente il dispositivo hardware che stiamo usando in quel momento.

GraphicsConfiguration non è un oggetto instanziabile di per sé, esso ci viene fornito facendo una query su uno dei tre oggetti heavyweight che lo supportano: Canvas, Frame e JFrame. Come si può notare l’unico componente Swing che permette di ottenere un GraphicsConfiguration è JFrame perché è l’unico che è in connessione diretta con il sistema operativo e che non esiste solo nell’ambito della Virtual Machine.

E’ anche possibile creare i componenti heavyweight di cui sopra linkandoli direttamente ad un oggetto di tipo GraphicsConfiguration ottenuto in altro modo, per esempio per far apparire la finestra in un determinato display:

Frame f = new Frame(gc); // gc è stato ottenuto prima
Rectangle bounds = gc.getBounds();
f.setLocation(10 + bounds.x, 10 + bounds.y);GraphicsConfiguration permette di allocare superfici accelerate tramite il metodo createCompatibleImage. Questo metodo è in qualche modo riprodotto in tutti gli oggetti che derivano Component: il metodo createImage(int, int) esegue esattamente la stessa operazione di createCompatibleImage.
Oltre a questi metodi (pensati per un uso in GUI, dove non interessa più di tanto il formato grafico dell’immagine) da J2SE 5.0 in poi è possibile allocare superfici accelerate anche tramite il costruttore di BufferedImage che permette un potente controllo su tutti gli attributi di encoding e formato dell’immagine.

Immagini Volatili
Prima di analizzare perché le immagini ritornate da createCompatibleImage sono BufferedImage (e perché dovrebbero funzionare meglio delle BufferedImage classiche), vediamo ora un nuovo tipo di immagini introdotto in Java 1.4: VolatileImage.

Una immagine volatile è una superficie accelerata mappata direttamente nella memoria della scheda grafica, superficie che può venire invalidata in qualsiasi momento. Le condizioni che possono provocare la perdita di questa mappatura sono le seguenti:

Cambiare la risoluzione su schermo
Far partire un’applicazione fullscreen
L’avvio di uno screensaver
Task switching forzato
Le VolatileImage sono immagazzinate nella memoria video e sono copiate in VRAM con una semplice copia locale. Questo comporta che il blitting su schermo sia molto più veloce rispetto all’impiego tradizionale di una Image che, risiedendo in memoria di sistema, deve impiegare il bus AGP per essere blittata a video. Lo scopo principe di una VolatileImage è quello di avere un buffering molto veloce in memoria video.

Un altro aspetto importante delle VolatileImage è che non supportano editing diretto. Per essere modificate si deve richiedere uno snapShot dell’immagine stessa con il metodo getSnapshot che però fornisce una BufferedImage, mappata però in memoria di sistema. Si può invece effettuare il blitting su questa superfice senza alcuna problematica.

L’approccio corretto per lavorare con una VolatileImage è il seguente:

// creazione dell'immagine
VolatileImage vImg = createVolatileImage(w, h);

// disegnare nell'immagine
void renderOffscreen() {
do {
if (vImg.validate(getGraphicsConfiguration()) ==
VolatileImage.IMAGE_INCOMPATIBLE)
{
//l'immagine è divenuta incompatibile con GraphicsConfiguration
//e va ricreata
vImg = createVolatileImage(w, h);
}
Graphics2D g = vImg.createGraphics();
//
// codice di rendering qui
//
g.dispose();
} while (vImg.contentsLost());
}Vediamo come andrebbe fatto (a mano) un backbuffering usando una VolatileImage:

// copiare dall'immagine ad una finestra (backbuffering)
// questo codice è portato ad esempio ma NON E'
// la maniera più corretta di fare backbuffering!
// gScreen è un qualsiasi Graphics2D su cui possiamo fare
// blitting (di una finestra, di un controllo etc etc)
do {
int returnCode = vImg.validate(getGraphicsConfiguration());
if (returnCode == VolatileImage.IMAGE_RESTORED) {
// L'immagine va ripristinata
renderOffscreen(); // ripristino
} else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
// Immagine Incompatibile
vImg = createVolatileImage(w, h);
renderOffscreen();
}
gScreen.drawImage(vImg, 0, 0, this);
} while (vImg.contentsLost());Molto lavoro quasi del tutto inutile, visto che Java ha un oggetto chiamato BufferStrategy che amministra l’N-buffering e il page flipping in maniera trasparente. Tuttavia sapere come implementare a mano il proprio algoritmo di double buffering può essere utile, ad esempio per simulare viewport bufferizzati multipli sullo stesso dispositivo grafico, cosa non possibile con BufferStrategy.

Immagini Managed
Java ci mette a disposizione un altro tipo di immagini accelerate, che si utilizza trasparentemente tramite la classe BufferedImage: le Immagini Managed. Queste immagini sono “gestite” da Java e altro non sono che dei raster tenuti in memoria di sistema che vengono bufferizzati in memoria video per aumentare le prestazioni di blitting.

Le Managed Images, inoltre, vengono automaticamente ripristinate quando il sistema perde la superficie accelerata in memoria video (copiando di nuovo il raster dalla memoria di sistema a quella video).

In Java 1.4 l’unico modo di creare una Managed Image era quello di usare i metodi createImage o createCompatibleImage, se si allocava una BufferedImage tramite new essa era solo immagazzinata in memoria di sistema. Per completare la metamorfosi verso il game programming, dalla versione 5.0 qualsiasi BufferedImage allocata sarà creata con accelerazione hardware e con un formato compatbile con il display, a meno di diverse disposizioni da parte del programmatore.

L’immagine mappata nella memoria accelerata è una copia di quella in memoria locale. Se noi modifichiamo l’immagine, lavoriamo sulla memoria locale e quindi tutte le operazioni di blitting o di primitive grafiche saranno software-driven. E’ ovvio quindi che le Managed Images si prestano bene per azioni di tipo sola lettura, come l’acquisizione di uno sprite da file e il suo blitting su schermo.

Volatile o Managed?
Il consiglio è il seguente: usare sempre Managed Images a meno che non c’è bisogno di un’accesso veloce in scrittura sull’immagine stessa.

In piena tradizione conservativa, l’azione di copia in memoria accelerata viene fatta solo al secondo blitting della Managed Image e, una volta eseguita, rimane corrente sino a che la superficie accelerata non viene persa. E’ possibile abilitare l’accelerazione video sin dal primo blit tramite la system property:

sun.java2d.accthreshold = 0Oppure ritardarla all’i-esimo blitting:

sun.java2d.accthreshold = #NUMEROPuò succedere che una Managed Image possa perdere l’accelerazione. Il caso più comune è dovuto all’accesso diretto al raster della BufferedImage in scrittura con getWritableRaster(). Se dobbiamo modificare l’immagine (ad esempio per creare uno sprite con il metodo del paperdolling) è consigliabile utilizzare una VolatileImage per non incappare in queste limitazioni, oppure copiare la BuffredImage modificata in una nuova BufferedImage managed:

//ripristina lo stato di managed per una immagine
//precedentemente modificata
//modified è la nostra immagine la cui accelerazione
//è stata compromessa
//gc è il GraphicsConfiguration corrente

//la nuova immagine
BufferedImage img;
Graphics g;

img = gc.createCompatibleImage(modified.getWidth(), modified.getHeight());
//creiamo un Graphics per scrivere su img
g = img.createGraphics();
//blittiamo modified nella nuova superficie
g.drawImage(modified, 0, 0, null);
//sostituiamo img a modified
modified = img;Scatenare OpenGL
Se le prestazioni video allineate su quelle di DirectDraw (parecchi centinaia di FPS su giochi 2D commerciali) sono troppo poche per voi o se il vosto applicativo fa un pesante uso di scaling, rotazioni e filtering si può chiedere aiuto alla pipeline OpenGL abilitando la seguente system property:

sun.java2d.opengl=trueIn questo modo tutta l’architettura Java2D diventa un engine OpenGL in proiezione ortogonale.
Il “trucco” che Java2D utilizza è il più vecchio del mondo: si genera un viewport OpenGL che abbia lo stesso numero di unità della risoluzione video corrente con origine nell’angolo in alto a sinistra, così da garantire alla vecchie applicazioni 2D di poter sfruttare lo stesso OpenGL senza avere problemi di metrica o di traslazione di coordinate.

D’altro canto, funzioni che in Java2D erano abbastanza costose (zooming, traslazioni/rotazioni/trasformazioni affini della scena, filtering e blending) ora sono in grado di essere eseguite andando ad impegnare la GPU.

Le immagini accelerate vengono convertite in texture applicate su dei quad per garantire il massimo della velocità nelle operazioni grafiche.

Limiti
Come detto in precedenza Java2D ha prestazioni accettabili anche senza dover ingaggiare necessariamente OpenGL. Lo sviluppatore dovrebbe sempre e comunque tenere in considerazione che il mercato dei giochi 2D, che è in piena rifioritura, non è sempre volto ai power gamers che spendono centinaia di euro l’anno per avere sempre l’ultimo mostro di scheda video a disposizione. E’ altrettanto vero che è ormai improbabile che anche le peggiori schede acceleratrici disponibili sul mercato non abbiano alcun supporto per OpenGL ma rimane un problema che l’utente medio neanche si pone: quello dei driver.

Spesso i driver forniti a con la scheda video sono vecchi e quelli a corredo del sistema operativo insufficenti anche per le applicazioni più elementari (come accelerare Java2D per l’appunto). Chi sviluppa giochi 2D dovrebbe essere consapevole di questo e scegliere una accelerazione OpenGL solo se strettamente necessaria per aumentare le prestazioni grafiche e sempre e solo se si siano eliminate tutte le altre possibili forme di inefficenza, come ad esempio un pessimo design d’insieme.



0allegati



S E R V E R B O O K ®


©ServerBook 21/10/2018

torna all'inizio