Войти
ПрограммированиеФорумГрафика

Минимальный мультиплатформенный рендерер (3 стр)

Advanced: Тема повышенной сложности или важная.

Страницы: 1 2 3 4 Следующая »
#30
19:06, 30 окт. 2014

innuendo
> Есть просто спрайты с простеньким шейдером аля sm2.0
Для 2д рендера достаточно, чтобы наружу торчали только текстуры и рендербуфер. Вершинные буфера, декларации, шейдера могут быть внутри либы. Так что все просто: RenderLib::CreateTextureFromImage(...)/DeleteTexture(..); RenderLib::DrawImage(...);


#31
19:12, 30 окт. 2014

Подняться на уровень выше? Поскольку отличий на уровне RenderDevice слишком много, то лучше делать отсечку на уровне мешей и сцены в каком нибудь виде.

#32
22:02, 30 окт. 2014

Ataman
> Для 2д рендера достаточно, чтобы наружу торчали только текстуры и рендербуфер.
> Вершинные буфера, декларации, шейдера могут быть внутри либы.

Это всё понятно. Про 2D и sm2.0 я написал для простого примера, чтобы забыть про сложности DS на мобилках.
Попробую зайти с другой стороны. Например, установка стейтов - D9/GL стейты ставятся по одиночке - DX11 по отдельным группа, DX12 ещё хуже - нужен минимальный механизм для установки стейтов. Минимальный, чтобы не иметь повторов кода. В UE4 сделано аля DX11, в итоге на независимом от gapi уровне нужно думать в рамках DepthStencilState/BlendState - хотя для GL это монописуально.

Или draw команды - должно торчать наружу Draw/DrawIndexed/DrawInstanced/DrawInstancedIndexed или же один выхов Draw с инкапсуляцией внутри ? В UE4 есть отдельно DrawUP - вот оно нуно или нет ?
Вот о чём я веду речь.

Или скажем, CreateTexture - нужно делать отдельно 1D/2D/Cube/3D/Array или опять 1 вызов с сокрытием типа ?
Или те же VB/IB - отдельно по буферам или нечто большее что скрывает VB/IB  ?

zlos
> Поскольку отличий на уровне RenderDevice слишком много

Так вот как раз вопрос - как сделать это минимальным ?


#33
23:41, 30 окт. 2014

innuendo
Кросплатформенный код пишется в основном только одним способом, ты придумываешь один общий интерфейс - и не важно на что он похож, главное чтобы на всех поддерживаемых платформах он реализовывался с минимумом гемороя. Дальше у тебя лишь стоит выбор на каком "уровне перехватить", либо ты делаешь некоторую тонкую абстракцию GAPI, либо поднимаешся выше, и делаешь это на уровне более близком к движку (меши, материалы, etc). В первом случаи - надо по минимумум писать дополнительного кода, во втором гораздо больше гибкости в реализации рендера. Больше гибкости - меньше вероятности что столкнешся с платформой которая рушит всю твою "красивую идилию"  прокладки GAPI. Все остальное - it depends, от того что ты хочешь на самом деле получить.

#34
0:10, 31 окт. 2014

innuendo
> Например, установка стейтов
Т.е. ты хочешь именно низкоуровный универсальный кроссплатформенный гапи? А сам пайплайн рендера делать общий (установка шейдеров, стейтов, буферов, дип)?

Ок. Ну давайте представим, что мы разрабатываем кроссплатформенный низкоуровневый гапи.
Интерфейс один, должно работать на Дх11,Гл3.х,ГлЕС2(3?).
По идее таких рендерных движков должно быть много, и можно систематизировать, как делают.

1. Стейты. Как в дх11 самое простое. Иначе придется делать кэш.
2. Текстуры. Либо куча форматов и конвертация через ассет пайплайн. Или как-то для платформы автоматом выбираются через юзеровскую семантику. Типы самплеров разные естественно.
3. Геометрия. Буфера отдельно для простоты и совместимости, слоты, частота. Декларация в каком-то виде.
4. Шейдера. Ну тут самая мякотка. По идее должны грузиться из исходников и бинарников. Кэширование внутри. Установка констант под вопросом. Линковка с вершинной декларацией. Именно на необходимости переписывать шейдера все и ломаются. ;)
5. Матрицы, СК, камеры, вьюпорты. Тут нужно просто выбрать какому стандарту следовать и в шейдерах учитывать.
6. FBO. Придется делать как в гл отдельным объектом.
7. Капсы на все.

