Pontes agnósticas de API (ou seja, OpenGL / D3D / Whatever). Você os usa, como os faz. Prós e contras [fechado]

12

Você está criando um mecanismo 3D. Você quer o melhor dos mundos multiplataforma. De repente, você percebe que, se quiser usar o Direct3D em máquinas Windows e o OpenGL no OSX / Linux, terá que sacrificar os recursos suportados pelo denominador menos comum.

Alguns podem usar o OpenGL em três sistemas operacionais ', pois parece ser o denominador menos comum por si só. Tudo está bem. Em seguida, você deve portar o back-end da API de gráficos para o GX da Nintendo, além de criar um caminho para PS3 e Xbox360.

O que você faz? Você cria sua própria API, que é o denominador menos comum em si mesma, e cria implementações de back-end para cada plataforma ou escreve para cada plataforma de sua própria filial?

Se você optar por projetar sua própria API, usa padrão de ponte ou seu próprio vodu? Onde a loucura para onde você percebe que tudo e a abordagem da pia da cozinha devem parar e você basicamente tem um mecanismo separado para cada plataforma como um ramo. Ou você se apega a tudo e à pia da cozinha e mantém os detalhes da plataforma nas especializações dos módulos de back-end para cada plataforma.

Quadro-chave
fonte

Respostas:

9

Não sou fã da abordagem do denominador menos comum. Se você fizer isso, poderá acabar com os recursos danificados e o desempenho ruim.

Em vez disso, o que eu fiz no passado é fornecer funcionalidades de nível um pouco mais alto em uma biblioteca. Essa biblioteca é (principalmente) independente de API e pode ser usada em qualquer lugar, mas a implementação da biblioteca é completamente diferente para diferentes plataformas / elementos gráficos. Por exemplo, em vez de ter uma função SetStateX (), você tem funções mais altas, como RenderMesh () ou CreateRenderTarget ().

Será mais trabalho do que uma camada realmente fina sempre que você mudar para uma nova plataforma, mas valerá a pena completamente, porque você poderá implementar as coisas da maneira ideal para essa plataforma e poderá usar vantagem de recursos exclusivos e nativos.

Mais uma coisa: não tenha medo de quebrar um pouco o encapsulamento. Não há nada mais frustrante que saber que você está em uma plataforma com certos recursos e não conseguir usá-los. Deixar algum tipo de backdoor para que um código de nível superior possa tirar proveito da plataforma é muito útil (por exemplo, conseguir recuperar o dispositivo D3D ou o contexto OpenGL).

Noel Llopis
fonte
3
Eu acho que você disse o que eu estava tentando dizer, só que melhor.
ASHelly
6

Tudo o que posso dizer é dar uma olhada no Ogre3D . Está escrito em C ++, código aberto (licença MIT agora) e é executado em todas as principais plataformas prontas para uso. Ele abstrai a API de renderização e pode mudar do DirectX para o OpenGL com apenas algumas configurações. No entanto, não sei o suficiente sobre as diferenças entre os conjuntos de recursos do DirectX e do OpenGL para dizer que ele suporta ou não um recurso específico.

O Torchlight da Runic Games foi escrito usando o Ogre e eu joguei isso no Mac e no PC e funciona muito bem em ambos.

Casey
fonte
2
+1 na abordagem Ogre. Eu sei disso, li alguns dos códigos. Eu estava mais interessado em ouvir histórias pessoais sobre a abordagem e o que outras pessoas fazem em situações como essa.
Keyframe 14/07
2
Obrigado! Bem, eu faria isso principalmente da maneira que o Ogre fazia. Eu usei a abordagem Interface / Factory em muitos desenvolvimentos entre plataformas e, na verdade, não tenho certeza de como faria isso. Eu diria que você absolutamente precisa trabalhar em várias plataformas ao mesmo tempo. Não tente codificar tudo no Windows e tente portar para o Mac, por exemplo.
Casey
Sim! Eu estava ficando muito cansado de rolar meu próprio invólucro entre APIs e comecei a usar o Ogre. Não olhou para trás. :)
jacmoe
1

Não fiz isso para gráficos, mas criei um kit de ferramentas de áudio para várias plataformas (PC / XBOX / PS2). Seguimos o caminho de criar nossa própria API com um recurso de denominador menos comum e recursos específicos de plataforma opcionais. Aqui estão algumas lições aprendidas:

A chave é definir um caminho de processamento que encapsule os principais recursos de cada plataforma e permita o crescimento. Para fazer isso, você precisa realmente entender a API de baixo nível de cada plataforma para poder identificar as abstrações corretas. Certifique-se de que a cadeia funcione para a plataforma menos capaz, enquanto fornece acesso aos recursos avançados da forma mais adequada. Faça o trabalho para corrigir isso e você economizará muito esforço mais tarde.

