Come risolviamo i grandi requisiti di memoria video in un gioco 2D?


40

Come risolviamo i grandi requisiti di memoria video in un gioco 2D?


Stiamo sviluppando un gioco 2D (Factorio) in allegro C / C ++ e stiamo affrontando un problema con l'aumento dei requisiti di memoria video all'aumentare del contenuto del gioco.

Attualmente raccogliamo tutte le informazioni sulle immagini che verranno utilizzate per prime, ritagliamo tutte queste immagini il più possibile e le organizziamo in grandi atlanti il ​​più strettamente possibile. Questi atlanti sono memorizzati nella memoria video, la cui dimensione dipende dalle limitazioni del sistema; attualmente sono normalmente 2 immagini fino a 8192x8192, quindi richiedono una memoria video da 256 Mb a 512 Mb.

Questo sistema funziona abbastanza bene per noi, poiché con alcune ottimizzazioni personalizzate e la suddivisione del thread di rendering e aggiornamento siamo in grado di disegnare decine di migliaia di immagini sullo schermo a 60 fps; abbiamo molti oggetti sullo schermo e consentire un grande ingrandimento è un requisito fondamentale. Come vorremmo aggiungere altro, ci saranno alcuni problemi con i requisiti di memoria video, quindi questo sistema non può contenere.

Una delle cose che volevamo provare è avere un atlante con le immagini più comuni e il secondo come cache. Le immagini sarebbero state spostate lì dalla memoria bitmap, su richiesta. Ci sono due problemi con questo approccio:

  1. Il disegno da bitmap di memoria a bitmap video è dolorosamente lento, in allegro.
  2. Non è possibile lavorare con bitmap video in altro che nel thread principale, in allegro, quindi è praticamente inutilizzabile.

Ecco alcuni requisiti aggiuntivi che abbiamo:

  • Il gioco deve essere deterministico, quindi i problemi di prestazioni / i tempi di caricamento non possono mai modificare lo stato del gioco.
  • Il gioco è in tempo reale e presto sarà anche multiplayer. Dobbiamo evitare anche la più piccola balbuzie a tutti i costi.
  • Gran parte del gioco è un mondo aperto continuo.

