Ponti agnostici API (es. OpenGL / D3D / qualunque cosa). Li usi, come li fai. Pro's and Con's [chiuso]


12

Stai realizzando un motore 3d. Vuoi il meglio dei mondi multipiattaforma. Improvvisamente ti rendi conto che se vuoi usare Direct3D su macchine Windows e OpenGL su OSX / Linux, dovrai sacrificare le funzionalità supportate di entrambi al minimo comune denominatore.

Alcuni possono usare OpenGL su tre sistemi operativi, poiché sembra essere il minimo comune denominatore da solo. Va tutto bene. Quindi, devi portare il tuo back-end dell'API grafica sul Nintendo GX, devi anche creare un percorso PS3 e Xbox360.

cosa fai? Progettate la vostra API che è il minimo comune denominatore in sé e scrivete implementazioni di back-end per essa per ogni piattaforma o scrivete per ogni piattaforma il suo ramo?

Se decidi di progettare la tua API, usi il modello bridge o il tuo voodoo? Dove finisce la follia dove ti rendi conto di tutto e l'approccio del lavello della cucina deve fermarsi e in pratica hai un motore separato per ogni piattaforma come ramo. Oppure ti attieni a tutto e al lavello della cucina e mantieni le specifiche della piattaforma nelle specializzazioni del modulo back-end per ogni piattaforma.

Risposte:


9

Non sono un fan dell'approccio denominatore meno comune. Se lo fai, potresti finire con funzionalità paralizzate e prestazioni scadenti.

Invece, quello che ho fatto in passato è fornire funzionalità di livello leggermente superiore in una libreria. Quella libreria è (principalmente) API indipendente e può essere utilizzata ovunque, ma l'implementazione della libreria è completamente diversa per differenti piattaforme / backend grafici. Ad esempio, invece di avere una funzione SetStateX (), hai funzioni più elevate come RenderMesh () o CreateRenderTarget ().

Sarà più lavoro di uno strato molto sottile ogni volta che ti sposti su una nuova piattaforma, ma ne varrà la pena perché sarai in grado di implementare le cose nel modo ottimale per quella piattaforma e sarai in grado di prendere vantaggio di funzionalità native uniche.

Ancora una cosa: non aver paura di rompere leggermente l'incapsulamento. Non c'è niente di più frustrante che sapere di essere in una piattaforma con determinate capacità e di non poterle usare. Lasciare una backdoor di qualche tipo in modo che il codice di livello superiore possa sfruttare la piattaforma è molto utile (ad esempio, essere in grado di recuperare il dispositivo D3D o il contesto OpenGL).


3
Penso che tu abbia detto quello che stavo cercando di dire, solo meglio.
AShelly,

6

Tutto quello che posso dire è dare un'occhiata a Ogre3D . È scritto in C ++, Open source (licenza MIT ora) e funziona su tutte le principali piattaforme. Estrae l'API di rendering e può passare dall'uso di DirectX a OpenGL con solo un paio di impostazioni. Tuttavia, non conosco abbastanza le differenze tra i set di funzionalità di DirectX e OpenGL per dire che supporta o non supporta una funzionalità specifica.

Torchlight di Runic Games è stato scritto usando Ogre e l'ho riprodotto su Mac e PC e funziona molto bene su entrambi.


2
+1 per l'approccio Ogre. Lo so, ho letto un po 'del codice. Mi interessava di più ascoltare storie personali sull'approccio e su cosa fanno gli altri in situazioni del genere.
Keyframe

2
Grazie! Beh, lo farei principalmente come faceva Ogre. Ho usato l'approccio Interface / Factory in molti sviluppi multipiattaforma e in realtà non sono sicuro di come lo farei altrimenti. Direi che devi assolutamente lavorare su più piattaforme contemporaneamente. Non provare a codificare tutto su Windows e ad esempio provare a eseguire il port su Mac.
Casey,

Sì! Mi stavo davvero stancando di girare il mio wrapper cross-API, e poi ho iniziato a usare Ogre. Non ho guardato indietro. :)
jacmoe,

1

Non l'ho fatto per la grafica, ma ho creato un toolkit audio multipiattaforma (PC / XBOX / PS2). Abbiamo intrapreso la strada della creazione della nostra API con funzionalità di minimo comune denominatore e funzionalità opzionali specifiche della piattaforma. Ecco alcune lezioni apprese:

La chiave è definire un percorso di elaborazione che incapsuli le capacità principali di ciascuna piattaforma e consenta la crescita. Per fare ciò, è necessario comprendere veramente l'API di basso livello di ogni piattaforma in modo da poter identificare le astrazioni giuste. Assicurati che la catena funzioni per la piattaforma meno capace, fornendo allo stesso tempo l'accesso alle funzionalità avanzate della forma più potente. Fai il lavoro per farlo bene e risparmierai molto impegno in seguito.

Per l'audio, la catena era qualcosa di simile SoundSources -> [Decoders] -> Buffers -> [3D Positioner] ->[Effects] -> Players.

Per la grafica, potrebbe essere Model -> Shapes -> Positioner -> Texturer -> [Lighting] -> [Particle Effects] -> Renderer. (Questo è probabilmente un set completamente sbagliato, non sono un ragazzo di grafica).

