Мосты, независимые от API (т. Е. OpenGL / D3D / что угодно). Используете ли вы их, как вы их делаете. Про и Кон [закрыто]

12

Вы делаете 3d движок. Вы хотите лучшее из мультиплатформенных миров. Внезапно вы понимаете, что если вы хотите использовать Direct3D на машинах Windows и OpenGL на OSX / Linux, вам придется пожертвовать поддерживаемыми функциями как наименьшего общего знаменателя.

Некоторые могут использовать OpenGL в трех ОС, поскольку он сам по себе является наименее распространенным знаменателем. Все хорошо. Затем вам нужно перенести бэкэнд вашего графического API на Nintendo GX, вам также нужно создать путь для PS3 и Xbox360.

Чем ты занимаешься? Вы разрабатываете свой собственный API, который сам по себе является наименьшим общим знаменателем, и пишете реализации для него для каждой платформы, или вы пишете для каждой платформы свою ветвь?

Если вы решили создать свой собственный API, вы используете шаблон моста или свой собственный вуду? Где прекращается безумие, когда вы понимаете, что все, и подход к мойке должен прекратиться, и у вас есть отдельный движок для каждой платформы как ответвления. Или вы придерживаетесь всего и кухонной раковины и сохраняете специфику платформы в спецификациях бэкэнд-модуля для каждой платформы.

Keyframe
источник

Ответы:

9

Я не фанат подхода наименьшего общего знаменателя. Если вы сделаете это, у вас могут появиться неполноценные функции и низкая производительность.

Вместо этого, то, что я делал в прошлом, - это предоставление чуть более высокого уровня функциональности в библиотеке. Эта библиотека (в основном) не зависит от API и может использоваться где угодно, но реализация библиотеки совершенно различна для разных платформ / графических бэкэндов. Так, например, вместо функции SetStateX () у вас есть более высокие функции, такие как RenderMesh () или CreateRenderTarget ().

Каждый раз, когда вы переходите на новую платформу, это будет больше работы, чем действительно тонкий слой, но оно того стоит, потому что вы сможете реализовать вещи оптимальным образом для этой платформы и сможете принять Преимущество родных, уникальных функций.

Еще одна вещь: не бойтесь слегка нарушить инкапсуляцию. Нет ничего более разочаровывающего, что вы знаете, что находитесь на платформе с определенными возможностями и не можете их использовать. Оставлять черный ход какого-либо рода, чтобы высокоуровневый код мог использовать преимущества платформы, очень полезно (например, возможность извлечь устройство D3D или контекст OpenGL).

Ноэль Ллопис
источник
3
Я думаю, ты сказал то, что я пытался сказать, только лучше.
AShelly
6

Все, что я могу сказать, это взглянуть на Ogre3D . Он написан на C ++, с открытым исходным кодом (сейчас MIT License) и работает на любой основной платформе из коробки. Он абстрагирует API рендеринга и может переключаться с использования DirectX на OpenGL с помощью всего лишь нескольких настроек. Однако я не знаю достаточно о различиях между наборами функций DirectX и OpenGL, чтобы сказать, что он поддерживает или не поддерживает определенную функцию.

Torchlight от Runic Games был написан с использованием Ogre, и я играл на Mac и PC, и он отлично работает на обоих.

Casey
источник
2
+1 за огров подход. Я знаю об этом, прочитал некоторые из кода. Мне было более интересно услышать личные истории о подходе и о том, что другие люди делают в такой ситуации.
ключевой кадр
2
Благодарность! Ну, я бы сделал это в основном так, как сделал Огре. Я использовал подход Interface / Factory во многих кросс-платформенных разработках, и я на самом деле не уверен, как бы я это сделал в противном случае. Я бы сказал, что вам абсолютно необходимо работать на нескольких платформах одновременно. Не пытайтесь все кодировать в Windows, а затем, например, пытаться портировать на Mac.
Кейси
Да! Я очень устал от прокрутки своей собственной кросс-API-оболочки, а потом я просто начал использовать Ogre. Не оглядывался назад. :)
Жакмо
1

Я не сделал этого для графики, но я создал кроссплатформенный аудио инструментарий (PC / XBOX / PS2). Мы пошли по пути создания нашего собственного API с возможностью наименьшего общего знаменателя, а также дополнительными возможностями для конкретной платформы. Вот некоторые извлеченные уроки:

Ключевым моментом является определение пути обработки, который включает в себя основные возможности каждой платформы и обеспечивает рост. Чтобы сделать это, вам нужно по-настоящему понять API низкого уровня каждой платформы, чтобы вы могли определить правильные абстракции. Убедитесь, что цепочка работает для наименее способной платформы, обеспечивая при этом доступ к расширенным функциям наиболее способной формы. Сделайте работу, чтобы сделать это правильно, и вы сэкономите много усилий позже.

