Puentes agnósticos API (es decir, OpenGL / D3D / Lo que sea). ¿Los usas, cómo los haces? Pro y Con [cerrado]

12

Estás haciendo un motor 3d. Quieres lo mejor de los mundos multiplataforma. De repente, se da cuenta de que si desea utilizar Direct3D en máquinas con Windows y OpenGL en OSX / Linux, tendrá que sacrificar las características compatibles de ambos al mínimo común denominador.

Algunos pueden usar OpenGL en tres SO ', ya que parece ser el mínimo común denominador en sí mismo. Todo es bueno. Luego, debe portar su backend de API de gráficos al GX de Nintendo, también debe hacer una ruta de PS3 y Xbox360.

¿Qué haces? ¿Diseña su propia API que es el mínimo común denominador en sí mismo y escribe implementaciones de back-end para cada plataforma o escribe para cada plataforma su propia rama?

Si opta por diseñar su propia API, ¿utiliza el patrón de puente o su propio vudú? ¿Dónde se detiene la locura donde te das cuenta de todo y el enfoque del fregadero de la cocina debe detenerse y básicamente tienes un motor separado para cada plataforma como una rama? O se adhiere a todo y al fregadero de la cocina y mantiene los detalles de la plataforma en las especializaciones de módulos de back-end para cada plataforma.

Fotograma clave
fuente

Respuestas:

9

No soy fanático del enfoque de denominador menos común. Si hace eso, puede terminar con características paralizadas y bajo rendimiento.

En cambio, lo que he hecho en el pasado es proporcionar una funcionalidad de nivel ligeramente superior en una biblioteca. Esa biblioteca es (principalmente) independiente de la API y se puede usar en cualquier lugar, pero la implementación de la biblioteca es completamente diferente para diferentes plataformas / backends de gráficos. Entonces, por ejemplo, en lugar de tener una función SetStateX (), tiene funciones más altas como RenderMesh () o CreateRenderTarget ().

Será más trabajo que una capa realmente delgada cada vez que se mude a una nueva plataforma, pero valdrá la pena por completo porque podrá implementar las cosas de la manera óptima para esa plataforma, y ​​podrá tomar ventaja de características nativas y únicas.

Una cosa más: no tengas miedo de romper ligeramente la encapsulación. No hay nada más frustrante que saber que estás en una plataforma con ciertas capacidades y no poder usarlas. Dejar una puerta trasera de algún tipo para que el código de nivel superior pueda aprovechar la plataforma es muy útil (por ejemplo, poder recuperar el dispositivo D3D o el contexto OpenGL).

Noel Llopis
fuente
3
Creo que dijiste lo que intentaba decir, solo que mejor.
AShelly
6

Todo lo que puedo decir es echar un vistazo a Ogre3D . Está escrito en C ++, código abierto (licencia MIT ahora) y se ejecuta en todas las plataformas principales listas para usar. Resume la API de representación y puede cambiar de usar DirectX a OpenGL con solo un par de configuraciones. Sin embargo, no sé lo suficiente sobre las diferencias entre los conjuntos de características de DirectX y OpenGL para decir que admite o no una característica específica.

Torchlight de Runic Games fue escrito usando Ogre y lo he jugado en Mac y PC y funciona muy bien en ambos.

Casey
fuente
2
+1 para el enfoque de ogro. Lo sé, he leído algunos de los códigos. Sin embargo, estaba más interesado en escuchar historias personales sobre el enfoque y lo que otras personas hacen en situaciones como esa.
Keyframe
2
¡Gracias! Bueno, lo haría principalmente como lo hizo Ogre. He utilizado el enfoque de Interfaz / Fábrica en muchos desarrollos multiplataforma y en realidad no estoy seguro de cómo lo haría de otra manera. Sin embargo, diría que es absolutamente necesario que trabajes en varias plataformas al mismo tiempo. No intente codificar todo en Windows y luego intente portarlo a Mac, por ejemplo.
Casey
¡Si! Estaba cada vez más cansado de lanzar mi propio contenedor cruzado de API, y luego comencé a usar Ogre. No he mirado hacia atrás. :)
jacmoe
1

No he hecho esto para gráficos, pero creé un kit de herramientas de audio multiplataforma (PC / XBOX / PS2). Tomamos la ruta de crear nuestra propia API con una capacidad de denominador menos común, así como capacidades opcionales específicas de la plataforma. Aquí hay algunas lecciones aprendidas:

La clave es definir una ruta de procesamiento que encapsule las capacidades centrales de cada plataforma y permita el crecimiento. Para hacer esto, debe comprender realmente la API de bajo nivel de cada plataforma para poder identificar las abstracciones correctas. Asegúrese de que la cadena funcione para la plataforma menos capaz, al tiempo que proporciona acceso a las funciones avanzadas de la plataforma más capaz. Haga el trabajo para hacerlo bien y ahorrará mucho esfuerzo más adelante.

