Поделиться через


Оптимизация производительности (Direct3D 9)

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

Общие советы по производительности

  • Очистить только в том случае, если необходимо.
  • Свести к минимуму изменения состояния и сгруппировать оставшиеся изменения состояния.
  • Используйте небольшие текстуры, если это можно сделать.
  • Рисуйте объекты в сцене с передней стороны на спину.
  • Используйте полоски треугольников вместо списков и вентиляторов. Для оптимальной производительности кэша вершин упорядочить полосы для повторного использования вершин треугольника раньше, а не позже.
  • Грациозно ухудшают специальные эффекты, требующие непропорциональной доли системных ресурсов.
  • Постоянно тестируйте производительность приложения.
  • Свести к минимуму параметры буфера вершин.
  • По возможности используйте статические буферы вершин.
  • Используйте один большой буфер статической вершины на FVF для статических объектов, а не один для каждого объекта.
  • Если приложению требуется случайный доступ к буферу вершин в памяти AGP, выберите размер формата вершины, равный 32 байтам. В противном случае выберите самый маленький подходящий формат.
  • Рисование с помощью индексированных примитивов. Это может позволить более эффективное кэширование вершин в оборудовании.
  • Если формат буфера глубины содержит канал наборов элементов, всегда очищайте канал глубины и наборы элементов одновременно.
  • Объедините инструкцию шейдера и выходные данные по возможности. Например:
    // Rather than doing a multiply and add, and then output the data with 
    //   two instructions:
    mad r2, r1, v0, c0
    mov oD0, r2
    
    // Combine both in a single instruction, because this eliminates an  
    //   additional register copy.
    mad oD0, r1, v0, c0 
    

Базы данных и Кулинг

Создание надежной базы данных объектов в вашем мире является ключом к отличной производительности в Direct3D. Это более важно, чем улучшения для растеризации или оборудования.

Вы должны поддерживать наименьшее количество многоугольников, которые можно управлять. Проектирование для низкого количества многоугольников путем создания моделей с низким многоугольником с самого начала. Добавьте многоугольники, если это можно сделать без ущерба для производительности позже в процессе разработки. Помните, что самые быстрые многоугольники — это те, которые вы не рисуете.

Примитивы пакетной обработки

Чтобы получить лучшую производительность отрисовки во время выполнения, попробуйте работать с примитивами в пакетах и максимально низкое количество изменений состояния отрисовки. Например, если у вас есть объект с двумя текстурами, сгруппировать треугольники, использующие первую текстуру, и следовать за ними с необходимым состоянием отрисовки для изменения текстуры. Затем сгруппировать все треугольники, использующие вторую текстуру. Простейшая поддержка оборудования Direct3D вызывается с пакетами состояний отрисовки и пакетов примитивов через аппаратный уровень абстракции (HAL). Чем эффективнее пакетные инструкции, тем меньше вызовов HAL выполняются во время выполнения.

Советы по освещению

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

  • Используйте как можно меньше источников света. Чтобы увеличить общий уровень освещения, например, используйте внешний свет вместо добавления нового источника света.
  • Направление света эффективнее, чем указатели или прожекторы. Для направленного освещения направление света фиксировано и не требуется вычисляться на основе вершины.
  • Прожекторы могут быть более эффективными, чем точка света, так как область за пределами конуса света вычисляется быстро. Будь то прожекторы более эффективными или не зависят от того, сколько вашей сцены освещено в центре внимания.
  • Используйте параметр диапазона, чтобы ограничить свет только частями сцены, которые необходимо осветить. Все типы света выходят довольно рано, когда они находятся вне диапазона.
  • Спекулярные выделения почти удвоит стоимость света. Используйте их только в том случае, если необходимо. При возможности задайте для D3DRS_SPECULARENABLE состояние отрисовки значение 0, значение по умолчанию. При определении материалов необходимо задать значение зрителя мощности равным нулю, чтобы отключить зриемые выделения для этого материала; Просто задать зрителя цвет 0,0,0 недостаточно.

Размер текстуры

Производительность сопоставления текстур сильно зависит от скорости памяти. Существует несколько способов максимально повысить производительность кэша текстур приложения.

  • Держите текстуры небольшими. Чем меньше текстуры, тем лучше они поддерживаются в дополнительном кэше ЦП.
  • Не изменяйте текстуры на основе примитивов. Попробуйте сохранить многоугольники сгруппированы по порядку используемых текстур.
  • По возможности используйте квадратные текстуры. Текстуры, размеры которых составляют 256x256, являются самыми быстрыми. Если приложение использует четыре текстуры 128x128, например, попробуйте использовать одну палитру и поместить их в одну текстуру 256x256. Этот метод также уменьшает объем переключения текстур. Конечно, не следует использовать текстуры 256x256, если приложение не требует много текстур, так как, как упоминалось, текстуры должны храниться как можно меньше.

