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

Архитектура OpenGL враппера

Страницы: 1 2 37 8 Следующая »
#0
23:27, 4 авг 2019

Получилось много текста, кому лень читать - вопрос содержится в последней строке сообщения.

Поднадоело при каждом вызове команд OpenGL передавать стопицот аргументов в духе

glCopyImageSubData(srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);

И порешал написать простенький враппер.
Почему бы не взять готовое решение? Потому что я велосипедист, растаман и мизантроп. Не, вру, мизантроп — это другой юзер.

Простенький - значит без замудрений, не нужен мне новый фреймворк, хочу всего лишь сократить число аргументов функций.
Например, чтобы создал один раз текстуру и далее нет необходимости каждый раз указывать её id. И команду выше уже можно сократить до:

texture1.copy_to_sub_image(texture2, srcLevel, dstLevel, src_offset, dst_offset, size);

где src_offset, dst_offset и size представлены как uvec3.

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

С буферами и VAO всё было просто, а вот на текстурах я основательно застрял.

Что имеем:
- 11 таргетов, обозначающих принципиально разные типы текстур, с каждым из которых надо работать по своему и у каждого свои методы записи и чтения данных
- internal format вносит ещё больше разнообразия, потому что данные могут быть не просто float, int или uint, но ещё и нормализованными, сжатыми, srgb, плюс ко всему есть необычные форматы типа GL_R3_G3_B2
- при записи данных надо указывать корректные format и type
- тип записываемых данных должен соответствовать аргументам internal format, format и type

Сразу сходу я решил упростить себе дело, отказавшись от необычных форматов типа GL_R3_G3_B2, потому что использоваться они навряд ли будут, а геммора с их поддержкой больше всего.
Ну и f16 я не стал пока реализовывать потому что в расте нет поддержки 16 bit float.

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

Акт второй
Для одномерной текстуры понадобятся только одномерные версии методов, для массивов надо помнить куда передавать номер элемента в массиве, для массивов кубомап ещё сложнее и тд.
Меняем архитектуру и создаём по одному типу для каждого таргета:

Texture1d
Texture1dArray
Texture2d
Texture2dArray
Texture2dMultisample
Texture2dMultisampleArray
Texture3d
TextureBuffer
TextureCubemap
TextureCubemapArray
TextureRectangle

И для каждого - свои уникальные методы. Для массивов - работа с массивами, для кубомап - работа со сторонами куба и тд.
Стало сильно лучше.

Акт третий
Для упрощения записи данных пришла мысль соединить аргументы internal format, format и type, однозначно обозначив тип данных текстуры.

+ все типы

Например, вот расшифровки трех типов:
1. UBVec4_unorm расшифровывается как GL_RGBA8 + GL_RGBA + GL_UNSIGNED_BYTE;
2. IVec3 - как  GL_RGB32I + GL_RGB_INTEGER + GL_INT;
3. u8_srgba_dxt5 - как GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5 + GL_RGBA + GL_UNSIGNED_BYTE.

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

Акт четвертый
Но если мы стильные модные молодежные, у нас везде в коде используется DSA, то идти по старому пути уже не получится, придется использовать Texture Storage.
А в таком случае текстура создается через glCreateTextures+glTextureStorage2D - тут нужно указывать только internal format, а для записи и чтения format+type каждый раз могут быть разными.

Притом нереально сделать метод upload без спецификации типа текстурынх данных и передавать туда что угодно, проверяя лишь кол-во компонентов(r,rg,rgb или rgba) и тип данных(int, uint, float и тд) потому что как видно из примера выше, можно залить в текстуру массив четырехмерных векторов unsigned byte, которые в одном случае будут нормализовываться и конвертироваться в float(1), а в другом случае это будут сжатые блоки(3).

Тут есть как минимум два варианта:
1. Можно строго типизировать текстуры, один раз в начале указать тип данных и далее везде подставлять одни и те же internal format + format + type. Таким образом в методе upload достаточно двух аргументов - mip-map уровень и собственно сами данные (понятное дело что нужна ещё версия upload_sub_image с указанием offset и size).

Но вообще говоря, текстуры бывают float, srgb float, compressed float, compressed srgb float, signed integer, unsigned integer, depth, stencil и depth-stencil, и можно сначала записать во float текстуру f32 данные а затем дополнить данными normalized u8. Или создать DXT текстуру, записать несжатое изображение и вытащить сжатое.
2. Тогда надо указывать тип данных при каждой операции чтении/записи и проверять его на совместимость с типом, указанным при создании текстуры, т.е. у метода upload будет три аргумента - mip-map уровень, тип данных и сами данные.

