Оптимизация производительности (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 выполняется во время выполнения.

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

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

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

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

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

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

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

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, IDirect3DDevice9::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-буферизации и отрисовки сцены от задней части к передней.

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