Преобразования матрицы

Direct3D использует матрицы мира и представления, настроенные для настройки нескольких внутренних структур данных. Каждый раз, когда вы устанавливаете новую матрицу мира или представления, система пересчитывает связанные внутренние структуры. Часто устанавливайте эти матрицы ( например, тысячи раз в кадре) — это вычислительное время. Можно свести к минимуму количество необходимых вычислений, объединив мир и матрицы представления в матрицу мирового представления, которую вы задали в качестве матрицы мира, а затем установив матрицу представления для удостоверения. Сохраняйте кэшированные копии отдельных миров и матриц представления, чтобы можно было изменять, объединять и сбрасывать матрицу мира по мере необходимости. Для ясности в этой документации примеры Direct3D редко используют эту оптимизацию.

Использование динамических текстур

Чтобы узнать, поддерживает ли драйвер динамические текстуры, проверьте флаг D3DCAPS2_DYNAMICTEXTURES структуры D3DCAPS9.

Помните следующее при работе с динамическими текстурами.

  • Их нельзя управлять. Например, пул не может быть D3DPOOL_MANAGED.
  • Динамические текстуры можно заблокировать, даже если они создаются в D3DPOOL_DEFAULT.
  • D3DLOCK_DISCARD является допустимым флагом блокировки для динамических текстур.

Рекомендуется создать только одну динамическую текстуру для каждого формата и, возможно, на размер. Динамические MIP-карты, кубы и тома не рекомендуется из-за дополнительных накладных расходов при блокировке каждого уровня. Для MIP-карт D3DLOCK_DISCARD допускается только на верхнем уровне. Все уровни удаляются путем блокировки только верхнего уровня. Это поведение одинаково для томов и кубов. Для кубов верхний уровень и лицо 0 заблокированы.

В следующем псевдокоде показан пример использования динамической текстуры.

DrawProceduralTexture(pTex)
{
    // pTex should not be very small because overhead of 
    //   calling driver every D3DLOCK_DISCARD will not 
    //   justify the performance gain. Experimentation is encouraged.
    pTex->Lock(D3DLOCK_DISCARD);
    <Overwrite *entire* texture>
    pTex->Unlock();
    pDev->SetTexture();
    pDev->DrawPrimitive();
}

Использование динамических буферов вершин и индексов

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

В идеале данные вершин или индекса никогда не изменятся, однако это не всегда возможно. Существует множество ситуаций, когда приложению необходимо изменить вершины или индексировать данные каждого кадра, возможно, даже несколько раз на кадр. В этих ситуациях необходимо создать буфер вершин или индекса с D3DUSAGE_DYNAMIC. Этот флаг использования приводит к оптимизации Direct3D для частых операций блокировки. D3DUSAGE_DYNAMIC полезно только при частом блокировке буфера; данные, которые остаются константами, должны быть помещены в статический буфер вершин или буфер индекса.

Чтобы повысить производительность при использовании динамических буферов вершин, приложение должно вызывать IDirect3DVertexBuffer9::Lock или IDirect3DIndexBuffer9::Lock с соответствующими флагами. D3DLOCK_DISCARD указывает, что приложению не нужно хранить старые данные вершин или индексов в буфере. Если графический процессор по-прежнему использует буфер при вызове блокировки с D3DLOCK_DISCARD, то указатель на новый регион памяти возвращается вместо старых данных буфера. Это позволяет графическому процессору продолжать использовать старые данные, пока приложение помещает данные в новый буфер. В приложении не требуется дополнительное управление памятью; Старый буфер повторно используется или уничтожается автоматически, когда графический процессор завершает работу с ним. Обратите внимание, что блокировка буфера с помощью D3DLOCK_DISCARD всегда удаляет весь буфер, указывая ненулевое смещение или поле ограниченного размера, не сохраняет информацию в разблокированных областях буфера.

Существуют случаи, когда объем данных, необходимых приложению для хранения на блокировку, небольшой, например добавление четырех вершин для отрисовки спрайта. D3DLOCK_NOOVERWRITE указывает, что приложение не перезаписывает данные, уже используемые в динамическом буфере. Вызов блокировки возвращает указатель на старые данные, позволяя приложению добавлять новые данные в неиспользуемые области вершины или буфер индекса. Приложение не должно изменять вершины или индексы, используемые в операции рисования, так как они по-прежнему могут использоваться графическим процессором. Затем приложение должно использовать D3DLOCK_DISCARD после полного динамического буфера для получения нового региона памяти, отменяя старые данные вершин или индексов после завершения графического процессора.