В этот момент мои мозги закипели

и я решил набраться опыта, посмотрев решения в других библиотеках. И вот какую дичь увидел:

+ Показать

https://docs.rs/glium/0.26.0-alpha1/glium/texture/index.html
Чувак создал 76(!) типов текстур, по одному на каждую комбинацию типов и таргетов, и строго ограничил работу с ними. Ну это вообще ни в какие ворота не вписывается.

Кароче запарился уже думать как правильно это дело организовать и решил спросить у вас, как вы себе представляете оптимальную обертку над текстурами OpenGL, которая даёт максимум возможностей и с которой будет просто работать?

#1
23:37, 4 авг 2019

Джек Аллигатор
> а для записи и чтения format+type каждый раз могут быть разными.
не могут, это большой оверхэд, ведь драйвер сам будет менять формат

#2
1:41, 5 авг 2019

делай движок под задачу, и по ходу появления новых задач(в новых проектах) доделывай новые фичи
делать все и сразу на все случаи жизни-рецепт неуспеха
извиняюсь за очевидный факт

Джек Аллигатор
> Но вообще говоря, текстуры бывают float
для 99.99999% проектов просто ставят максимально доступный размер текстуры(флоат32, даже для одноцветных черно белых текстур) не парясь с оптимизацией и сколько памяти оно жрет(если проект упрется в это, тогда оптимизируют). Для мобилок допиливают в последний момент, если есть необходимость их поддерживать.

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

Джек Аллигатор
> IVec3
Никогда не используй vec3 в 2019
либо vec2 либо vec4
все сведи к флоатам для простоты

Джек Аллигатор
> mip-map уровень
все очень плохо, оно очень поломано на всех платформах(если надо чтото кроме одной ОС поддерживать)
на один мипмапинг у тебя уйдет год и ты не сможешь это пофиксить(ни один GLES3 движок не смог)

/A\
> не могут, это большой оверхэд,
пока существует Юнити, про оверхед можешь даже не думать

#3
1:55, 5 авг 2019

Danilw
> все очень плохо, оно очень поломано на всех платформах(если надо чтото кроме
> одной ОС поддерживать)
> на один мипмапинг у тебя уйдет год и ты не сможешь это пофиксить(ни один GLES3
> движок не смог)
?
Загрузил текстуру, вызвал glGenerateMipmap.
Либо предварительно готовлю все уровни текстуры и заливаю по одному уровню.
Что тут может работать не так?

> делай движок под задачу, и по ходу появления новых задач(в новых проектах)
> доделывай новые фичи
> делать все и сразу на все случаи жизни-рецепт неуспеха

> кароче берешь любой GLES3 движок опенсурс, копипастишь оттуда основную логику
> загрузчиков АПИ, и будет тебе 10-15 нужных коротких функций вместо тыщи строк
> кода.
Дык там совсем немного кода получается. Тем более я не только враппер пишу, но и параллельно изучаю OpenGL.
Это только на текстурах так встрял, без них планировалось за пару дней весь враппер закончить и вернуться к движкописательству.

#4
5:19, 5 авг 2019

