GlClear () слишком долго – Android OpenGL ES 2

Я разрабатываю приложение для Android с использованием OpenGL ES 2. Проблема, с которой я сталкиваюсь, заключается в том, что функция glClear() занимает очень много времени, чтобы обработать, что игра кажется нервной, поскольку кадры задерживаются. Выход прогона программы с помощью датчиков времени показывает, что при настройке всех вершин и изображений из атласа требуется всего менее 1 миллисекунды, glClear() занимает от 10 до 20 миллисекунд. Фактически, клиринг часто занимает до 95% от общего времени рендеринга. Мой код основан на общих учебниках, а функция Render – это:

 private void Render(float[] m, short[] indices) { Log.d("time", "--START RENDER--"); // get handle to vertex shader's vPosition member int mPositionHandle = GLES20.glGetAttribLocation(riGraphicTools.sp_Image, "vPosition"); // Enable generic vertex attribute array GLES20.glEnableVertexAttribArray(mPositionHandle); // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, true, 0, vertexBuffer); // Get handle to texture coordinates location int mTexCoordLoc = GLES20.glGetAttribLocation(riGraphicTools.sp_Image, "a_texCoord" ); // Enable generic vertex attribute array GLES20.glEnableVertexAttribArray ( mTexCoordLoc ); // Prepare the texturecoordinates GLES20.glVertexAttribPointer ( mTexCoordLoc, 2, GLES20.GL_FLOAT, false, 0, uvBuffer); // Get handle to shape's transformation matrix int mtrxhandle = GLES20.glGetUniformLocation(riGraphicTools.sp_Image, "uMVPMatrix"); // Apply the projection and view transformation GLES20.glUniformMatrix4fv(mtrxhandle, 1, false, m, 0); // Get handle to textures locations int mSamplerLoc = GLES20.glGetUniformLocation (riGraphicTools.sp_Image, "s_texture" ); // Set the sampler texture unit to 0, where we have saved the texture. GLES20.glUniform1i ( mSamplerLoc, 0); long clearTime = System.nanoTime(); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); Log.d("time", "Clear time is " + (System.nanoTime() - clearTime)); // Draw the triangles GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); GLES20.glDisableVertexAttribArray(mTexCoordLoc); Log.d("time", "--END RENDER--"); } 

Я попробовал переместить png атлас в /drawable-nodpi но это не повлияло.

Я попытался использовать функции glFlush() и glFinish() . Интересно, что если я не вызываю glClear() то он должен быть вызван автоматически. Это связано с тем, что общее время рендеринга все еще так же высоко, как при его вызове, и на экране нет остатков предыдущего кадра. Только первый вызов glClear() занимает много времени. Если он вызывается снова, последующие вызовы будут только 1 или 2 миллисекунды.

Я также пробовал различные комбинации параметров (например, GLES20.GL_DEPTH_BUFFER_BIT ) и используя glClearColor() . Четкое время все еще велико.

Заранее спасибо.

Вы не измеряете то, что считаете себя. Измерение прошедшего времени вызова API OpenGL в основном бессмысленно.

асинхронность

Ключевым аспектом для понимания является то, что OpenGL – это API для передачи работы на графический процессор. Самая легкая ментальная модель (которая во многом соответствует действительности) заключается в том, что при создании вызовов OpenGL API вы ставите в очередь работу, которая позже будет отправлена ​​на GPU. Например, если вы делаете glDraw*() , glDraw*() вызов, glDraw*() рабочий элемент, который ставится в очередь, и в какой-то момент позже будет отправлен на GPU для выполнения.

Другими словами, API очень асинхронен. Работа, которую вы запрашиваете, вызывая вызовы API, не завершается к моменту возвращения вызова. В большинстве случаев он еще не отправлен на GPU для исполнения. Он только поставлен в очередь и будет представлен в какой-то момент позже, в основном вне вашего контроля.

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

синхронизация

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

Давайте посмотрим на рабочую нагрузку, где общая пропускная способность ограничена графическим процессором (либо с помощью производительности GPU, либо из-за того, что частота кадров ограничена обновлением дисплея). Если бы мы полностью асинхронны для всей системы, и процессор может создавать команды GPU быстрее, чем GPU может их обработать, мы будем постепенно увеличивать объем работы. Это нежелательно по нескольким причинам:

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

Чтобы этого избежать, драйверы используют механизмы дросселирования, чтобы предотвратить слишком большой прогресс CPU. Детали того, как именно это обрабатывается, могут быть довольно сложными. Но, как простая модель, это может быть что-то вроде блокировки процессора, когда он получает более 1-2 кадров раньше, чем закончил рендеринг графического процессора. В идеале вы всегда хотите, чтобы какая-то работа стояла в очереди, чтобы графический процессор никогда не простаивал для приложений с ограниченным графическим интерфейсом, но вы хотите как можно меньше поддерживать объем работы в очереди, чтобы минимизировать использование памяти и латентность.

Значение ваших измерений

Объясняя эту информацию, ваши измерения должны быть намного менее удивительными. Скорее всего, наиболее вероятным сценарием является то, что ваш вызов glClear() вызывает синхронизацию, а время, которое вы измеряете, – это время, необходимое GPU для догоняния, пока не будет смысла отправлять больше работы.

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

  • Предположим, вы сделали вызов glClear() который формирует начало кадра рендеринга n .
  • В это время на дисплее отображается рамка n - 3 , и графический процессор занят обработкой команд рендеринга для кадра n - 2 .
  • Водитель решает, что вы действительно не должны получать больше 2 кадров вперед. Поэтому он блокирует ваш вызов glClear() пока GPU не закончит команды рендеринга для кадра n - 2 .
  • Он также может решить, что ему нужно подождать, пока на дисплее не отобразится кадр n - 2 , что означает ожидание следующей синхронизации луча.
  • Теперь, когда фрейм n - 2 отображается на дисплее, буфер, который ранее содержал фрейм n - 3 больше не используется. Теперь он готов к использованию для кадра n , что означает, что теперь может быть отправлена ​​команда glClear() для кадра n .

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

Вывод

Принимая во внимание, что ваши измерения не являются непосредственно полезными в конце концов, что вы можете извлечь из этого? К сожалению, не так много.

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

  • Измерить / профилировать использование вашего ЦП, чтобы убедиться, что вы действительно ограничены графическим процессором.
  • Используйте инструменты профилирования GPU, которые часто доступны для производителей графических процессоров.
  • Упростите рендеринг или пропустите его части и посмотрите, как меняется производительность. Например, становится ли он быстрее, если вы упростите геометрию? Вы можете быть ограничены обработкой вершин. Бывает ли это быстрее, если вы уменьшите размер фреймбуфера? Или, если вы упростите свои шейдеры фрагментов? Вероятно, вы ограничены обработкой фрагментов.