Scrivi un'API front-end che gestisce i tuoi oggetti principali e un back-end specifico per la piattaforma che associ l'API alle funzionalità di basso livello. Fornire il massimo sforzo per ogni capacità. Ad esempio, su PC e XBOX il posizionamento audio 3D è stato fatto utilizzando le funzionalità HRTF dei chip audio, mentre PS2 ha utilizzato una semplice panoramica e dissolvenza. Un motore grafico potrebbe fare qualcosa di simile con l'illuminazione.

Implementa il maggior numero possibile di front-end con il codice neutro della piattaforma. Il codice per attaccare un oggetto riverbero su un oggetto sonoro o una risorsa texture su un oggetto forma dovrebbe essere completamente comune, così come il codice per iterare ed elaborare oggetti attivi. D'altra parte, gli oggetti di basso livello possono essere completamente specifici della piattaforma tranne l'interfaccia comune.

Assicurarsi che l'API o i file di configurazione consentano all'utente di specificare opzioni specifiche della piattaforma. Abbiamo cercato di evitare di spingere il codice specifico della piattaforma a livello di gioco mantenendolo nei file di configurazione: il file di configurazione di una piattaforma può specificare "effect: SuperDuperParticleGenerator" mentre un altro dice "effect: SoftGlow"

Sviluppa sicuramente le piattaforme in parallelo. Assicurarsi che le interfacce specifiche della piattaforma siano ben definite e testabili da sole. Questo impedisce molto "è il livello della piattaforma o il livello API?" problemi durante il debug.


0

Sto scrivendo un motore di gioco open source chiamato YoghurtGum per piattaforme mobili (Windows Mobile, Android). Questo è stato uno dei miei grandi problemi. Per prima cosa l'ho risolto in questo modo:

class RenderMethod
{

public:

  virtual bool Init();
  virtual bool Tick();
  virtual bool Render();

  virtual void* GetSomeData(); 

}

Hai notato il void*? Questo perché RenderMethodDirectDrawrestituisce una superficie DirectDraw mentre RenderMethodDirect3Drestituisce un pool di vertici. Anche tutto il resto è stato diviso. Avevo una Spriteclasse che aveva un SpriteDirectDrawpuntatore o un SpriteDirect3Dpuntatore. In un certo senso ha fatto schifo.

Quindi ultimamente, ho riscritto molte cose. Quello che ho ora è a RenderMethodDirectDraw.dlle a RenderMethodDirect3D.dll. In effetti, puoi provare a utilizzare Direct3D, fallire e utilizzare invece DirectDraw. Questo perché l'API rimane la stessa.

Se vuoi creare uno sprite, non lo fai direttamente ma attraverso una fabbrica. Il factory chiama quindi la funzione corretta nella DLL e la converte in un genitore.

Quindi, questo è RenderMethodnell'API:

virtual Sprite* CreateSprite(const char* a_Name) = 0;

E questa è la definizione di cui RenderMethodDirectDraw:

Sprite* RenderMethodDirectDraw::CreateSprite(const char* a_Name)
{
    bool found = false;
    uint32 i;
    for (i = 0; i < m_SpriteDataFilled; i++)
    {
        if (!strcmp(m_SpriteData[i].name, a_Name))
        {
            found = true;
            break;
        }
    }

    if (!found) 
    {
        ERROR_EXPLAIN("Could not find sprite named '%s'", a_Name);
        return NULL; 
    }

    if (m_SpriteList[m_SpriteTotal]) { delete m_SpriteList[m_SpriteTotal]; }
    m_SpriteList[m_SpriteTotal] = new SpriteDirectDraw();

    ((SpriteDirectDraw*)m_SpriteList[m_SpriteTotal])->SetData(&m_SpriteData[i]);

    return (m_SpriteList[m_SpriteTotal++]);
}

Spero che abbia senso. :)

PS Mi sarebbe piaciuto usare STL per questo, ma non c'è supporto su Android. :(

Fondamentalmente:

  • Mantieni ogni rendering nel suo contesto. O una DLL, una libreria statica o solo un gruppo di intestazioni. Finché hai un RenderMethodX, SpriteX e StuffX sei d'oro.
  • Ruba il più possibile dalla fonte degli Ogre.

EDIT: Sì, ha senso avere interfacce virtuali come questa. Se il tuo primo tentativo fallisce, puoi provare un altro metodo di rendering. In questo modo è possibile mantenere agnostico tutto il metodo di rendering del codice.


1
Ha davvero senso avere un'interfaccia virtuale se non si ha mai più di un'implementazione attiva contemporaneamente?
NocturnDragon,

0

Mi piace usare SDL per questo. Ha backend renderer per D3D, OpenGl, OpenGL ES e una manciata di altri backend specifici per piattaforma ed è disponibile per tutti i tipi di piattaforme diverse, e attualmente in fase di sviluppo attivo, con collegamenti a molte lingue diverse disponibili.

Estrae i diversi concetti di rendering e rende la creazione di video (nonché la gestione di suoni e input e poche altre cose) disponibile in una semplice API multipiattaforma. Ed è stato progettato da Sam Lantinga, uno dei principali sviluppatori di Blizzard, in particolare per rendere i giochi di porting e creare giochi multipiattaforma, quindi sai che hai a che fare con una libreria di alta qualità.

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.