Para o áudio, a cadeia era algo parecido SoundSources -> [Decoders] -> Buffers -> [3D Positioner] ->[Effects] -> Players.

Para gráficos, pode ser Model -> Shapes -> Positioner -> Texturer -> [Lighting] -> [Particle Effects] -> Renderer. (Este é provavelmente um cenário completamente errado, eu não sou um cara gráfico).

Escreva uma API de front-end que lide com seus objetos principais e um back-end específico da plataforma que mapeie a API para os recursos de baixo nível. Forneça o melhor esforço para cada recurso. Por exemplo, no PC e no XBOX, o posicionamento do áudio 3D foi feito usando os recursos HRTF dos chips de som, enquanto o PS2 usou um pan e fade simples. Um mecanismo gráfico pode fazer algo semelhante com a iluminação.

Implemente o máximo possível do front end com código neutro de plataforma. O código para anexar um objeto de reverberação a um objeto de som ou um recurso de textura para um objeto de forma deve ser completamente comum, assim como o código para iterar e processar objetos ativos. Por outro lado, os objetos de baixo nível podem ser completamente específicos da plataforma, exceto a interface comum.

Verifique se a API ou os arquivos de configuração permitem ao usuário especificar opções específicas da plataforma. Tentamos evitar levar o código específico da plataforma ao nível do jogo, mantendo-o nos arquivos de configuração: o arquivo de configuração de uma plataforma pode especificar "effect: SuperDuperParticleGenerator", enquanto outro diz "effect: SoftGlow"

Definitivamente desenvolva as plataformas em paralelo. Verifique se as interfaces específicas da plataforma estão bem definidas e testáveis ​​por conta própria. Isso impede muito do "é o nível da plataforma ou da API?" problemas ao depurar.

AShelly
fonte
0

Estou escrevendo um mecanismo de jogo de código aberto chamado YoghurtGum para plataformas móveis (Windows Mobile, Android). Esse foi um dos meus grandes problemas. Primeiro eu resolvi assim:

class RenderMethod
{

public:

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

  virtual void* GetSomeData(); 

}

Você viu o void*? Isso RenderMethodDirectDrawocorre porque retorna uma superfície DirectDraw e RenderMethodDirect3Dretorna um pool de vértices. Todo o resto foi dividido também. Eu tive uma Spriteaula que tinha um SpriteDirectDrawponteiro ou umSpriteDirect3D ponteiro. Isso meio que sugou.

Ultimamente, tenho reescrito bastante as coisas. O que eu tenho agora é um RenderMethodDirectDraw.dlle umRenderMethodDirect3D.dll . Na verdade, você pode tentar usar o Direct3D, falhar e usar o DirectDraw. Isso ocorre porque a API permanece a mesma.

Se você deseja criar um sprite, não o faz diretamente, mas através de uma fábrica. A fábrica chama a função correta na DLL e a converte em um pai.

Então, isso está na RenderMethodAPI:

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

E esta é a definição em 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++]);
}

Espero que isto faça sentido. :)

PS: Eu adoraria ter usado o STL para isso, mas não há suporte no Android. :(

Basicamente:

  • Mantenha todas as renderizações em seu próprio contexto. Uma DLL, uma biblioteca estática ou apenas um monte de cabeçalhos. Contanto que você tenha um RenderMethodX, SpriteX e StuffX, você é de ouro.
  • Roube o máximo que puder da fonte Ogre.

EDIT: Sim, faz sentido ter interfaces virtuais como esta. Se sua primeira tentativa falhar, você pode tentar outro método de renderização. Dessa forma, você pode manter todo o seu método de renderização de código independente.

knight666
fonte
1
Realmente faz sentido ter uma interface virtual se você nunca terá mais de uma implementação ativa ao mesmo tempo?
NocturnDragon
0

Eu gosto de usar SDL para isso. Possui backend de renderizador para D3D, OpenGl, OpenGL ES e vários outros backends específicos de plataforma, e está disponível para todos os tipos de plataformas diferentes, e atualmente em desenvolvimento ativo, com ligações para vários idiomas diferentes disponíveis.

Ele abstrai os diferentes conceitos de renderizador e disponibiliza a criação de vídeo (além de lidar com som e entrada e algumas outras coisas) em uma API simples e multiplataforma. E foi projetado por Sam Lantinga, um dos principais desenvolvedores da Blizzard, especificamente para facilitar a portabilidade de jogos e a criação de jogos de plataforma cruzada, para que você saiba que está lidando com uma biblioteca de alta qualidade.

Mason Wheeler
fonte