#35
0:30, 31 окт. 2014

innuendo
> Так вот как раз вопрос - как сделать это минимальным ?
Никак. От слова "вообще".

#36
2:20, 31 окт. 2014

zlos
> Никак. От слова "вообще".
+100500. Или общее фигово, или частное хорошо.

#37
3:49, 31 окт. 2014

bazhenovc
> Посмотри в сторону BGFX, она очень удачно сделана в плане
> мультиплатформенности.
Круто, чёто я не видел этого раньше.

Ataman
> Ок. Ну давайте представим, что мы разрабатываем кроссплатформенный
> низкоуровневый гапи.
> ...
Очень хорошо всё написал, по-моему.

Я в своё время проделал работу по абстрагированию низкого уровня DX11, OpenGL >3.2, WebGL в моём движке Inanity (тема на gamedev). Коротким кодом не получится.

По пунктам, как у меня сделано:
> 1. Стейты. Как в дх11 самое простое. Иначе придется делать кэш.
Частично как в dx11, частично как в OpenGL. Да, для dx11 с кэшем.
> 2. Текстуры. Либо куча форматов и конвертация через ассет пайплайн. Или как-то
> для платформы автоматом выбираются через юзеровскую семантику. Типы самплеров
> разные естественно.
Ну вроде как DXT/S3TC одно и то же и на дх, и на гл, несжатые форматы одинаковы тоже. Парсеры форматов типа PNG/TGA платформонезависимые. Перевернутость текстурных координат в гл легко нивелировать.
> 3. Геометрия. Буфера отдельно для простоты и совместимости, слоты, частота.
> Декларация в каком-то виде.
Да, ровно так, отдельные буфера, слоты. glVertexAttribDivisor может внезапно не поддерживаться в рантайме, тогда приходится выбирать обходной путь на уровне выше.
> 4. Шейдера. Ну тут самая мякотка. По идее должны грузиться из исходников и
> бинарников. Кэширование внутри. Установка констант под вопросом. Линковка с
> вершинной декларацией. Именно на необходимости переписывать шейдера все и
> ломаются. ;)
Да, самая жесть. У меня сделана полная абстракция - шейдерный DSL поверх C++. Презентация (на английском).
Установка констант - через абстрактные константные буферы. Если в рантайме выясняется, что не поддерживаются (uniform buffers в OpenGL), прозрачно эмулируются через отдельные переменные.
> 5. Матрицы, СК, камеры, вьюпорты. Тут нужно просто выбрать какому стандарту
> следовать и в шейдерах учитывать.
> 6. FBO. Придется делать как в гл отдельным объектом.
> 7. Капсы на все.
У меня капсов получилось мало, по сути только на разновидности инстансинга и MRT. Инстансинг абстрагируется объектом более высокого уровня.

Короче, сделать можно, но тяжело. Абстракции и "общий знаменатель" выбирались отдельно для каждой фичи, долго и мучительно, исходя из реальных возможностей DX11 и GL. Чтобы одновременно обеспечить производительность, и как можно более усреднить оба GAPI. И сейчас для DX12/Metal/Mantle я хз, как добавить поддержку, вроде как архитектурные отличия значительные, и нужен теперь другой общий знаменатель.

#38
7:45, 31 окт. 2014

Вы зачем пытаетесь написать весь рендер общим?
Делайте логику общую, и прочие элементы, кроме рендера!!
А рендер реализовывайте именно под каждый АПИ свой.

Т.е. вот надо вам тени сделать:
и делаете версию на dx11, а потом версию на opengl.

и геморроя не будет никакого!

но всё ещё остаются нужны общие для всех АПИ модели и текстуры.
модели понятно, а зачем текстуры?
а вот потому, что текстуры - загружаемая хрень, как и модели.
всё, что загружаемое - нужно запихивать в интерфейсы.

#39
9:06, 31 окт. 2014

quyse
> Коротким кодом не получится.

Мало имеется ввиду число методов этого самого RenderDevice. Создание текстур - CreateTexture на все случая или 1 CreateTexture все случая ?

> . И сейчас для DX12/Metal/Mantle я хз, как добавить поддержку, вроде как
> архитектурные отличия значительные, и нужен теперь другой общий знаменатель.

