Как стать автором
Обновить

Комментарии 27

А как предполагается проверять, что текстуру уже можно использовать, если никакой связи с загружающим потоком нет? И я не уверен, что такое использование не противоречит стандарту - OpenGL очень не любит(по факту - не допускает) модификацию стейта из более чем одного потока

Я не проверяю, сразу использую. Пока текстура не загружена отображается чёрный прямоугольник. Этот рецепт работает на 10+ разных компьютерах.
Если очень хочется добавить в свой цикл рендера еще один if, можете проверять таблицу loaded_state.

Статью дополнил, спасибо.

Что-то очередь какая-то грустная с глобальными локами, почему не реализовали Lock free queue?

Хотелось бы мне ответить: для простоты понимания рецепта! Но на самом деле я просто дилетант в мультипоточности. Да и просадки в производительности я не чувствую, там в очередь добавляются байтики из основного треда практически мгновенно и почти никогда не файтятся со вторым потоком.

Принудительный ресайз до степени двойки? Прямо-таки прошлым веком повеяло, седой стариной... Так-то с версии 2.0 можно уже не страдать: https://www.khronos.org/opengl/wiki/NPOT_Texture

Теоретически да. Практически вылетают краши из апи OpenGL, которые невозможно понять, тем более пофиксить. Проще и надежнее опереться на "старый век". Но при желании вы просто можете проигнорировать блок этого кода.

И прощай мипмапы

С какой это радости прощай? С мипмапами всё по-прежнему хорошо. Им тоже можно иметь размеры NPOT. Сколько текстур прогрузил, ни разу не видал ни крашей, ни проблем с мипмапами из-за размеров NPOT.

Какой прирост скорости в сравнении многопотока и одного потока?

Не знаю. Тут решается не проблема скорости, а чтобы рендер игрового цикла не фризился когда понадобилась новая текстура.

Да там вообще не нужно ни то ни другое. Всё равно синхронизации с основным потоком нет - какая разница когда команды будут выполнены

Если из этого рецепта убрать glFinish, то последняя заказанная текстура с некоторой вероятностью не будет прогружаться или будет прогружаться визуально некорректно.

Может быть glFlush лучше чем glFinish. Если кто-нибудь попробует затестит это, пусть отпишется тут о результатах =)

Вот не факт теперь после вашего предыдущего комментария. Я не проверял, но судя по описанию, как раз из-за того что glFinish блокирующая - оно и успевает успевать прогружатся

Подгрузку и выгрузку текстур в многопоточном режиме было бы очень легко реализовать на Go с каналами и горутинами

Ожидал описания, что в потоке на котором gl context происходит загрузка, в рабочих потоках подготовка или как оно тут сделано.

Вообще правильно:

  • подготовку делать многопоточно

  • В потоке рендера делать только glTexImage. Это минимальная нагрузка на CPU и никакой на GPU.

  • Никакие glFinish, который замораживает поток до момента, пока GPU всё не нарисует, не нужны. glFinish проблема не в заморозке CPU потока, а в том, что после его разморозки GPU queue пустая, и пока начнёт заполняться, GPU простаивает. Убиваете производительность.

  • glFlush скидывает очередь команд с CPU на GPU. Очень драйверозависимая штука, может как тормознуть (adreno), так и чуть ускорить (mali).

  • А вообще, лучше сделать дочерний glContext. В родительском грузить текстуры, в дочернем рендерить (или наоборот, не помню). GLuint между родственниками шарится.

Я делал в потоке рендера glTexImage2D...
если он загружает картинку из памяти видеокарты, не сжатую и после него нет генерации мипмапов, то да, скорость мгновенная... но стоит добавить ему флаг сжимать текстуру или генерировать мипмапу после него -- начинаются фризы в рендер цикле, играть становится не возможно. Я пытался сам генерировать мипмапы, но упёрся в странный баг кривой загрузки мипмапы размера 2*2, убил пару дней на него и сдался, пошел другим путём. Самостоятельно сжимать картинку на цпу я даже не стал пытаться.

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

Ну так компрессию всегда делают в оффлайне и заливают в раньайме уже пожатое :) пожать в какой-нибудь dxt1 очень ресурсоёмкая задача.

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

Сжатие на видеокарте шакалит текстуру очень заметно, но и память экономит значительно, поэтому мне не вариант хранить только сжатое. Я даю пользователю выбор какие текстуры грузить — сжатые или не сжатые. Соответственно на винче хранятся PNG'шки без потери качества.

А пользователь на стадии препроцесса не может это решить? Для игр всё препроцессится.

А вообще, лучше сделать дочерний glContext. В родительском грузить
текстуры, в дочернем рендерить (или наоборот, не помню). GLuint между
родственниками шарится.

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

  • Texture::UploadData копируется/передаётся по значению, при этом поле data не дублируется - возможны проблемы с учётом ресурсов. И при освобождении структуры поле data не чистится. Т.к. блок данных может быть большой (и копировать его затратно) можно запретить копирование структуры. Например, в очереди хранить shared_ptr на структуру. Или разбираться с мувиками;

  • Метод dequeue может вызвать блокировку, если встать в ожидание и данных больше не будет.

Вообще: с чем связано такое желание работать в бесконечном цикле, без выхода, без подчистки ресурсов? Обычно игры (даже с бесконечным миром) рано или поздно завершаются.

Фишку с UploadData можно улучшить (особенно если вы пишете академический диплом), а можно оставить и так, память не течёт.

Блокировка второго потока, ответственного за загрузку текстур, в ситуации когда загружать нечего... желательна.

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

Я точно не знаю как это работает

Ну и плохо. После завершения main() ваша "безопасная очередь" будет уничтожена вместе со всеми своими примитивами синхронизации, а поток, который на них ждет, еще некоторое время завершен не будет. Уничтожение того же заблокированного мутекса - это undefined behavior, любое обращение из потока к уже уничтоженному объекту очереди - тоже. Просто у вас появится гейзенбаг - игра иногда на некоторых системах может начать падать при выходе. Потоки должны быть корректно завершены до завершения main().

но оно работает!

Это пока.

Всё верно. Для решения этого потенциального краша можно добавить какой-нибудь бинарный флаг типа exit в структуру UploadData и обработать его соответственно.

Ваш способ просто неверный, OpenGL не разрешает доступ к одному контексту из разных потоков

То, что это работает на вашей машине, не гарантирует, что оно не развалится у соседа

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории