среда, 4 августа 2010 г.

Slow OpenGL vertex pipe

На работе в проекте столкнулись со следующей проблемой: OpenGL медленно работает при большом количестве DIP-ов.

Дипов много: на тестовом уровне - около 4000. На каждый вызов приходится смена вершинного и индексного буферов, установка шейдерных юниформов, текстур и иногда кое-каких стейтов. Direct3D версия каким-то образом умудряется всё это расхлёбывать - видно что CPU bound, но performance в пределах допустимого. Наш же OpenGL порт в текущем виде догнать Direct3D версию, похоже, не в состоянии. С вопросами "почему так много" - не ко мне, мы всего лишь делаем порт существующего Direct3D рендера, который работает (у)довлетворительно.

Разобрал енджин так, чтобы рисовались только анимированые персонажи в wireframe - получилось в районе 400 дипов при 30-40 персонажах. Direct3D рендер переваривает это за 14-15 ms, OpenGL - вдвое медленнее. Применение различных constraints на кол-во установок стейтов показали, что тормозит связка glVertexAttribPointer()/glDrawElements(). Снизить кол-во VAP-ов (3000) невозможно, т. к. нет другого способа указывать драйверу, откуда брать вершинные данные. Существует расширение GL_ARB_vertex_array_object, но пока на моей конфигурации (WinXP + GeForceПЕЧ 260) оно не даёт ощутимого прироста.

Хуже всего, что в сети почти нет никакой информации, как можно соптимизировать такую ситуацию хотя бы под одного вендора (NVIDIA). Гугл выдаёт ссылки на треды и пейпры, в которых настоятельно рекомендуется "подключить VBO". Что делать, если уже использован оптимальный codepath, неясно.

Думаю отложить на время все свои изыски "в прекрасном и высоком" и заняться написанием демки, в которой будет рисоваться множество однотипной геометрии (сферы или ещё что-то), и посмотреть, как ведёт себя драйвер и сколько fps можно из него выжать при 1k дипов и разных форматах вершины.

Мне просто стало интересно.

Update:
Сидел на выходных, писал тест-апп. Рисуется моделька Макрона (финальный босс в Quake 2) 1000 раз, по дипу на модельку (я привёл только меш крупным планом, в демке они все крутятся на своих позициях в виде решётки). В меше (дипе) - 1200 треугольников. В вершине - позиция и нормаль, также делал выравнивание вершины до 32 байт. 

Удалось уложиться в 38 ms (~26 fps) на моём ноуте с HD 4330 и проце Celeron M. IMHO очень неплохо - 1 200 000 треугольников на мобильной конфигурации. Завтра побегу тестировать на рабочей лошадке. 

Update:

156 fps при 1000 дипов (Core 2 Quad + GTX 260). Не всё так плохо.

Выяснилась интересная подробность работы железа: если шейдер не использует какие-то вершинные атрибуты из буфера вершин, то они не читаются из видеопамяти. Т. е. скажем, формат вершины может быть хоть 100 байт, но если вершинный шейдер использует только позицию, то выбираться из памяти будут только соответствующие данные, а не вся вершина. При задействовании же всех атрибутов из 40 кб вершины в указанном выше тесте скорость падает до 50 fps.

Update:

Перенёс программу на Mac (это было просто, т. к. я использовал glut). При выборке в вершинном шейдере всех атрибутов на GeForce наблюдается падение скорости, аналогично как это происходит в Windows. На iMac с HD 4850 падения скорости - нет, fps держится в пределах 120 кадров, т. е. приблизительно аналогично GTX 260 в лёгком режиме. И ещё одно - на Mac+ATI gl_Position не мэпится на нулевой generic attribute, как и положено по стандарту.

4 комментария:

  1. Жесть. 4(8)к вбо в кадре.
    Оно конечно нелепое предложение, но на баде спасает сортировка по вбо и жесткий контроль над форматом вершин. Так удается протащить сотню дипов на адекватной скорости (без этого - около 75, садимся на драйвер после 30-го)

    ОтветитьУдалить
  2. > Direct3D рендер переваривает это за 14-15 ms, OpenGL - вдвое медленнее.

    Хотелось бы дебаггером посмотреть на этот код.

    ОтветитьУдалить
  3. > Direct3D рендер переваривает это за 14-15 ms, OpenGL - вдвое медленнее.
    может дело в драйверах? - сравнить, например, DX NV, DX AMD, GL NV, GL AMD

    ОтветитьУдалить