Para el audio, la cadena era algo así SoundSources -> [Decoders] -> Buffers -> [3D Positioner] ->[Effects] -> Players.

Para los gráficos, puede ser Model -> Shapes -> Positioner -> Texturer -> [Lighting] -> [Particle Effects] -> Renderer. (Este es probablemente un conjunto completamente incorrecto, no soy un tipo de gráficos).

Escriba una API de front-end que maneje sus objetos principales y una back-end específica de la plataforma que asigne la API a las capacidades de bajo nivel. Proporcione el mejor esfuerzo para cada capacidad. Por ejemplo, en la PC y XBOX, el posicionamiento de audio en 3D se realizó utilizando las capacidades HRTF de los chips de sonido, mientras que PS2 usó un simple pan y fade. Un motor gráfico puede hacer algo similar con la iluminación.

Implemente la mayor cantidad de front-end posible con el código neutral de la plataforma. El código para adjuntar un objeto de reverberación a un objeto de sonido o un activo de textura a un objeto de forma debe ser completamente común, al igual que el código para iterar y procesar objetos activos. Por otro lado, los objetos de bajo nivel pueden ser completamente específicos de la plataforma, excepto por la interfaz común.

Asegúrese de que la API, o los archivos de configuración, permitan al usuario especificar opciones específicas de la plataforma. Intentamos evitar empujar el código específico de la plataforma al nivel del juego manteniéndolo en archivos de configuración: el archivo de configuración de una plataforma puede especificar "efecto: SuperDuperParticleGenerator" mientras que otro dice "efecto: SoftGlow"

Definitivamente desarrollar las plataformas en paralelo. Asegúrese de que las interfaces específicas de la plataforma estén bien definidas y se puedan probar por sí mismas. Esto evita mucho de "¿es el nivel de plataforma o el nivel de API?" problemas al depurar.

AShelly
fuente
0

Estoy escribiendo un motor de juegos de código abierto llamado YoghurtGum para plataformas móviles (Windows Mobile, Android). Este fue uno de mis grandes y grandes problemas. Primero lo resolví así:

class RenderMethod
{

public:

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

  virtual void* GetSomeData(); 

}

¿Viste el void*? Esto se debe a que RenderMethodDirectDrawdevuelve una superficie DirectDraw mientras RenderMethodDirect3Ddevuelve un grupo de vértices. Todo lo demás también estaba dividido. Tuve una Spriteclase que tenía un SpriteDirectDrawpuntero o un SpriteDirect3Dpuntero. Es un poco horrible.

Así que últimamente, he estado reescribiendo muchas cosas. Lo que tengo ahora es a RenderMethodDirectDraw.dlly a RenderMethodDirect3D.dll. De hecho, puede intentar usar Direct3D, fallar y usar DirectDraw en su lugar. Eso es porque la API sigue siendo la misma.

Si quieres crear un sprite, no lo haces directamente sino a través de una fábrica. Luego, la fábrica llama a la función correcta en la DLL y la convierte en una matriz.

Entonces, esto está en la RenderMethodAPI:

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

Y esta es la definición en 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 esto tenga sentido. :)

PD: Me encantaría haber usado STL para esto, pero no hay soporte en Android. :(

Básicamente:

  • Mantenga cada render en su propio contexto. Ya sea una DLL, o una biblioteca estática o simplemente un montón de encabezados. Mientras tengas un RenderMethodX, SpriteX y StuffX, estarás dorado.
  • Roba todo lo que puedas de la fuente Ogre.

EDITAR: Sí, tiene sentido tener interfaces virtuales como esta. Si su primer intento falla, puede probar con otro método de renderizado. De esta manera, puede mantener agnóstico todo su método de representación de código.

caballero666
fuente
1
¿Realmente tiene sentido tener una interfaz virtual si nunca va a tener más de una implementación activa al mismo tiempo?
NocturnDragon
0

Me gusta usar SDL para esto. Tiene backends de renderizador para D3D, OpenGl, OpenGL ES y un puñado de otros backends específicos de plataforma, y ​​está disponible para todo tipo de plataformas diferentes, y actualmente en desarrollo activo, con enlaces a muchos idiomas diferentes disponibles.

Resume los diferentes conceptos de renderizador y hace que la creación de video (así como el manejo de sonido y entrada y algunas otras cosas) esté disponible en una API simple y multiplataforma. Y fue diseñado por Sam Lantinga, uno de los principales desarrolladores de Blizzard, específicamente para facilitar la transferencia de juegos y crear juegos multiplataforma, para que sepa que se trata de una biblioteca de alta calidad.

Mason Wheeler
fuente