Il test consisteva nel disegnare 10 000 sprite in un batch per dimensioni da 1x1 a 300x300, più volte per ogni configurazione. Ho fatto i test sulla Nvidia Geforce GTX 760.

  • Dal bitmap video al disegno del bitmap video sono stati impiegati 0,1 us per sprite, quando la bitmap di origine non cambiava tra le singole bitmap (la variante dell'atlante); le dimensioni non contavano
  • Da bitmap video a disegno di bitmap video, mentre la bitmap di origine è stata commutata tra disegni (variante non atlante), ha richiesto 0,56us per sprite; la dimensione non importava, neanche.
  • La memoria da bitmap a disegno di bitmap video era davvero sospetta. Le dimensioni da 1x1 a 200x200 hanno richiesto 0,3us per bitmap, quindi non così orribilmente lente. Per taglie più grandi, il tempo ha iniziato a salire molto drammaticamente, a 9us per 201x201 a 3116us per 291x291.

L'uso dell'atlante aumenta le prestazioni di un fattore maggiore di 5. Se avessi 10 ms per il rendering, con un atlante sono limitato a 100.000 sprite per frame e senza di esso un limite di 20.000 sprite. Questo sarebbe problematico.

Stavo anche cercando di trovare un modo per testare la compressione bitmap e il formato bitmap 1bpp per le ombre, ma non sono riuscito a trovare un modo per farlo in allegro.


1
Grande fan del tuo gioco, ho sostenuto la campagna Indiegogo. Lo abbuffo ogni pochi mesi. Bel lavoro finora! Ho rimosso le domande "quale tecnologia utilizzare" che sono fuori tema per il sito. Le restanti domande sono ancora piuttosto ampie, se hai qualcosa di più specifico dovresti provare a restringere l'ambito.
MichaelHouse

Grazie per il supporto. Allora, dov'è il posto dove chiedere quale tecnologia usare allora? Non sto cercando una risposta con una raccomandazione specifica del motore, ma non sono riuscito a trovare un confronto approfondito dei motori 2D e ispezionarli manualmente uno a uno, inclusi i test di prestazioni e usabilità, richiederebbe anni.
Marwin,

Controlla in fondo a questa pagina alcuni posti dove porre domande come "quale tecnologia usare". Hai una domanda totalmente valida e ragionevole, non è solo il tipo di domanda che affrontiamo in questo sito. Anche se non stai cercando un motore specifico, questo è davvero l'unico modo per rispondere alla domanda "C'è qualche tecnologia che fa X?". Qualcuno potrebbe semplicemente rispondere "sì" e non dare una raccomandazione per uno specifico, ma non sarebbe molto utile. Buona fortuna!
MichaelHouse

2
Stai comprimendo le tue trame?
GuyRT

3
@Marwin, le trame compresse possono comportare molto meglio delle trame non compresse perché riducono la larghezza di banda di memoria necessaria (questo è particolarmente vero sulle piattaforme mobili in cui la larghezza di banda è molto inferiore). Puoi risparmiare un'enorme quantità di memoria semplicemente comprimendo le tue trame. In realtà, l'unico aspetto negativo sono i manufatti che vengono inevitabilmente introdotti.
GuyRT

Risposte:


17

Abbiamo un caso simile con il nostro RTS (KaM Remake). Tutte le unità e le case sono sprite. Abbiamo 18000 folletti per unità e case e terreno, oltre a un altro ~ 6.000 per i colori delle squadre (applicati come maschere). A lungo allungato abbiamo anche circa 30.000 caratteri usati nei caratteri.

Quindi ci sono alcune ottimizzazioni contro atlanti RGBA32 che stai usando:

  • Dividi prima il tuo pool di sprite in molti atlanti più piccoli e usali su richiesta come indicato in altre risposte. Ciò consente anche di utilizzare diverse tecniche di ottimizzazione per ciascun atlante individualmente . Ho il sospetto che avrai un po 'meno di una RAM sprecata, quindi quando imballati con trame così enormi ci sono solitamente aree inutilizzate nella parte inferiore;

  • Prova a usare le trame pallettizzate . Se usi shader puoi "applicare" la palette nel codice shader;

  • Potresti cercare di aggiungere un'opzione per usare RGB5_A1 invece di RGBA8 (se ad esempio le ombre a scacchiera vanno bene per il tuo gioco). Evita Alpha a 8 bit quando possibile e usa RGB5_A1 o formati equivalenti con una precisione minore (come RGBA4), occupano metà dello spazio;

  • Assicurati di impacchettare strettamente gli sprite in atlanti (vedi algoritmi di Bin Imballaggio), ruota gli sprite quando necessario e vedi se riesci a sovrapporre gli angoli trasparenti per gli sprite di rombo;

  • Potresti provare formati di compressione hardware (DXT, S3TC, ecc.) - possono ridurre drasticamente l'utilizzo della RAM, ma verificare la presenza di artefatti di compressione - su alcune immagini la differenza può essere impercettibile (puoi usarla selettivamente come descritto nel primo punto elenco), ma su alcuni - pronuncia molto. Diversi formati di compressione causano diversi artefatti, quindi potresti sceglierne uno che è il migliore per il tuo stile artistico.

  • Cerca di dividere i grandi sprite (ovviamente non manualmente, ma all'interno del tuo packer di texture atlante) in sprite di sfondo statico e sprite più piccoli per le parti animate.


2
+1 per l'utilizzo di DXT, è un'ottima cosa da avere. Ottima compressione e utilizzata direttamente dalla GPU per cui il sovraccarico è minimo.

1
Sono d'accordo con dxt. Potresti anche richiedere il supporto DXT7 (DX11 + hardware), che ha le stesse dimensioni del DXT1 ma (apparentemente) di qualità superiore. Tuttavia dovresti avere il doppio delle trame (un DXT7 e un DXT1) o comprimere / decomprimere durante il caricamento.
Programmdude,

5

Prima di tutto devi usare più atlanti a trama più piccola. Meno trame hai, più difficile e rigida sarà la gestione della memoria. Suggerirei una dimensione dell'atlante di 1024, nel qual caso avresti 128 trame invece di 2, o 2048 nel qual caso avresti 32 trame, che potresti caricare e scaricare secondo necessità.

La maggior parte dei giochi esegue questa gestione delle risorse con limiti di livello, mentre viene visualizzata una schermata di caricamento che scarica tutte le risorse che non sono più necessarie al livello successivo e che vengono caricate.

Un'altra opzione è il caricamento su richiesta, che diventa necessario se i limiti di livello sono indesiderati o anche un singolo livello è troppo grande per adattarsi alla memoria. In questo caso il gioco proverà a prevedere ciò che il giocatore vedrà in futuro e caricarlo in background. (Ad esempio: roba che è attualmente a 2 schermi di distanza dal lettore.) Allo stesso tempo, le cose che non sono state più utilizzate per un periodo più lungo verranno scaricate.

C'è un problema tuttavia, cosa succede quando accade qualcosa di inaspettato che il gioco non è stato in grado di prevedere?

  • Fare il panico e visualizzare una schermata di caricamento fino a quando non viene caricato tutto il materiale necessario. Questo potrebbe sembrare dirompente per l'esperienza.
  • Avere sprite a bassa risoluzione per tutto precaricato, continuare il gioco e sostituirli non appena gli sprite ad alta risoluzione hanno terminato il caricamento. Questo potrebbe sembrare economico per il giocatore.
  • Renderlo influenzato dal gameplay e ritardare l'evento per tutto il tempo necessario. Ad esempio, non generare quel nemico fino a quando non viene caricata la grafica. Non aprire quella cassa del tesoro prima di aver caricato tutta la grafica per quel bottino, ecc.

Ho aggiunto alcuni dei requisiti che ho omesso. La schermata di caricamento o qualsiasi tipo di caricamento non è possibile. Tutto deve essere eseguito in background, o tra le singole zecche (meno di 15 ms per ciascuna) mentre la maggior parte delle volte è già solitamente utilizzata dai preparativi per il rendering e dall'aggiornamento del gioco. In ogni caso, suddividere in parti più piccole potrebbe aggiungere una certa flessibilità nella commutazione, sarebbe sicuramente più veloce. La domanda è quanto influisce sulle prestazioni durante il rendering, poiché il cambio della bitmap di origine durante il disegno rallenta il rendering. Dovrei fare una misurazione esatta per dire quanto.
Marwin,

@Marwin Impatto sulle prestazioni, sì, ma poiché hai a che fare con il 2D, dovresti essere ancora lontano dal diventare un problema. Se il rendering attualmente richiede 1 ms per fotogramma e, usando trame più piccole, all'improvviso occorrono 2 ms, quindi è ancora più che abbastanza veloce da raggiungere 60 FPS costanti. (16ms)
API-Beast

@Marwin Multiplayer è un affare complicato, lo è sempre stato, lo sarà sempre. Molto probabilmente dovrai scendere a compromessi lì. Avrai delle balbuzie, semplicemente perché devi trasferire dati su Internet, i pacchetti andranno persi, i ping potrebbero improvvisamente aumentare, ecc. La balbuzie è inevitabile, quindi ciò che sarà più importante è rendere il modello di rete stesso resistente alla balbuzie. Sapere quando aspettare e come aspettare altri giocatori.
API-Beast

Ciao, la balbuzie è quasi evitabile nel multiplayer, stiamo lavorando su quell'area in questo momento e credo che abbiamo un buon piano. Potrei anche pubblicare e rispondere alla mia domanda descrivendo in dettaglio cosa abbiamo studiato in seguito :) Potrebbe essere una sorpresa, ma il tempo di rendering in realtà è un problema. Abbiamo fatto molte ottimizzazioni per rendere il rendering più veloce. Il rendering principale è ora realizzato in thread separati e altre piccole modifiche. Non dimenticare che, con lo zoom massimo, il giocatore può facilmente vedere decine di migliaia di folletti allo stesso tempo. E vorremmo anche consentire livelli di zoom ancora più elevati in seguito.
Marwin,

@Marwin Hm, gli oggetti 10k di solito non dovrebbero essere un problema per un PC o un laptop moderno se usi un batch adeguato, hai profilato il tuo codice di rendering?
API-Bestia

2

Wow, questa è una grande quantità di sprite di animazione, generate da modelli 3D presumo?

Non dovresti davvero creare questo gioco in 2D grezzo. Quando hai una prospettiva fissa, succede una cosa divertente, puoi mescolare senza soluzione di continuità sprite e sfondi pre-renderizzati con modelli 3D renderizzati dal vivo, che è stato ampiamente utilizzato da alcuni giochi. Se vuoi animazioni così raffinate, sembra il modo più naturale per farlo. Ottieni un motore 3D, configuralo per utilizzare la prospettiva isometrica e renderizzare gli oggetti per i quali continui a utilizzare gli sprite come semplici superfici piane con un'immagine su di essi. E puoi usare la compressione delle trame con un motore 3D, che da solo è un grande passo avanti.

Non penso che caricare e scaricare farà molto per te poiché puoi avere praticamente tutto sullo schermo allo stesso tempo.


2

Innanzitutto trova il formato di trama più efficiente che puoi mentre sei ancora soddisfatto delle immagini del gioco, sia RGBA4444, o compressione DXT ecc. Se non sei soddisfatto dei manufatti generati in un'immagine compressa alfa DXT, sarebbe praticabile rendere le immagini non trasparenti usando la compressione DXT1 per il colore combinata con una texture di mascheramento in scala di grigi a 4 o 8 bit per l'alfa? Immagino che rimarrai su RGBA8888 per la GUI.

Suggerisco di suddividere le cose in trame più piccole usando qualsiasi formato tu abbia deciso. Determina gli elementi che sono sempre sullo schermo e quindi sempre caricati, questo potrebbe essere il terreno e gli atlanti della GUI. Vorrei quindi suddividere gli elementi rimanenti che vengono comunemente resi insieme il più possibile. Non immagino che potresti perdere troppe prestazioni anche andando a 50-100 chiamate di disegno su PC, ma correggimi se sbaglio.

Il prossimo passo sarà generare le versioni mipmap di queste trame come qualcuno ha sottolineato sopra. Non li memorizzerei in un singolo file ma separatamente. Quindi finiresti con le versioni 1024x1024, 512x512, 256x256 ecc. Di ogni file e lo farei fino a quando non raggiungerò il livello più basso di dettagli che vorrei mai essere visualizzato.

Ora che hai le trame separate puoi costruire un sistema di livello di dettaglio (LOD) che carica le trame per il livello di zoom corrente e scarica le trame se non utilizzate. Una trama non viene utilizzata se l'elemento in rendering non è sullo schermo o non è richiesto dal livello di zoom corrente. Prova a caricare le trame nella RAM video in un thread separato dai thread di aggiornamento / rendering. È possibile visualizzare la trama LOD più bassa fino a quando non viene caricata quella richiesta. Questo a volte può comportare un passaggio visibile tra una trama con dettagli bassi / dettagli elevati, ma immagino che ciò avvenga solo quando esegui uno zoom indietro e uno zoom estremamente rapidi mentre ti sposti sulla mappa. Potresti rendere il sistema intelligente tentando di precaricare dove pensi che la persona si sposterà o zoomerà e caricherà il più possibile all'interno degli attuali vincoli di memoria.

Questo è il genere di cose che testerei per vedere se aiuta. Immagino che per ottenere livelli di zoom estremi avrai inevitabilmente bisogno di un sistema LOD.


1

Credo che l'approccio migliore sia quello di dividere la trama in molti file e caricarli su richiesta. Probabilmente il tuo problema è che stai cercando di caricare trame più grandi di cui avresti bisogno per una scena 3D completa e stai usando Allegro per questo.

Per il grande ingrandimento che desideri applicare, devi utilizzare le mipmap. Le mipmap sono versioni a bassa risoluzione delle trame che vengono utilizzate quando gli oggetti sono abbastanza lontani dalla fotocamera. Ciò significa che potresti salvare il tuo 8192x8192 come 4096x4096 e poi un altro di 2048x2048 e così via, e passare alle risoluzioni più basse quanto più piccolo è lo sprite sullo schermo. Puoi salvarli entrambi come trame separate o ridimensionarli durante il caricamento (ma la generazione di mipmap durante il runtime aumenterà i tempi di caricamento per il tuo gioco).

Un sistema di gestione adeguato carica i file richiesti su richiesta e rilascia le risorse quando nessuno li utilizza, oltre ad altre cose. La gestione delle risorse è un argomento importante nello sviluppo del gioco e stai riducendo la tua gestione a una semplice mappatura delle coordinate a una singola trama, che è vicina a non avere alcuna gestione.


1
Dividendo in file, intendi file su HDD? Presumo che potrei archiviare tutte le immagini su RAM per i principianti, e persino copiare da memoria-bitmap a video-bitmap è troppo lento attualmente, quindi il caricamento da HDD sarebbe sicuramente ancora più lento. Avere mimpaps non mi aiuterà, dato che avrò ancora la massima risoluzione in Vram.
Marwin,

Sì, non devi caricare tutto, devi caricare solo ciò che usi. Ogni volta che vuoi cambiare un pixel su una trama caricata in VRAM, il sistema deve spostare TUTTA LA STRUTTURA su RAM, solo per poter modificare un singolo pixel, per riportarlo su VRAM. Se hai tutto in una singola trama, questo comporta lo spostamento di 256 MB su RAM, quindi di nuovo su VRAM, che blocca l'intero computer. Avere separato in diversi file e trame è il modo corretto di farlo.
Pablo Ariel,

La modifica della trama che innesca la copia in memoria e torna a ram si applica solo per bitmap persistenti, la cache probabilmente non sarebbe impostata su persistente, l'unico inconveniente sarebbe la necessità di aggiornarla quando si perde / trova la visualizzazione. Ma in allegro, anche la semplice copia dell'immagine 640X480 da Vram a bitmap di memoria (salva l'anteprima del gioco) richiede molto tempo.
Marwin,

1
Ho bisogno di avere tutto in un'unica grande trama per ottimizzare il disegno stesso, senza di esso, l'effetto di cambiare il contesto tra i singoli sprite rallenta troppo il rendering almeno in allegro. Non fraintendetemi, ma qui siete un tipo di capitano ovvio, poiché mi state vagamente suggerendo di fare qualcosa che chiedo in questa domanda.
Marwin,

1
Avere queste trame mappate con mip in diversi file mi costringerebbe a ricaricare tutti gli atlanti quando il giocatore ingrandisce. Dato che il motore ha solo poche unità di ms per esso, non vedo un modo per farlo.
Marwin,

0

Consiglio di creare più file di atlanti che possono essere compressi con zlib e trasmessi in streaming fuori dalla compressione per ciascun atlante, e avendo più file di atlanti e file di dimensioni inferiori è possibile limitare la quantità di dati immagine attivi nella memoria video. Inoltre, implementa il meccanismo a triplo buffer in modo che tu prepari ogni fotogramma di disegno prima e abbia la possibilità di completarlo più velocemente in modo che gli stutter non vengano visualizzati sullo schermo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.