Джек Аллигатор
> glCopyImageSubData(srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
эта функция даже не учитывает texture arrays, то есть для всяких cubemap работать не будет.

я бы сделал как минимум так:

auto srcSubresource = ImageSubresource()
  .setImage(srcImage)
  .setLevel(2)
  //.setLayer(0) //аргументы по умолчанию можно не указывать
  .setOffset(glm::ivec2(16, 16));
auto dstSubresource = ImageSubresource()
  .setImage(dstImage)
  .setLevel(2);
renderGraph->AddTask(RenderGraph::CopyImageSubresourceTask()
  .setSrcSubresource(srcSubresource)
  .setDstSubresource(dstSubresource));

ещё можно сделать немного другой подход — необходимые аргументы указывать прямо в конструкторе, а все остальные — через setBlaBla().

Ну и если ты — один из тех, кому платят обратно пропорционально объёму написанного кода, то код выше можно сократить до:

renderGraph->AddTask(RenderGraph::CopyImageSubresourceTask{{srcImage, 2, 0, glm::ivec2(16, 16)}, {dstImage, 2}});
#5
8:58, 5 авг 2019

Джек Аллигатор
Предлагаю вертеть SRGB дополнительным постэффектом, и отказаться от ненормализованных форматов,
тогда их станет на порядок меньше.

#6
10:04, 5 авг 2019

Джек Аллигатор
> Поднадоело при каждом вызове команд OpenGL
фига ты терпила. только сейчас начал враппер писать.

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

а вообще понадобилась фигня - сразу дописывай кусок враппера. весь враппер тебе вообще не нужен, тк игру пишешь, а не враппер

#7
10:11, 5 авг 2019

Suslik
ты точно не переусложняешь?

или ты так обходишь крестопроблему обязательного указывания не обязательных аргументов в крестах?

#8
10:38, 5 авг 2019

*Lain*
Да ну, по-моему очень приятный код.

#9
10:39, 5 авг 2019

*Lain*
> ты точно не переусложняешь?
а как ещё ты укажешь 2 из 20 необязательных полей в общем случае? ещё одна важная проблема, которую решает такой подход. вот есть тебя код:

glCopyImageSubData(tex, mipLevel, arrayLayer, ...);
glCopyImageSubData(tex, arrayLayer, mipLevel, ...);

можешь определить, в какой из строчек ошибка?
а так?

auto sub = ImageSubresource()
  .setImage(tex)
  .setArrayLayer(mipLevel)
  .setMipLevel(arrayLayer);
auto sub = ImageSubresource()
  .setImage(tex)
  .setArrayLayer(arrayLayer)
  .setMipLevel(mipLevel);

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

#10
11:48, 5 авг 2019

Suslik
> можешь определить, в какой из строчек ошибка?
Это решается именованными типами. Тыж об этом писал.


> а как ещё ты укажешь 2 из 20 необязательных полей в общем случае?

glCopyImageSubData(texture:tex, layer:arrayLayer, mip:mipLevel);
#11
12:03, 5 авг 2019

Suslik
> renderGraph->AddTask(RenderGraph::CopyImageSubresourceTask{{srcImage, 2, 0,
> glm::ivec2(16, 16)}, {dstImage, 2}});
Что за зверь этот RenderGraph? Где можно почитать, что это такое? Нашел только презентацию по Frostbite.

#12
12:13, 5 авг 2019

Vlad2001_MFS
Смею предположить что это некий монитор состояний пйплайна, который собирает команды рендеринга,
а перед презентацией очередного кадра делает всякие оптимизации,
удаляет там дубликаты стейтов и т.д.

#13
12:14, 5 авг 2019

Fedor1995
> Это решается именованными типами. Тыж об этом писал.
если бы C++ поддерживал нормальную реализацию strong typedef'ов, я бы использовал оба способа одновременно. к сожалению, без поддержки со стороны языка не получится обойтись без синтаксического оверхеда местами.

Fedor1995
> glCopyImageSubData(texture:tex, layer:arrayLayer, mip:mipLevel);
только сейчас обнаружил, что в C++ наконец-то добавляют designated initializers! то есть можно будет так:

ImageSubresource srcSubresource = {.tex=srcTex, .level=srcLevel, .layer=srcLayer};

так можно уже сейчас, то только в C, а почему-то не в C++ (я всё ещё не понимаю, как так получилось). если реализовать такую штуку, то можно будет избегать ошибок не только на этапе компиляции, если strong typedef'ы не совпадают, но и уже на этапе написания и без синтаксического оверхеда.

#14
12:17, 5 авг 2019

Vlad2001_MFS
> Что за зверь этот RenderGraph? Где можно почитать, что это такое?
это паттерн написания рендерера, целью которого ставится составление полного графа зависимостей пассов внутри кадра, чтобы пользоваться этой информацией для автоматических оптимизаций и синхронизации. например, в вулкане на основе рендерграфа можно автоматически расставлять барьеры синхронизации и конвертить image layout'ы. имеет мало смысла для предыдущих API вроде opengl/dx11, так как в них часть работы рендерграфа за тебя реализует драйвер. хотя после реализации рендерграфа вообще не понимаю, как без него жил, потому что он позволяет by design кучу ошибок предотвращать и находить, автоматически строит информацию для профайлинга и так далее.

я о своей реализации тут писал: https://gamedev.ru/flame/forum/?id=238789&page=8&m=4957979#m111

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

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