Для аудио цепочка была чем-то вроде SoundSources -> [Decoders] -> Buffers -> [3D Positioner] ->[Effects] -> Players.

Для графики это может быть Model -> Shapes -> Positioner -> Texturer -> [Lighting] -> [Particle Effects] -> Renderer. (Это, вероятно, совершенно неправильный набор, я не графический парень).

Напишите интерфейсный API, который обрабатывает ваши основные объекты, и специфичный для платформы сервер, который отображает API на низкоуровневые возможности. Приложите максимум усилий для каждой возможности. Например, на ПК и XBOX позиционирование 3D-звука осуществлялось с использованием возможностей звуковых чипов HRTF, в то время как PS2 использовала простое панорамирование и фейдеры. Графический движок может сделать что-то похожее с освещением.

Реализуйте как можно больше внешнего интерфейса с помощью нейтрального кода платформы. Код для привязки объекта реверберации к звуковому объекту или актива текстуры к объекту формы должен быть совершенно обычным, как и код для итерации и обработки активных объектов. С другой стороны, объекты низкого уровня могут полностью зависеть от платформы, за исключением общего интерфейса.

Убедитесь, что API или файлы конфигурации позволяют пользователю указывать специфичные для платформы параметры. Мы старались не выдвигать специфичный для платформы код на игровой уровень, сохраняя его в файлах конфигурации: в одном файле конфигурации платформы можно указать «effect: SuperDuperParticleGenerator», а в другой - «effect: SoftGlow»

Определенно развивайте платформы параллельно. Убедитесь, что специфичные для платформы интерфейсы хорошо определены и тестируемы сами по себе. Это предотвращает много "это уровень платформы или уровень API?" проблемы при отладке.

AShelly
источник
0

Я пишу игровой движок с открытым исходным кодом под названием YoghurtGum для мобильных платформ (Windows Mobile, Android). Это была одна из моих больших больших проблем. Сначала я решил это так:

class RenderMethod
{

public:

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

  virtual void* GetSomeData(); 

}

Вы заметили void*? Это потому, что RenderMethodDirectDrawвозвращает поверхность DirectDraw, в то время как RenderMethodDirect3Dвозвращает пул вершин. Все остальное тоже раскололось. У меня был Spriteкласс, который имел либо SpriteDirectDrawуказатель, либо SpriteDirect3Dуказатель. Это вроде отстой.

Так что в последнее время я много переписывал вещи. То, что у меня есть сейчас - это RenderMethodDirectDraw.dllи RenderMethodDirect3D.dll. На самом деле вы можете попробовать использовать Direct3D, потерпеть неудачу и вместо этого использовать DirectDraw. Это потому, что API остается прежним.

Если вы хотите создать спрайт, вы делаете это не напрямую, а через фабрику. Затем фабрика вызывает правильную функцию в DLL и преобразует ее в родительский.

Итак, это в RenderMethodAPI:

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

И это определение в 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++]);
}

Я надеюсь в этом есть смысл. :)

PS Я бы хотел использовать STL для этого, но на Android нет поддержки. :(

В принципе:

  • Держите каждый рендер в своем собственном контексте. Либо DLL, либо статическая библиотека, либо просто набор заголовков. До тех пор, пока у вас есть RenderMethodX, SpriteX и StuffX, вы просто великолепны.
  • Украдите столько, сколько сможете из источника огров.

РЕДАКТИРОВАТЬ: Да, имеет смысл иметь виртуальные интерфейсы, как это. Если ваша первая попытка не удалась, вы можете попробовать другой метод рендеринга. Таким образом, вы можете сохранить весь свой метод визуализации кода независимым.

knight666
источник
1
Действительно ли имеет смысл иметь виртуальный интерфейс, если вы никогда не собираетесь использовать более одной активной реализации одновременно?
NocturnDragon
0

Мне нравится использовать SDL для этого. Он имеет бэкенды рендеринга для D3D, OpenGl, OpenGL ES и нескольких других специфичных для платформы бэкэндов, и он доступен для всех видов различных платформ, и в настоящее время находится в стадии активной разработки, с привязками ко многим различным языкам.

Он абстрагирует различные концепции рендеринга и делает создание видео (а также обработку звука и ввода и некоторые другие вещи) доступными в простом кроссплатформенном API. И он был разработан Сэмом Лантинга, одним из ведущих разработчиков Blizzard, специально для упрощения портирования игр и создания кроссплатформенных игр, чтобы вы знали, что имеете дело с высококачественной библиотекой.

Мейсон Уилер
источник