Самый эффективный способ создания больших текстур во время выполнения в OpenGL ES для Android

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

Поскольку Unity для Android использует OpenGL ES, а мое приложение является графическим, которое должно работать с идеальной скоростью 60 кадров в секунду, я создал плагин C++, работающий с кодом OpenGL, вместо того, чтобы просто использовать медленную конструкцию текстуры Unity Texture2D. Плагин позволяет мне загружать данные пикселей в новую текстуру OpenGL, а затем сообщать об этом Unity через функцию CreateExternalTexture() их Texture2D.

Поскольку версия OpenGL ES, работающая в этой настройке, к сожалению, однопоточная, для поддержания работы в кадре я выполняю вызов glTexImage2D() с уже существующим TextureID, но с нулевыми данными в первом кадре. А затем вызовите glTexSubImage2D() с разделом моего буфера данных пикселей для нескольких последующих кадров, чтобы заполнить всю текстуру, по сути, выполняя создание текстуры синхронно, но разбивая операцию на несколько кадров!

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

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

Могу ли я в любом случае избежать такого выделения памяти, возможно, выделив огромный блок памяти в начале и используя его в качестве пула для новых текстур? Я читал вокруг, и люди, кажется, предлагают вместо этого использовать FBO? Возможно, я неправильно понял, но мне показалось, что вам все еще нужно сделать вызов glTexImage2D(), чтобы выделить текстуру, прежде чем прикреплять ее к FBO?

Любой совет приветствуется, заранее спасибо! знак равно

PS: я не из графического фона, поэтому я не знаю лучших практик с OpenGL или другими графическими библиотеками, я просто пытаюсь создавать новые текстуры во время выполнения без выделения!

1 ответ

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

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

// Make the texture blurry.
blur(texture);

Но вместо этого мне пришлось создать 11 различных текстур с разным разрешением и переключаться между ними в качестве входов и выходов для горизонтальных / вертикальных шейдеров размытия с FBO, чтобы получить прилично выглядящее размытие. Мне никогда не нравилось программирование на GPU, потому что там было часто встречается самое сложное управление состоянием, которое я когда-либо встречал. Это было невероятно неправильно, что мне нужно было пойти на чертежную доску, чтобы просто выяснить, как минимизировать количество текстур, выделенных из-за этого фундаментального требования, что входы текстуры для шейдеров также не могут использоваться в качестве выходов текстуры.

Итак, я создал пул текстур и OMG, он сильно упростил вещи! Это сделано для того, чтобы я мог просто создавать временные объекты текстуры влево и вправо и не беспокоиться об этом, потому что уничтожение объекта текстуры на самом деле не вызывает glDeleteTextures, он просто возвращает их в пул. Таким образом, я смог наконец-то просто сделать:

blur(texture);

... как я хотел все время. И по какой-то забавной причине, когда я начал использовать пул все больше и больше, он увеличил частоту кадров. Я предполагаю, что даже несмотря на все мысли, которые я вложил в минимизацию количества выделяемых текстур, я по-прежнему выделял больше, чем нужно, способами устранения пула (обратите внимание, что реальный пример из реального мира делает намного больше, чем размытие, включая DOF, bloom, hipass, lowpass, CMAA и т. д., а код GLSL фактически создается на лету на основе языка визуального программирования, который пользователи могут использовать для создания новых шейдеров на лету).

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

struct GlTextureDesc
{
    ...
};

... и это довольно здоровенная структура, учитывая, сколько параметров текстуры мы можем указать (формат пикселя, количество цветовых компонентов, уровень LOD, ширина, высота и т. д. и т. д.).

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

Это позволяет нам сделать это:

// Provides a pool of textures. This allows us to conveniently rapidly create,
// and destroy texture objects without allocating and freeing an excessive number 
// of textures.
class GlTexturePool
{
public:
    // Creates an empty pool.
    GlTexturePool();

    // Cleans up any textures which haven't been accessed in a while.
    void cleanup();

    // Allocates a texture with the specified properties, retrieving an existing 
    // one from the pool if available. The function returns a handle to the
    // allocated texture.
    GLuint allocate(const GlTextureDesc& desc);

    // Returns the texture with the specified key and handle to the pool.
    void free(const GlTextureDesc& desc, GLuint texture);

private:
    ...
};

В этот момент мы можем создавать временные объекты текстуры слева и справа, не беспокоясь о чрезмерных вызовах glTexImage2D а также glDeleteTexturesЯ нашел это чрезвычайно полезным.

Наконец следует отметить, что cleanup функция выше. Когда я сохраняю текстуры в хеш-таблице, я ставлю на них метку времени (используя системное реальное время). Периодически я вызываю эту функцию очистки, которая затем просматривает текстуры в хэш-таблице и проверяет отметку времени. Если какой-то промежуток времени прошел, пока они просто сидят, простаивая в бассейне (скажем, 8 секунд), я звоню glDeleteTextures и удалите их из бассейна. Я использую отдельный поток вместе с условной переменной для создания списка текстур для удаления в следующий раз, когда действительный контекст становится доступным путем периодического сканирования хеш-таблицы, но если ваше приложение все однопоточное, вы можете просто вызвать эту очистку функционировать каждые несколько секунд в вашем основном цикле.

Тем не менее, я работаю в VFX, который не имеет таких жестких требований в реальном времени, как, скажем, игры AAA. В моей области больше внимания уделяется офлайн-рендерингу, и я далеко не мастер GPU. Там могут быть лучшие способы решения этой проблемы. Тем не менее, я считаю чрезвычайно полезным начать с этого пула текстур, и я думаю, что это может быть полезно и в вашем случае. И это довольно тривиально для реализации (просто заняло у меня полчаса или около того).

Это может привести к выделению и удалению множества текстур, если размеры, форматы и параметры текстур, которые вы запрашиваете для выделения / освобождения, повсюду. Там это может помочь немного объединить вещи, например, по крайней мере, использовать размеры POT (степень двух) и т. Д. И принять решение о минимальном количестве используемых форматов пикселей. В моем случае это не было большой проблемой, так как я использую только один пиксельный формат, и большинство временных текстур, которые я хотел создать, в точности соответствуют размеру области просмотра, увеличенной до потолочного POT.

Что касается FBO, я не уверен, как они помогут вашей непосредственной проблеме с чрезмерным распределением / освобождением текстуры. Я использую их в основном для отложенного затенения, чтобы выполнить постобработку для таких эффектов, как DOF, после рендеринга геометрии за несколько проходов в стиле композитинга, примененном к полученным 2D текстурам. Я использую FBO для этого, естественно, но я не могу думать о том, как FBO немедленно уменьшают количество текстур, которые вы должны выделить / освободить, если вы не можете просто использовать одну большую текстуру с FBO и визуализировать несколько входных текстур для нее для вывода за пределы экрана. текстура. В этом случае это не было бы то, что FBO помогло бы напрямую, а просто могло бы создать одну огромную текстуру, чьи секции вы можете использовать в качестве ввода / вывода вместо множества меньших.

Другие вопросы по тегам