Вот тут и начинается самое интересное :)


Void12
> Вы зачем пытаетесь написать весь рендер общим?

Никто не пытается так сделать

> Делайте логику общую, и прочие элементы, кроме рендера!!
> А рендер реализовывайте именно под каждый АПИ свой.

Где и как начинается платформозависимая часть ?

> а вот потому, что текстуры - загружаемая хрень, как и модели.
> всё, что загружаемое - нужно запихивать в интерфейсы.

Покажи на примерах


Ataman
> Т.е. ты хочешь именно низкоуровный универсальный кроссплатформенный гапи? А сам
> пайплайн рендера делать общий (установка шейдеров, стейтов, буферов, дип)?

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

thevlad
> Кросплатформенный код пишется в основном только одним способом, ты придумываешь
> один общий интерфейс

Как ты думаешь, в чём весь вопрос ? В этом самом общем интерфейсе :)

#40
13:00, 31 окт. 2014

Итак мой опыт DX9/11 OGL 3.3/ es 2.0 3.0 / Metal

Каждый апи наследует и создате создает 3 вещи - Текстуры, Шейдеры, Буферы (для вершин и нидексов ).

Шейдеры все отеделены, hlsl glsl metal. Но это херовое решение, лучьше поебаться и объеденить в один

Далее рендер DX и Metal рисуют сверху в низ, OGL на оборот - матрицу процекции надо будет перевернуть

Для метала и Огл-на надо сделать поддежку инклуов для шейдеров - они это не умеют

Компиляцию текстур, разные платформы...

OpenGL ES 2.0 не поддерживает half float для вершин - мы используем float ( byte поддерживает )

OpenGL ES 3.0 поддерживает инстансинг и MRT А вот 2.0 не умеет, инстанскинг можно развренуть(отрендерить по отдельности)

стенсил почти везде одинаков, в Metal текстура глубены и стенсил текстура отеделены

выстовление RenderTargeta лучше всего подсмотреть в Metal с таким подходом он будет работать везде. Очень продуманно!

// У нас теперь так выставляется

struct NU_API colorAttachmentDesc{
    TextureGpuPtr texture;
    TexLoadAction loadAction;
    TexStoreAction storeAction;
    Color clearColor;
    u32 slice;
    colorAttachmentDesc(){
        loadAction = TexLoadActionDontCare;
        storeAction = TexStoreActionDontCare;
        slice = 0;
    }
    void Set(Texture *tex, TexLoadAction loadAction, TexStoreAction storeAction, Color clearColor = Color()){
        nu_assert_ptr(tex);
        this->loadAction = loadAction;
        this->storeAction = storeAction;
        this->clearColor = clearColor;
        texture = tex->GetTexureGpu();
    }
};
struct NU_API depthAttachmentDesc{
    TextureGpuPtr texture;
    TexLoadAction loadAction;
    TexStoreAction storeAction;
    float clearDepth;
    depthAttachmentDesc(){
        loadAction = TexLoadActionDontCare;
        storeAction = TexStoreActionDontCare;
        clearDepth = 1.0;
    }
    void Set(Texture *tex, TexLoadAction loadAction, TexStoreAction storeAction, float clearDepth =  1.0){
        nu_assert_ptr(tex);
        this->loadAction = loadAction;
        this->storeAction = storeAction;
        this->clearDepth = clearDepth;
        texture = tex->GetTexureGpu();
    }
};
struct NU_API stencilAttachmentDesc{
    TextureGpuPtr texture;
    TexLoadAction loadAction;
    TexStoreAction storeAction;
    u32 clearStencil;
    stencilAttachmentDesc(){
        loadAction = TexLoadActionDontCare;
        storeAction = TexStoreActionDontCare;
        clearStencil = 0;
    }
    void Set(Texture *tex, TexLoadAction loadAction, TexStoreAction storeAction, u32 clearStencil = 0){
        nu_assert_ptr(tex);
        this->loadAction = loadAction;
        this->storeAction = storeAction;
        this->clearStencil = clearStencil;
        texture = tex->GetTexureGpu();
    }
};
struct NU_API RenderPassDesc{
    FromStruct from;
    colorAttachmentDesc colorAttachments[NU_MAX_COLOR_ATTACHMENTS];
    depthAttachmentDesc depthAttachment;
    stencilAttachmentDesc stencilAttachment;
    RenderPassDesc(FromStruct set_from = FROM){
        from = set_from;
    }
};

