Почему функции OpenGL ES нельзя вызывать из другого потока

Я экспериментирую с написанием небольшого игрового движка для Android с использованием OpenGL ES. Я создал игровой поток, который обновляет игровые объекты и GLThread, рисует сцену. Я прочитал, что вам нужно загрузить текстуры в методе onSurfaceCreated GLSurfaceView. Я следую за этим, но для некоторой цели отладки я пытался загрузить текстуры из моей игровой темы вместо GLThread. У меня не было ошибок, и текстура не показывалась на экране. Я потратил весь свой день, пытаясь понять проблему, и, наконец, я прочитал ниже здесь

"Просто обязательно используйте OpenGL в основном потоке". Очень важно. Вы не можете вызвать в своем игровом движке (который может быть в другом потоке) функцию текстурной загрузки, которая не синхронизируется с gl-нитью. Установите флаг, чтобы сигнализировать gl-thread для загрузки новой текстуры (например, вы можете поместить функцию в OnDrawFrame (GL gl), которая проверяет, должна ли быть загружена новая текстура.

Я изменю свой код так, чтобы текстуры загружались из GL Thread. Я просто не мог понять, почему это так? Почему функции OpenGL не работают из другого потока?

Я знаю, как создавать потоки, но я не знаю, что такое синхронизация. В приведенном выше изложении упоминается: "Вы не можете вызывать в своем игровом движке (который может быть в другом потоке) функцию текстурной загрузки, которая не синхронизируется с gl-нитью". Поэтому я думаю, что мой игровой поток не может быть синхронизирован с GL Thread. Возможно ли создать другой поток, который синхронизирован с GL Thread, чтобы функции GL могли быть вызваны из него? Чему я должен научиться в потоковом понимании этих понятий?

Ответ 1

quixoto комментарий ближе всего, я думаю. Традиционная причина, по которой контексты OpenGL являются конкретными потоками практически на каждой платформе, заключается в том, что OpenGL сильно зависит от состояний и не имеет семантики для создания серии изменений атомарных. Так, например, операция рисования на одном потоке может быть:

glVertexPointer(... supply vertex positions ...)
glTexCoordPointer(... provide texture positions ...)
/* and supply a few other pointers, maybe bind a texture */

glDrawArrays(... draw some geometry ...)

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

Android предоставляет EGL, который поддерживает общую концепцию общей группы OpenGL (хотя и неявно, вы предоставляете существующий контекст, в котором вы хотите, чтобы новый контекст находился в общей группе с помощью третьего аргумента eglCreateContext). Если в общей группе присутствуют два контекста, то каждый из них имеет независимое состояние и безопасен для вызова только из одного потока, но для каждого из них доступны именованные объекты, такие как текстуры или объекты буфера вершин. Таким образом, используя общие группы, вы можете одновременно выполнять действия OpenGL для нескольких потоков, чтобы иметь возможность комбинировать результаты в одном потоке.

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