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

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