Конечно не все платформы умеют не загружать или не выгружать текстуры, но если умеют это будет плюсом

Далее такие кеши как Fbo Vao Programs СЕЙЧАС реализованы в нутри отдельных платформ. Есть смысл унификации. Т.к.будет дублирование


ТЕПЕРЬ ВАЖНОЕ

Работа с Graphics API должа быть исключительно в отдельном потоке - на iOS при вызове glDrawInstancing можно получить ожидание драйвера на 20 мс...
По этой причине у нас так - формирования стека задач идет в основном рендере, а выполнение команд уже в потоке где идет работа с GPU

// сюда заносяться все команды, поставь шейдер, поставь текстуру, то се - пятое десятое
http://www.everfall.com/paste/id.php?3hqn3ejz1t79

//А потом в потоке GAPI делаем проход по командам, и усе.
http://www.everfall.com/paste/id.php?vbxy8fd9xy65

Если будут вопросы, отвечу)

#41
14:05, 31 окт. 2014

FDsagizi
> на iOS при вызове glDrawInstancing можно получить ожидание драйвера на 20 мс...
о_О 20ms на один батч? Это же дич какая-то. С такими цифрами выноси не выноси - жопа же будет.

#42
14:13, 31 окт. 2014

FDsagizi
> Каждый апи наследует и создате создает 3 вещи - Текстуры, Шейдеры, Буферы (для
> вершин и нидексов ).

Как текстуры создаются ?

#43
14:23, 31 окт. 2014

innuendo
Чувааак, бери BGFX и не заморачивайся :)

#44
15:13, 31 окт. 2014

MrShoor
> о_О 20ms на один батч? Это же дич какая-то. С такими цифрами выноси не выноси - жопа же будет.
Ну как жопа, у нас время кадра порядка 30 мс.
У них так устроен драйвер, что синхронизация кадра может пройти в случайном баче. Причем это не значит что будет 20 и 20 и 20 - нее, время кадра стабильно.

Тут фишка в том, что на PC такой проблемы не наблюдается, ибо там все команды уходят в отдельный тред, и "зависания" происходят в нутри него

innuendo
> Как текстуры создаются ?
Эм, не понял суть вопроса))
Вообще с текстурами шейдерами и буферами у нас так.
У нас есть класс текстуры

Texture

и есть класс

TextureGPU

GPU - нужен для наследования рендером, в нутри него жевет указатель на Api, так же он умеет загружать текстурные данные асинхронно, и у него идет межпоточный реф коунт.

к примеру мы создали текстуру

Texture *tex = new Texture();
tex->SetLoadFromFile("File Name..."); В этот момент ничего не происходит. Просто есть текстура которая знает имя файла и все.

Если эта текстура поадает в рендер. То у нее идет запрос на получение GPU ресурса, собсвенно сам стек работает исключительно с ГПУ ресурсами, сами текстуры он не хранит - для чего это надо?

К примеру мы созадали РендерТаргет текстуру

Texture *tex = new Texture();
tex->SetSize(256,256);
tex->SetFormat(TF_D_32);

далее мы его отправили на рендер, и в следующем кадры изменили

tex->SetSize(500,500) - вот тут бывает трудная ситуация ибо как быть ? Рендеру нужна та самая текстура, 256 256, а мы уже хотим из нее сделать 500 500.
По этому разделение рулит.

Texture SetSize(int set_w, int set_h){
    SafeRelease(gpu_resoure); // отпускаем ресурс
    w = set_w;
    h  = set_h;
}

и если вдруг эта текстура снова понадобиться, для нее будет создан новый ресурс
Так же это очень удобно для смены рендера на лету - ибо мы просто "опускаем" все ГПУ ресурсы, и они потом автоматом пересоздаются при запросе на рендер, но уже с для другого Api

Если интересует как выглядет класс внутри:

Базовый хидер: http://www.everfall.com/paste/id.php?4cg0re6gnr9c
Базовый срр: http://www.everfall.com/paste/id.php?mio3n42pnion

Реализация GL: http://www.everfall.com/paste/id.php?05219wngpo11
Реализция Metal: http://www.everfall.com/paste/id.php?jy1ya2kufqs3

Страницы: 1 2 3 4 Следующая »
ПрограммированиеФорумГрафика

Тема в архиве.