Вобщем ситуация - написал загрузку Quake3 BSP, вывод и обнаружил что пустая сцена с видимостью в 1000 треугольников дает примерно 250 fps. И это не на самом слабом железе. Вывод осуществляется сл образом:
1. Осуществляется проход по дереву и заполняется данные о видимых листья (используется frustrum culling, данные заносятся в стек).
2. Далее перебирается это стек и уже заносятся данные о видимых индексах. Индексы заносятся в std::vector, сразу сортируются по используемому материалу. В результате мы получаем список используемых индексов для каждого материала.
3. Данные об иднексах заносятся в глобальный динамический VBO. Вертексы идут в гобальном статическом и заносятся туда один раз после загрузки.
4. Отрисовка идет сл. образом: бинд шейдера, бинд материала, вывод индексов для этого материала (gldrawelementsbasevertex). Т.е. для каждого материала вывод всех используемых индексов идет за одну команду.
Собственно из чего состоит сцена: 1 источник света (bump + specular mapping), BSP карта, CubeShadowMap (рассчет только на 1ом кадре - потом использование), deffered rendering+antializing.
Судя по дебагу основное время кушается пунктами 1, 2, 3. Занимает порядка 2мс. Вывод геометрии тоже занимает порядка 2мс (идет параллельно с обсчетом сцены) + переключение буфферов + фишки deffered rendering'а. gDebugger показывает около 200 вызвов комманд за кадр (но это я как понимаю, включая установку стейтов, бид fbo, vao и тд. + передача данных в шейдер).
Собственно вопрос, где может так жудко тормозить? Ведь сцены выводятся с десятками тысяч полигонов в современных играх и выдает приемлимый фпс. Данный вывод BSP дерева испольуется во всех примерах, что я смотрел. Полагаю он не самый оптимальный, и есть гораздо более быстрые пути.
Все-таки зависимость между FPS и нагрузкой - далеко нелинейная, думаю 250 FPS вполне себе результат.
Maris
>1. Осуществляется проход по дереву и заполняется данные о видимых листья (используется frustrum culling, данные заносятся в стек).
std::stack ?? Зачем??? что это тебе дает? какое преимущество? Если да, то лишние аллокации, кешь промахи на CPU.
>2. Далее перебирается это стек и уже заносятся данные о видимых индексах. Индексы заносятся в std::vector, сразу сортируются по используемому материалу. В результате мы получаем список используемых индексов для каждого материала.
Итерация по нодовым контейнерам крайне медленная, ты используешь лишние промежуточные контейнеры. std::vector переаллоцируется каждый раз?. Сортировать не нужно, лучше сразу в листьях хранить готовые батчи геометрии с нужным материалом и готовыми VBO.
>3. Данные об иднексах заносятся в глобальный динамический VBO. Вертексы идут в гобальном статическом и заносятся туда один раз после загрузки.
И снова не нужно гонять заполнения индексов. В листьях хранишь батчи, в батчах смещение в вершинном буфере, готовый индексный буфер. Или смещение в глобального индексном буфере, ну и материал. У тебя CPU bound.
Помоему весь фпс сожрала теневая кубемапа, шесть проходов всё-таки, особенно если разрешение каждой стороны эдак 4096х4096.
Ну и сортировка в STL тоже потребляет время. Сортировку для таких вещей надо писать свою, максимально быструю.
Upd: увидел что используется дефферед. Ну вот он всё и сожрал. На форварде было бы под полторы-две тыщи.
std::stack ?? Зачем??? что это тебе дает? какое преимущество? Если да, то лишние аллокации, кешь промахи на CPU
Нет контейнер написан свой. Лишнией аллокаций нет, т.к. написан свой менеджер памяти, который выделяет одни раз через new() большой кусок памяти. Свой стек берет память именно оттуда. Информация о видимых листьях нужна в будет в будущем так же для обрезки источников света и объектов. Поэтому ее все-равно куда-то надо сохранять.
Итерация по нодовым контейнерам крайне медленная, ты используешь лишние промежуточные контейнеры. std::vector переаллоцируется каждый раз?. Сортировать не нужно, лучше сразу в листьях хранить готовые батчи геометрии с нужным материалом и готовыми VBO.
std::vector инициализируется сразу с аллоцированными 100 элементами. clear(), конечно делается, но capacity не уменьшается - проверял. Вектор статически. Т.е. все равно даже если кол-во индексов больше чем элементов в векторе после нескольких секунд должно все работать.
Тоже думал в глобальный индексный буфер засунуть, а в листьях смещения хранить правда есть небольшое но. Один элемент (face) может принадлежать нескольким листьям. Сейчас это отсекается через битовые операции. Если же писать все индексы из листьев и их выводить, некоторая геометрия будет выводится по нескольку раз. Примерно, раза 1.5 на текущей сцене больше треугольников выводится. Конечно, зависит от самой сцены, но все-таки...
Помоему весь фпс сожрала теневая кубемапа, шесть проходов всё-таки, особенно если разрешение каждой стороны эдак 4096х4096.
На данный момент кубемапа статическая - проходы только 1ый кадр идут, дальше уже только используются текстуры. Разрешения в 2 раза меньше.
Upd: увидел что используется дефферед. Ну вот он всё и сожрал. На форварде было бы под полторы-две тыщи.
Разница между форваред и дефферед примерно 20фпс... Пока источник один, но планируется больше. С форваред думается будут проблемы..
Да тут что угодно тормозить может. Фантазировать можно много.
Вот сразу вопрос, например: индексный буфер один или цепочка? Локается как, с флагом D3DLOCK_DISCARD или D3DLOCK_NOOVERWRITE? Если "один, дискард", то скорее всего идут постоянные аллокации в оперативке из-за того, что буфер вечно занят GPU и выделяется новый кусок памяти. И если локается большой кусок буфера, то это может быть медленно.
Maris
> Если же писать все индексы из листьев и их выводить, некоторая геометрия будет
> выводится по нескольку раз.
Но она же вся целиком будет на GPU, а от овердрав может спасти z-prepass. Надо тестировать.
Упс, вопрос был по опен-жопе.. Тогда вопрос про флаги снимается (исхожу из логичной вещи, что у glMapBuffer не был поставлен GL_READ_WRITE). Там вроде как нельзя запереть буфер с NOOVERWRITE. Вопрос про кол-во буферов в силе.
Упс, вопрос был по опен-жопе.. Тогда вопрос про флаги снимается (исхожу из логичной вещи, что у glMapBuffer не был поставлен GL_READ_WRITE). Там вроде как нельзя запереть буфер с NOOVERWRITE. Вопрос про кол-во буферов в силе.
В системе в общем-то 2 глобальных блока буферов. Каждый блок включает в себя индексный, вертексный и данные для шейдеров (ubo). Используется 2 потока - 1ый рисует данные, например, с 1ого блока, 2ой блок в это время замаплен в память glMapBuffer с флагом DYNAMIC_DRAW. Туда происходит вся запись во 2м потоке. В конце кадра блоки меняются местами - 1ый мапится в память, 2ой становится рабочим.
Но она же вся целиком будет на GPU, а от овердрав может спасти z-prepass. Надо тестировать.
Да, тестировать надо. Возможно быстрее это все будет вывести с GPU, чем оптимизировать.
Разница между форваред и дефферед примерно 20фпс...
Вот это вот оччень подозрительно. Дефферед всегда медленее форварда при небольшом кол-ве источников. Преимущество начинает проявляться где-то после 10-12.
А при форварде еще какие-то пост-процессы активны?
Вот это вот оччень подозрительно. Дефферед всегда медленее форварда при небольшом кол-ве источников. Преимущество начинает проявляться где-то после 10-12.
А при форварде еще какие-то пост-процессы активны?
Да никаких пост-процессов нету в форварде. По тестам получалось примерно следующее - 20фпс сам дефферед (т.е. рендер в текстуру и ее отрисовка) + 50-60фпс антиалайзинг проход для дефферд (правда в форварде антиалайзинга нет).
Еще раз посидел, поковырял - по тайменгам получается следующее:
1. Обход геометрии дерева и определение видимых листьев ~500 мкс
2. Сборка индеков - 1500 мкс.
3. Копирование индексов в буфер 30 мкс :)
Вобщем, явно тормозит сборка, хотя и происходит в заведомо аллоцированные вектора..
Собственно сам код. Тормозит между for ( core::s32 AreaID: *stack ) {...}
CEntity*CQuake3BSP::buildDrawSurfaces(const area_stack_t *stack, CEntity *entity ) { int dataIndex = currentRenderData++; MapRenderData_t *renderData = &frameRenderData[( dataIndex - 1 ) % MAX_RENDER_DATA]; clearRenderData( renderData ); if ( stack->size( ) == 0 ) { return entity; } for ( core::s32 AreaID: *stack ) { auto &pLeaf = m_Leafs[AreaID]; // If we get here, the leaf we are testing must be visible in our camera's // Get the number of faces that this leaf is in charge of. const int faceCount = pLeaf.numOfLeafFaces; // Loop through and render all of the faces in this leaf for ( auto j = 0; j < faceCount; j++ ) { // Grab the current face index from our leaf faces array const int faceIndex = m_LeafFaces[pLeaf.leafface + j]; // Before drawing this face, make sure it's a normal polygon if ( m_Faces[faceIndex].type != FACE_POLYGON ) continue; // Since many faces are duplicated in other leafs, we need to // make sure this face already hasn't been drawn. if ( !renderData->m_FacesDrawn.On( faceIndex ) ) { // Increase the rendered face count to display for fun renderData->numFacesDrawn++; // Set this face as drawn and render it renderData->m_FacesDrawn.Set( faceIndex ); renderFace( faceIndex, renderData->indicesToDrawn[m_Faces[faceIndex].textureID] ); //renderFace( faceIndex, entity, renderData ); } } } renderIndices( entity, renderData ); } void CQuake3BSP::renderFace( const core::s32 &faceIndex, std::vector<core::u32> &indexToDraw ) const { const q3Face_t *face = &m_Faces[faceIndex]; for ( auto i = 0; i < face->numOfIndices; i++ ) { indexToDraw.push_back( m_Indices[face->startIndex + i] + face->startVertIndex ); } }
Maris
Компилил в релизе, конечно же?
Попробовал писать всю геометрию из листьев сразу в буффера, а в листьях хранить только ссылки в глобальный буффер. Сборка индексов ускорилась в разы, т.к. надо только скопировать данные о смещениях. Но вот сам рендер просел... В результате фпс просел до 150-180. Дебаг показал, что во многих листьях храниться очень мало геометрии: 2-3 треугольника и все. А на их вывод тратиться отдельный glDrawElements() ... Т.е. для вывода уровня вызывается порядка сотни glDrawElements(). Не оптимально точно.
Компилил в релизе, конечно же?
250фпс в дебаге. В релизе, наверное, больше будет. Не думаю, что на много правда.
Maris
> пустая сцена с видимостью в 1000 треугольников дает примерно 250 fps
> deffered rendering+antializing.
Пункт 2 всё и съедает. В принципе, тут 250fps это нормально.
Тема в архиве.