Механизм асинхронного запроса полезен для определения того, используются ли вершины графическим процессором. Выполните запрос типа D3DQUERYTYPE_EVENT после последнего вызова DrawPrimitive, использующего вершины. Вершины больше не используются, если IDirect3DQuery9::GetData возвращает S_OK. Блокировка буфера с помощью D3DLOCK_DISCARD или без флагов всегда гарантирует правильность синхронизации вершин с графическим процессором, однако использование блокировки без флагов приведет к штрафу производительности, описанной ранее. Другие вызовы API, такие как IDirect3DDevice9::BeginScene, IDirect3Device9::EndSceneи IDirect3DDevice9::P resent не гарантируют, что графический процессор завершен с помощью вершин.

Ниже приведены способы использования динамических буферов и соответствующих флагов блокировки.

    // USAGE STYLE 1
    // Discard the entire vertex buffer and refill with thousands of vertices.
    // Might contain multiple objects and/or require multiple DrawPrimitive 
    //   calls separated by state changes, etc.
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // Discard and refill the used portion of the vertex buffer.
    CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
    // USAGE STYLE 2
    // Reusing one vertex buffer for multiple objects
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // No overwrite will be used if the vertices can fit into 
    //   the space remaining in the vertex buffer.
    DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
    
    // Check to see if the entire vertex buffer has been used up yet.
    if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
    {
        // No space remains. Start over from the beginning 
        //   of the vertex buffer.
        dwLockFlags = D3DLOCK_DISCARD;
        m_nNextVertexData = 0;
    }
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData, 
               &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 
               m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
 
    // Advance to the next position in the vertex buffer.
    m_nNextVertexData += nSizeOfData;

Использование сетки

Вы можете оптимизировать сетки с помощью индексированных треугольников Direct3D вместо индексированных полос треугольников. Оборудование обнаружит, что 95 процентов последовательных треугольников фактически образуют полосы и корректируют соответствующим образом. Многие драйверы также делают это для более старого оборудования.

Объекты сетки D3DX могут иметь каждый треугольник или лицо, помеченные DWORD, называемым атрибутом этого лица. Семантика DWORD определяется пользователем. Они используются D3DX для классификации сетки в подмножества. Приложение задает атрибуты для каждого лица с помощью вызова ID3DXMesh::LockAttributeBuffer. Метод ID3DXMesh::Optimize имеет возможность сгруппировать вершины сетки и лица на атрибуты с помощью параметра D3DXMESHOPT_ATTRSORT. После этого объект сетки вычисляет таблицу атрибутов, которую можно получить приложением, вызвав ID3DXBaseMesh::GetAttributeTable. Этот вызов возвращает значение 0, если сетка не отсортирована по атрибутам. Приложение не может задать таблицу атрибутов, так как она создается методом ID3DXMesh::Optimize. Сортировка атрибутов учитывает данные, поэтому если приложение знает, что сетка отсортирована, она по-прежнему должна вызывать ID3DXMesh::Optimize для создания таблицы атрибутов.

В следующих разделах описаны различные атрибуты сетки.

Идентификатор атрибута

Идентификатор атрибута — это значение, которое связывает группу лиц с группой атрибутов. Этот идентификатор описывает подмножество лиц ID3DXBaseMesh::D rawSubset. Идентификаторы атрибутов указываются для лиц в буфере атрибутов. Фактические значения идентификаторов атрибутов могут быть все, что соответствует 32 битам, но обычно используется значение 0 к n, где n является числом атрибутов.

Буфер атрибутов

Буфер атрибутов представляет собой массив DWORD (по одному на лицо), который указывает, в какой группе атрибутов принадлежит каждое лицо. Этот буфер инициализируется до нуля при создании сетки, но заполняется подпрограммами загрузки или должен быть заполнен пользователем, если требуется несколько атрибутов с идентификатором 0. Этот буфер содержит сведения, используемые для сортировки сетки на основе атрибутов в ID3DXMesh::Optimize. Если таблица атрибутов отсутствует, ID3DXBaseMesh::D rawSubset сканирует этот буфер, чтобы выбрать лица заданного атрибута для рисования.

Таблица атрибутов

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

Производительность Z-буфера

Приложения могут повысить производительность при использовании z-буферизации и текстирования, гарантируя, что сцены отрисовываются с передней на спину. Текстурированные примитивы z-буфера предварительно проверяются на основе строки сканирования z-буфера. Если линия сканирования скрыта ранее отрисованным многоугольником, система отклоняет ее быстро и эффективно. Z-буферизация может повысить производительность, но метод наиболее полезен, когда сцена рисует одни и те же пиксели более одного раза. Это трудно вычислить точно, но часто можно сделать близкое приближение. Если одни и те же пиксели рисуются менее чем в два раза, можно добиться оптимальной производительности, переключив z-буферизацию и отрисовки сцены с заднего края на передний план.

советы по программированию