Home

Приложения:

Проекты:

Документация:

Форум
/Главная/Документация/nv.html

Руководство по программированию GPU NVidia

Глава 1. О документе

1.1. Введение

1.2. Обратная связь

Глава 2. Как оптимизировать ваше приложение

2.1. Проведение точных измерений

2.2 Поиск узких мест

2.2.1 Причины возникновения узких мест

2.2.2. Основные тесты

2.2.3. Использование утилиты NVPerfHUD

2.3 Узкое место процессор

2.4. Узкое место GPU

Глава 3. Общие рекомендации по улучшению производительности GPU

3.1. Советы

3.2. Пакеты

3.2.1. Используйте меньшее количество пакетов

3.3. Вершинный шейдер

3.3.1. Используйте вызовы с индексированными примитивами

3.4. Шейдеры

3.4.1. Используйте по-возможности минимальные версии шейдера

3.4.2. Компилируйте шейдер с использованием профайла ps_2_a

3.4.3. Используйте минимально достаточную точность при работе с вещественными числами

3.4.4. Оптимизируйте расчеты путем использования алгебры

3.4.5. Избегайте помещать компоненты вектора в свободные компоненты векторных регистров.

3.4.6. Избегайте излишне универсальных функций в библиотеках

3.4.7. Не вычисляйте длину нормализованных векторов

3.4.9. Не используйте uniform-параметры для констант, которые не изменятся за время жизни пиксельного шейдера

3.4.10. Сбалансируйте вершинный и пиксельный шейдеры

3.4.11. При наличии узкого места в пиксельном шейдере поместите часть работы в вершинный шейдер

3.4.12. Используйте стандартную библиотечную функцию mul()

3.4.13. Используйте D3DTADDRESS_CLAMP (or GL_CLAMP_TO_EDGE) вместо saturate() для получения тектурных координат

3.4.14. Используйте интертполянты с меньшими номерами в первую очередь

3.5. Текстурирование

3.5.1. Используйте mipmap

3.5.2. Аккуратно пользуйтесь трехлинейной и анизотропной фильтрацией

3.5.3. Замените сложные функции чтением из текстур

3.6. Производительность

3.6.1. Удвоение скорости с Z-Only и трафаретной отрисовкой

3.6.2. Оптимизация Early-Z

3.6.3. Сначала глубина

3.6.4. Размещение памяти

3.7. Устранение ступенчатых искажений (АА)

Глава 4. Советы по программированию GPU серий GeForce 6 и 7

4.1. Поддержка Shader Model 3.0

4.1.1. Пиксельный шейдер версии 3.0

4.1.2. Вершинный шейдер версии 3.0

4.1.3. Динамическое ветвление

4.1.4. Облегчение работы с кодом

4.1.5. Экземпляры объекта

4.1.6. Заключение

 

Глава 1. О документе

 

1.1. Введение

 

Данное руководство поможет вам получить максимальную производительность от вашего компьютера и создать приложение с лучшей графикой. Помните, никогда не рано спросить совета или помощи послав письмо на devsupport@nvidia.com.

Данный документ организован следующим образом:

Глава 1 дает краткий обзор

Глава 2 обьясняет как оптимизировать ваше приложение путем выявления и устранения распрастраненных узких мест

Глава 3 содержит рекомендации по устранению узких мест. Рекомендации разбиты на категории и приоритеты.

Глава 4 содержит некоторые полезные советы по программированию GPU серий GeForce 7, GeForce 6 и Quadro FX на основе NV4X.

Глава 5 содержит некоторые полезные советы по программированию GPU серий GeForce FX и Quadro FX на основе NV3X. Эти советы посвящены, в основном, особенностям GPU, но в некоторых случаях помогут улучшить производительность.

Глава 6 содержит общие рекомендации по ускорителям NVIDIA, такие как производительность, идентификация GPU, и т.п.

Глава 7 посвящена технологии SLI, позволяющей увеличить производительность путем использования нескольких GPU одновременно

Глава 8 обьясняет как использовать стереоскопические возможности

Глава 9 содержит обзор инструментов улучшения производительности

 

1.2. Обратная связь

 

Ваши комментарии и вопросы направляйте на адрес devsupport@nvidia.com.

 

Глава 2. Как оптимизировать ваше приложение

 

В этой главе приведена типичная процедура поиска и устранения узких мест в графическом приложении.

 

2.1. Проведение точных измерений

 

Многие инструменты позволяют вам точно и надежно измерить производительность. Например, желтая линия в NVPerfHUD (см. Руководство по NVPerfHUD) позволяет измерить количество миллисекунд за кадр и отображает текущее количество кадров в секунду.

Чтобы точно измерить производительность необходимо:

  1. Убедится что приложение работает без сообщений об ошибках. Например, когда приложение запущено с Microsoft DirectX Debug, не должно выводится никаких ошибок или предупреждений.
  2. Убедится что ваша тестовая среда содержит release-версии всех dll-библиотек, в том числе последнюю версию DirectX runtime.
  3. Используйте release-версии (не debug) всех программ.
  4. Проверьте что параметры дисплея установлены правильно (обычно это значит по-умолчанию). Анизотропная фильтрация или antialiasing могут значительно повлиять на производительность.
  5. Выключите вертикальную синхронизацию. Тогда количество кадров в секунду не будет ограничено частотой вертикальной развертки вашего монитора.
  6. Запускайте приложение на соответствующей аппаратной конфигурации. Узкие места могут значительно сместиться если вы переключитесь с бюджетной системы на более продвинутую.

 

2.2 Поиск узких мест

 

2.2.1 Причины возникновения узких мест

 

Предположим, мы обнаружили ситуацию, где приложение работает медленно, и нам необходимо выяснить чем это вызвано. Обычно производительность сильно зависит от содержания сцены. Более того, она может изменяться в различных частях кадра. Поэтому поиск узкого места зачастую означает поиск наиболее критичного узкого места. Устранение наиболее критичных узких мест позволяет добится наибольшего прироста производительности.

 

Рис 1. Потенциальные узкие места

 

В идеальном мире нет узких мест: процессор, шина AGP и конвеер графического процессора загружены полностью (рис. 1). К сожалению, на практике это не достижимо, так как какой-то из компонентов всегда сдерживает остальные. Узким местом может быть процессор или GPU. Зеленая линия в NVPerfHUD (см. Главу 10.1) показывает в течение скольки миллисекунд GPU работает вхолостую на каждом кадре. Если GPU ожидает даже 1 миллисекунду за кадр, это говорит о том, что одно из узких мест - процессор. Если GPU ожидает большую часть времени, или даже всего лишь 1 миллисекунду за все кадры, но приложение не может синхронизировать процессор и GPU, значит самое критичное узкое место процессор. В таком случае улучшение производительности GPU лишь увеличит его время ожидания.

 

2.2.2. Основные тесты

 

Существуют несколько простых тестов, позволяющих обнаружить проблемные места. Для их проведения не нужны дополнительные инструменты или драйверы.

  1. Избавтесь от функций доступа к файлам. Любой обмен с диском сильно уменьшит скорость обновления кадров. Это условие легко проконторлировать достаточно взглянуть на индикатор использования жесткого диска на вашем системном блоке, или используя такие утилиты как perfmon, CodeAnalyst или VTune. Помните, что обращение к диску также может быть вызвано подкачкой страниц, особенно это актуально если ваше приложение использует много памяти.
  2. Запускайте приложение на одной и той же скорости GPU, но с различными скоростями процессора. Для этого удобно использовать BIOS, который позволяет выставлять скорость процессора. Это позволит вам обойтись одним компьютером для теста. Если скорость обновления кадров изменяется пропорционально частоте процессора, значит узкое место процессор.
  3. Уменьшите частоту вашего GPU. Для этого используйте утилиты типа Coolbits (см. Главу 6). Если замедление частоты GPU пропорционально изменяет частоту кадров, значит узкое место шейдеры вершин, растеризатор или шейдер фрагментов.
  4. Уменьшите частоту памяти вашего GPU. Для этого используйте утилиты типа Coolbits (см. Главу 6). Если замедление частоты памяти GPU пропорционально изменяет частоту кадров, значит узкое место доступ к текстурам или буферу кадра (frame buffer).

 

Изменение частоты процессора, GPU или памяти GPU легко обнаруживает узкое место если им является процессор, и намного труднее если им является GPU. Если понижение частоты процессора на N процентов ведет к понижению частоты кадров на N процентов, значит наиболее критичное узкое место процессор. Если понижение частоты GPU и памяти GPU на N процентов ведет к понижению частоты кадров на N процентов, значит наиболее критичное узкое место GPU.

 

2.2.3. Использование утилиты NVPerfHUD

 

Чтобы убедиться, что проблема в процессоре, запустите ваше приложение на специальном пустом драйвере (Null Hardware Driver). Такой драйвер делает все то, что делает обычный драйвер, за исключением того что он никогда ничего не отправляет на обработку в GPU. Таким образом пустой драйвер эмулирует бесконечно быстрый GPU. Если производительность с пустым драйвером не выше, чем с нормальным, значит узкое место процессор.

NVPerfHUD 2.0 может работать в специальном режиме, когда все функции обмена с GPU отключены. Таким образом эмулируется пустой драйвер. Однако следует помнить, что отключение функций обмена с GPU снижает и нагрузку на процессор, так как процессор не должен более обрабатывать и подтверждать все изменения состояний GPU, которые обычно предшествуют вызову процедуры отображения.

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

Если NVPerfHUD показывает, что GPU никогда не простаивает, значит узкое место GPU. Синяя линия на экране показывает сколько миллисекунд драйвер ждет GPU, что позволяет оценить производительность GPU.

Кроме того, NVPerfHUD 3.0 имеет режим анализа кадра Frame Analysis, позволяющий остановить ваше приложение и пошагово отрисовать кадр. Используя Advanced State Inspectors для Index Unit, Vertex Shader, Pixel Shader и Render Operations вы можете увидеть что происходит в GPU на каждой стадии графического конвеера.

Методология поиска и устранения узких мест подробно описана в руководстве пользователя NVPerfHUD и доступна по адресу:

http://developer.nvidia.com/object/nvperfhud_home.html.

 

2.3 Узкое место процессор

 

Если приложение тормозит изза процессора, используйте профайлинг (profiling) для поиска наиболее тяжелых функций. Обычно процессор тратит значительное время в следующих модулях:

- Приложение (exe-файл и используемые dll-файлы)

- Драйвер (nv4disp.dll, nvoglnt.dll)

- Библиотека DirectX runtime (d3d9.dll)

- Библиотека абстрактного аппаратного обеспечения (bal32.dll)

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

Далее, необходимо проанализировать код приложения и найти модули кода, которые можно сократить или убрать. Если процессор тратит много времени в библиотеках hal32.dll, d3d9.dll или nvoglnt.dll, то это может означать неоптимальное использование вызовов API. Если драйвер отнимает значительную часть времени процессора, то можно оптимизировать размер пакета (batch size) и уменьшить количество вызовов драйвера. Подробная информация об оптимизации пакетов содержится здесь:

http://developer.nvidia.com/docs/IO/8230/BatchBatchBatch.ppt

http://download.nvidia.com/developer/presentations/GDC_2004/Dx9Optimization.pdf

 

Также определить проблемы с быстродействием драйвера можно с помощью утилиты NVPerfHUD. Утилита показывает время, потраченное процессором в драйвере ( в виде красной линии), и количество пакетов, отрисованных за один кадр.

Быстродействие при медленном процессоре определяется также следующими факторами:

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

- Процессор выполняет работу за GPU. Удаление нескольких невидимых треугольников требует процессорного времени, но если GPU простаивает, то почему бы не передать эту работу туда? Удаление оптимизаций на стороне процессора может увеличить общую производительность при условии что узкое место процессор.

- Разгрузка процессора путем передачи части работы по обсчету пикселов и вершин в пиксельные и вертексные шейдеры в GPU.

- Использование шейдеров для увеличения размеров пакета и уменьшения процессорного времени в драйвере. Например вы можете скомбинировать два материала в один шейдер и отрисовать их за один вызов, вместо того чтобы делать отдельный вызов со своим шейдером для каждого материала. Для комбинирования различных шейдеров в один можно использовать Shader Model 3.0 (см. Параграф 4.1).

 

2.4. Узкое место GPU

 

В GPU используется конвеерная архитектура. Если известно, что узкое место в GPU, то необходимо найти какая именно стадия конвеера самая медленная. Обзор различных стадий конвеера приведен здесь:

http://developer.nvidia.com/docs/IO/4449/SUPP/GDC2003_PipelinePerformance.ppt

 

С помощью утилиты NVPerfHUD можно включать или выключать ту или иную функциональность драйвера или GPU. Например с помощью mipmap LOD bias можно сделать все текстуры размером 2х2. Если производительность при этом возрастет, значит узкое место кэш-промахи при доступе к текстурам. Подобным образом с помощью NVPerfHUD можно управлять выполнением пиксельных шейдеров.

Если вы обнаружили, что узкое место в GPU, прочтите в Главе 3 о том, как улучшить производительность.

 

Глава 3. Общие рекомендации по улучшению производительности GPU

 

 

В этой главе приведены наиболее эффективные приемы по улучшению производительности на GPU серий GeForce FX, GeForce 6 и GeForce 7. Для удобства информация рассортирована по стадиям конвеера и эффективности.

Много полезной информации о производительности современных GPU можно найти в главе Производительность графического конвеера в книге GPU Gems: Programming Techniques, Tips, and Tricks for Real-Time Graphics. Глава посвящена выявлению узких мест и устранению потенциальных проблем производительности. Главу можно посмотреть здесь:

http://developer.nvidia.com/object/gpu_gems_samples.html

 

 

 

3.1. Советы

 

При правильном использовании на современных GPU можно добиться впечатляюшей производительности. Ниже приведены рекомендации как это сделать.

 

1. Неоптимальное разбиение на пакеты вызывает возникновение узкого места в процессоре

Используйте меньшее количество пакетов:

        Комбинируйте несколько текстур в одну чтобы избежать частых изменений состояний текстур (http://developer.nvidia.com/object/nv_texture_tools.html)

        При использовании DirectX пользуйтесь Instancing API чтобы избежать SetMatrix и ему подобных способов изменения состояния

 

2. Узкое место в вершинном шейдере

Используйте индексированные примитивы:

        Используйте ID3DXMesh:OptimizeInplace() или ID3DXMesh:Optimize() для оптимизации мешей в DirectX 9.

        Если индексированный список использовать нельзя, пользуйтесь нашей утилитой NVTriStrip (http://developer.nvidia.com/object/nvtristrip_library.html)

 

3. Узкое место в пиксельном шейдере

Используйте минимально возможную версию пиксельного шейдера:

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

 

При работе с функциями ps_2_* пользуйтесь профайлом ps_2_a

 

Используйте, по-возможности, меньшую точность при работе с плавающей запятой

        Пользуйтесь типом half вместо float

 

Используйте тип half везде где это возможно:

        В изменяемых параметрах

        В Uniform-параметрах

        В переменных

        В константах

 

Сбалансируйте вершинный и пиксельный шейдеры

 

Переведите часть расчетов в вершинный шейдер

 

Не используйте uniform-параметры для хранения констант, которые не изменяются в течении времени жизни шейдера

 

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

 

Замените сложные функции чтением из текстур

        Попиксельное отраженное освещение

        Используйте утилиту FX Composer для создания файлов с программно-сгенерированными текстурами

        Sin, cos, log, exp являются встроенными инструкциями и поэтому их не следует заменять текстурами

 

4. Узкое место в чтении текстур

Используйте mipmap

        Осторожно используйте трилинейную и анизотропную фильтрацию

        Приведите в соответствие уровень анизотропной фильтрации и сложность текстуры

        Используйте Photoshop plug-in для подбора уровня анизотропной фильтрации (см. http://developer.nvidia.com/object/nv_texture_tools.html). Следуйте правилу: если в текстуре много шума - включите анизотропную фильтрацию.

 

5. Узкое место в растеризаторе

Используйте z-only и трафаретную отрисовку

Используйте оптимизацию Early-Z (z-cull)

 

 

3.2. Пакеты

 

 

3.2.1. Используйте меньшее количество пакетов

 

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

 

3.3. Вершинный шейдер

 

3.3.1. Используйте вызовы с индексированными примитивами

 

Вызовы с индексированными примитивами позволяют GPU задействовать вершинный кэш постобработки (post-transform-and-lighting cache). Так что если данная вершина уже трансформирована, то она не будет обрабатываться снова, а будет использован результат из кэша.

В DirectX вы можете использовать функцию OptimizeInPlace() класса ID3DXMesh для оптимизации мешей с тем чтобы увеличить процент попаданий в кэше шейдера.

Так же для этой цели вы можете использовать нашу утилиту NVTriStrip (см. http://developer.nvidia.com/object/nvtristrip_library.html).

 

3.4. Шейдеры

 

Высокоуровневый язык программирования шейдеров HLSL является мощным и гибким средством, значительно облегчающим написания шейдеров. К сожалению, написать медленный шейдер на нем намного легче. Если не проявлять осторожность, медленные шейдеры могут значительно затормозить ваше приложение. Далее приведен список советов, которые помогут вам избежать написания медленных шейдеров для простых эффектов. Кроме того вы научитесь как полностью задействовать вычислительные ресурсы GPU. При правильном использовании GPU серии GeForce FX могут производить до 20 операций за такт, а GPU серий GeForce 6 и 7 и более.

 

3.4.1. Используйте по-возможности минимальные версии шейдера

 

Используйте минимальные версии шейдера для ваших эффектов. Например, если вы просто выполняете чтение и смешивание текстур, то для этого нет никакой необходимости использовать версию ps_2_0 или выше.

 

3.4.2. Компилируйте шейдер с использованием профайла ps_2_a

 

Компилятор Microsoft HLSL (fxc.exe) оптимизирует под конкретный GPU в соответствии с используемым профайлом. Если у вас GPU серии GeForce FX, то вам следует использовать профайл ps_2_a, который представляет из себя развитие профайла ps_2_0, дополненное функциональностью специфичной для GeForce FX. Компилирование с профайлом ps_2_a скорее всего даст вам лучшую производительность, чем с ps_2_0. Имейте в виду, что профайл ps_2_a появился лишь в выпуске HLSL от июля 2003 года.

В общем случае всегда следует использовать последний выпуск fxc (c DirectX 9 и выше), так как Microsoft постоянно добавляет различные опции и исправляет ошибки в каждом выпуске. С GPU серий GeForce 6 и 7 обычно достаточно просто скомпилировать шейдер с нужным профайлом и с последней версией компилятора.

 

3.4.3. Используйте минимально достаточную точность при работе с вещественными числами

 

Еще один важный фактор, влияющий на быстродействие точность при работе с плавающей запятой. GPU серий GeForce FX, 6 и 7 поддерживают три формата вещественных чисел: 32-битный float для чисел плавающей запятой, 16-битный half для чисел плавающей запятой, и 12-битный fixed для чисел с фиксированной запятой. Тип float подобен соответствующему IEEE-типу с форматом s23e8. Тип half так же подобен соответствующему IEEE-типу с форматом s10e5. 12-битный тип fixed принимает значения от -2 до 2 и не используется в профайлах ps_2_0 и выше. Тип fixed используется в профайлах ps_1_0 ps_1_4 в DirectX, и только при наличии расширения NV_fragment_program или Cg в OpenGL.

Скорость при работе с разными типами различна:

  • Тип Fixed наиболее быстрый и хорошо подходит для расчетев с минимальной точностью, например вычисление цвета
  • Тип half следует использовать если вам нужны расчеты вещественных чисел при максимальной скорости, позволяет в некоторых случаях утроить производительность
  • Тип float следует использовать если вам нужна максимальная точность

Вы можете использовать флаг /Gpp чтобы указать компилятору использовать везде тип half. После того как ваш шейдер заработал, включите этот флаг и посмотрите как изменится производительность. Если не появились ошибки, оставте этот флаг включенным. В противном случае исправте ваш шейдер и вручную установите тип half там где это целесообразно (/Gpp покажет вам максимально возможную производительность, к которой следует стремиться).

При использовании типов half и fixed убедитесь, что вы используете те же типы для параметров, переменных и констант. Если вы используете ассемблер с профайлом ps_2_0 в DirectX, пользуйтесь модификатором _pp для уменьшения точности.

Если вы используете расширение ARB_fragment_program в OpenGL, включите опцию ARB_precision_hint_fastest для уменьшения времени выполнения с возможным снижением точности, или включите опцию NV_fragment_program для получения возможности задания точности для каждой инструкции отдельно (см. http://www.nvidia.com/dev_content/nvopenglspecs/GL_NV_fragment_program_option.txt).

Обычно операций по расчету цвета можно выполнять с типами half и fixed без заметного ущерба качества (например, операция tex2D*diffuseColor).

На GPU серии GeForce FX под OpenGL можно значитетельно увеличить производительность шейдера если пользоваться типом fixed для операций (таких как, скалярное произведение нормализованных векторов).

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

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

 

3.4.4. Оптимизируйте расчеты путем использования алгебры

 

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

  • Обычная сферическая проекция часто выражается формулой

Что равносильно

 

 

Если заранее известно, что вектор отражения нормализован (см. Параграф 3.4.8 и 3.4.6), то сумма первых 3-х слагаемых будет всегда равна 1. Тогда выражение можно записать так:

 

 

 

  • Поместите скалярное произведение на 1.414 в другую константу (см. Параграф 3.4.8).

 

  • Выражение dot(normalize(N), normalize(L)) может быть вычислено намного быстрее:

Обычно оно вычисляется по формуле (N/|N|) dot (L/|L|), что требует двух вызовов медленной функции вычисления квадратного корня rsq. Немного изменив выражение получим:

(N/|N|) dot (L/|L|)

= (N dot L) / (|N| * |L|)

= (N dot L) / (sqrt( (N dot N) * (L dot L) )

= (N dot L) * rsq( (N dot N) * (L dot L) )

Теперь нам требуется только один вызов rsq.

 

3.4.5. Избегайте помещать компоненты вектора в свободные компоненты векторных регистров.

 

Упаковка разнородной информации затрудняет эффективную оптимизацию кода при компиляции. Например, если вы обрабатываете тангенциальную матрицу, не стоит помещать компоненты вектора viewVec в свободные регистры. Данная ошибка проиллюстрирована ниже:

 

// Так лучше не делать

tangent = float4(tangentVec, viewVec.x)

binormal = float4(binormalVec, viewVec.y)

normal = float4(normalVec, viewVec.z)

 

Вместо этого поместите вектор viewVec в свою собственную векторную переменную.

 

 

3.4.6. Избегайте излишне универсальных функций в библиотеках

 

Библиотечные функции часто делают универсальными. Например, отражение обычно расчитывают так:

 

float3 reflect(float3 I, float3 N)

{

return (2.0*dot(I,N)/dot(N,N))*N - I

}

 

В данном примере вектор отражения не зависит от длины векторов нормали или падения. Однако часто известен также нормализованный вектор нормали, который используется, например, для вычисления цвета. В этом случае из нашей версии reflect() можно убрать скалярное произведение и rsq. Оптимизации подобного рода могут значительно улучшить производительность.

 

 

3.4.7. Не вычисляйте длину нормализованных векторов

 

Хорошим примером излишне универсальной и медленной библиотечной функции будет вычисление длины вектора. Но часто векторы уже нормализованы к этому моменту. Компилятор не имеет возможности это определить, в итоге GPU будет выполнять расчет 1 для каждого пиксела.

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

 

3.4.8. Выносите операции с константами в CPU

 

Часто разработчики выполняют операции с константами в пиксельном шейдере. Если в выражении используется две или более констант, то часто константы можно сгруппировать вместе. Например:

half4 main(float2 diffuse : TEXCOORD0,

uniform sampler2D diffuseTex,

uniform half4 g_OverbrightColor)

{

return tex2D(diffuseTex, diffuse) * g_OverbrightColor * 3.0;

}

 

Умножение g_OverbrightColor * 3 можно выполнить в процессоре, избежав таким образом возможных миллионов умножений на каждый кадр в GPU.

 

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

 

Другой распрастраненный пример вычисление произведения materialColor * lightColor для каждой вершины. Так как данное выражение принимает всегда одно значение для каждого пакета отрисовки, его следует рассчитывать в процессоре перед тем как пакет посылается в GPU.

 

Так же в процессоре следует вычислять инверсные и дополнительные матрицы, так как их нужно вычислять только однажды, а не для каждой вершины или фрагмента. Ключи компилятора /Zpr (упаковать старшие в ряду) или /Zpc (упаковать старшие в колонке) позволят вам хранить матрицы в нужном вам формате.

 

3.4.9. Не используйте uniform-параметры для констант, которые не изменятся за время жизни пиксельного шейдера

 

Иногда разработчики используют uniform-параметры для передачи в шейдер часто используемых констант, например 0, 1 или 255. Этого следует избегать, так как компилятору в этом случае значительно сложнее отличить константы от параметров шейдера и оптимизировать код.

 

3.4.10. Сбалансируйте вершинный и пиксельный шейдеры

 

Достижение максимальной производительности достигается путем удаления узких мест то есть выравниванием нагрузки на все компоненты системы: процессор, шину AGP, элементы конвеера GPU. На решение использовать пиксельный шейдер или вершинный шейдер влияют несколько факторов:

  • Насколько много вы используете треугольников для представления ваших обьектов? Вам, возможно, придется снизить нагрузку на вершинный шейдер если у вас в каждом кадре отрисовываются миллионы треугольников, особенно при использовании многопроходных алгоритмов.
  • Какого разрешения вы хотите достигнуть? Если вы планируете использовать ваше приложение на высоком разрешении, возможно пиксельный шейдер станет узким местом. В этом случае вам придется переместить часть вычислений в вершинный шейдер.
  • Как велики ваши пиксельные шейдеры? Если у вас сложные пиксельные шейдеры, то возможно они станут узким местом. Например, если ваш шейдер выполняется за 20 циклов и рассчитывается для более, чем половины пикселов экрана, то на GPU серии GeForce FX в пиксельном шейдере возникнет узкое место. В этом случае вам придется перенести часть расчетов в вершинный шейдер (см. примеры в параграфе 3.4.11). Чтобы узнать, сколько циклов выполняются ваши шейдеры используйте утилиту NVShaderPerf. Имейте в виду, что более поздние GPU серий GeForce 6 или 7 позволяют производить более сложные вычисления в шейдере без возникновения там узкого места.

 

3.4.11. При наличии узкого места в пиксельном шейдере поместите часть работы в вершинный шейдер

 

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

Например, расчеты затухания света можно перенести из мировых координат в координаты относительно источника. Или при использовании bump-mapping можно переместить расчеты в тангенциальное просторанство каждой вершины (при условии если вы не используете попиксельное отражение в кубическую map).

 

3.4.12. Используйте стандартную библиотечную функцию mul()

 

Вместо перемножения матриц вручную используйте стандартную библиотечную функцию mul(). Это поможет избежать некоторых проблем со старшими элементами рядов и колонок при работе с матрицами.

 

3.4.13. Используйте D3DTADDRESS_CLAMP (or GL_CLAMP_TO_EDGE) вместо saturate() для получения тектурных координат

 

На некоторых GPU saturate() может работать медленно. Если результат используется в качестве текстурных координат, то лучше использовать способность GPU преобразовывать координаты в диапазон [0..1], чем делать это в шейдере.

 

3.4.14. Используйте интертполянты с меньшими номерами в первую очередь

 

Производительность будет выше если вы будете использовать наборы текстурных координат с меньшими номерами. Начните с TEXCOORD0, затем TEXCOORD1, TEXCOORD2 и так далее.

 

 

3.5. Текстурирование

 

3.5.1. Используйте mipmap

 

Чтобы избежать сверкающих недорисовок всегда используйте mipmap-ы в ваших приложениях. Тогда вы достигните более высокого качества, высокой эффективности работы кэша текстур, и улучшенной производительности. Все это вы получите за увеличение обьема требуемой памяти всего лишь на 33%. В частности при включении mapmap многие операции с 3D-текстурами ускорились на 30-40%.

При создании mipmap следует не просто создавать все меньшие и меньшие mipmap-ы с помощью box-фильтра. Лучше использовать фильтры Гаусса или Митчелла, так как они дают результаты более высокого качества. Наш Photoshop plug-in (входящий в состав пакета NVidia Texture Tool Suite) позволит вам быстро создать высококачественные mipmap-ы. Пакет можно скачать здесь: http://developer.nvidia.com/object/nv_texture_tools.html.

 

3.5.2. Аккуратно пользуйтесь трехлинейной и анизотропной фильтрацией

 

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

Наш Photoshop plug-in позволяет определить необходимый уровень фильтрации. Plug-in можно скачать здесь: http://developer.nvidia.com/object/nv_texture_tools.html. С его помощью можно так же определить какую фильтрацию применять для каждой текстуры: анизотропную или трехлинейную.

 

3.5.3. Замените сложные функции чтением из текстур

 

Текстуры явлются удобным средством хранения предварительно рассчитанных результатов для сложных функций: их можно представить многомерными массивами. GPU серии GeForce FX работют с текстурами очень эффективно: часто чтение текстуры выполняется с почти такой же скоростью, что и арифметическая операция. Вы можете использовать нашу утилиту FX Composer для выполнения таких оптимизаций. Утилиту можно скачать здесь: http://developer.nvidia.com/FXComposer

Если у вас есть некоторая сложная функция, состоящая из последовательности арифметических операций, ее всегда можно записать в текстуру, улучшив тем самым производительность. Следует помнить, что некоторые сложные функции, например log или exp, реализованы в виде микроинструкций в профайлах версии ps_2_0 и выше, поэтому их не следует кодировать в текстуры.

 

3.5.3.1. Попиксельное освещение

 

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

Обычной ситуацией, требующей применения двухмерных текстур, является попиксельное освещение. Вы можете использовать двухмерную текстуру, индексированную по одной оси выражением (N dot L), а по другой (N dot H). В каждой ячейке текстуры (u, v) будет тогда записано выражение:

 

max(N dot L,0) + Ks*pow((N dot L>0) ? max(N dot H,0) : 0), n)

 

Это выражение вычисляет освещение согласно стандартной модели Блинна с учетом диффузии и отражения.

 

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

Полезным может оказаться использование одномерных ARGB-текстур, индексированных по выражению (N dot H). Такая текстура хранит различные степени выражения (N dot H) в различных каналах. Например она может содержать:

Далее, каждому материалу можно назначить 4-х компонентную весовую константу, которая будет использоваться при суммировании компонентов текстуры. Таким образом можно получить значение монохромного отражения для теней. Плюсом такого подхода является то, что он будет работать даже на GPU класса GeForce4, и позволяет получить множество различных эффектов.

 

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

В описанном выше подходе можно использовать трехмерные текстуры. Первые две размерности текстуры описаны выше, а в третьей размерности можно закодировать коэффициент отражения.

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

 

 

3.5.3.2. Нормализация векторов

 

Если вы пишете шейдер в профайле ps_1_*, для быстрой нормализации векторов используйте нормализационные кубические карты. Для высококачественной нормализации вы можете использовать две 16-разрядных знаковых кубических карты: одну для осей X и Y, другую для оси Z.

Еще один способ оптимизации использует тот факт, что нормализуемые векторы часто являются почти нормализованными, так как они образованы путем интерполирования нормалей или применения к нормалям фильтров. В этом случае вы можете апроксимировать выражение 1/||V|| первыми членами ряда Тейлора 1 / sqrt(x) при x=1:

 

так что:

 

 

Данную формулу можно вычислить двумя ассемблерными инструкциями:

dp3_sat r1, r0, r0

mad_d2 r1, r0, 1-r1, r0_x2

 

где r0 содержит V, а финальное значение V / ||V|| записано в r1.

Данный код можно использовать только в профайле ps_1_4, так как в нем используется регистровый модификатор _x2. Для более старых версий можно использовать другой код:

dp3_sat r1, r0_bx2, r0_bx2

mad r1, r0_bias, 1-r1, r0_bx2

 

Здесь в r0 записано (V + 1) , что не должно стать проблемой, так как V во многих случаях необходимо передавать в шейдер в сжатой форме в диапазоне [-1, 1] до [0, 1].

GPU серий GeForce 6 и 7 имеют в своем составе специальный модуль нормализации с половинной точностью, который может быстро нормализовать вектор fp16. Чтобы задействовать этот модуль просто выполните нормализацию вектора fp16, при этом компилятор сгенерирует инструкцию nrmh.

Более подробно оптимизация описана в наших справочниках Эвристики Нормализации и Компрессия Bump Map-ов. Справочники можно посмотреть здесь:

http://developer.nvidia.com/object/normalization_heuristics.html

http://developer.nvidia.com/object/bump_map_compression.html

 

3.5.3.3. Функция sincos

 

В дополнение к совету выше, GPU серии GeForce FX аппаратно поддерживает некоторые сложные математические функции. Одна из них sincos позволяет одновременно вычислит синус и косинус.

 

 

3.6. Производительность

 

3.6.1. Удвоение скорости с Z-Only и трафаретной отрисовкой

 

GPU серий GeForce 6 и 7 работают с удвоенной скоростью при отрисовывании только значений глубины и трафарета. Чтобы включить этот режим выполните следующие условия:

  • Запись цвета выключена
  • Активная поверхность глубины/трафарета не должна быть многокомпонентной
  • Texkill не должен использоваться ни в одном из фрагментов
  • Alfa Test должен быть выключен
  • Color Key не должен использоваться ни в одной из текстур
  • User Clip Planes должны быть выключены

 

 

3.6.2. Оптимизация Early-Z

 

Оптимизация Early-Z (иногда ее называют Z-Cull) позволяет улучшить производительность путем исключения невидимых поверхностей из процесса отрисовки. Если к невидимой поверхности подключен интенсивный шейдер, такая оптимизация позволит значительно сократить расчеты. Чтобы воспользоваться Z-Cull оптимизацией выполните следующие условия:

  • Не создавайте треугольники с дырками (то есть избегайте alpha test и texkill)
  • Не изменяйте значение глубины (то есть позвольте GPU использовать интерполированным значением глубины).

 

Нарушение этих правил приведет к тому, что данные, используемые GPU для Z-Cull оптимизации, будут неактуальны, что приведет к отключению Z-буфера вплоть до момента его очистки.

 

3.6.3. Сначала глубина

 

Наилучший способ воспользоваться двумя вышеописанными оптимизациями сначала просчитать глубину. То есть на первом проходе надо воспользоваться отрисовкой с удвоенной скоростью с тем чтобы отрисовать вашу сцену без шейдинга. Тем самым выявляются поверхности, видимые наблюдателю. Теперь вы можете отрисовать всю сцену заново, на этот раз с шейдингом. Z-Cull автоматически пропустит расчеты невидимых поверхностей.

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

Еще один подобный подход - Отложенный Шейдинг - вы можете найти в NVSDK версии 7.1 и выше.

 

3.6.4. Размещение памяти

 

Чтобы минимизировать шанс недостатка памяти лучше всего размещать шейдеры и отрисовывать сцену следующим образом:

  1. Сначала разместите поверхности

        Размещайте в порядке плотности (кол-во битов на пиксел * ширину)

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

 

  1. Создайте вершинные и пиксельные шейдеры
  2. Разместите оставшиеся текстуры

 

 

3.7. Устранение ступенчатых искажений (АА)

 

GPU серий GeForce FX, 6 и 7 имеют мощные средства АА, мы рекомендуем всегда включать АА.

Если вы используете приемы, которые не сочетаются с АА, напишите нам, мы будем рады обсудить возможные пути решения.

Одна из проблем, которая была решена в DirectX 9.0b использование АА в пост-процессинговых эффектах. Функция StretchRect() позволяет копировать фоновый буфер в неотоброжаемую текстуру для последующего использования с многокомпонентными текстурами.

Например, при включенной опции 4x Multisampling, для фонового буфера размером 100х100 драйвер создает буферы фона и глубины размером 200х200 для выполнения АА. Если приложение создаст неотображаемую текстуру размером 100х100, то с помощью функции StretchRect() можно преобразовать весь фоновый буфер в неотображаемую поверхность, при этом GPU выполнит АА и поместит откорректированное изображение в неотображаемую поверхность.

После этого над текстурой 100х100 можно выполнить различные операции постобработки (например, сияние), после чего поместить результат обратно в фоновый буфер.

Различие разрешений между реальным размером фонового буфера (200х200) и его размером с точки зрения приложения (100х100) обуславливает невозможность соединения многокомпонентных Z-буферов и однокомпонентных текстур.

 

 

 

Глава 4. Советы по программированию GPU серий GeForce 6 и 7

 

В данной главе представлены некоторые полезные советы, которые помогут вам полностью задействовать возможности GPU серий GeForce 6 и 7, а так же серии Quadro FX на основе NV4X.

 

4.1. Поддержка Shader Model 3.0

 

В состав Microsoft DirectX 9.0 входят несколько новых стандартов для новых технологий пиксельных и вершинных шейдеров версий 2.0 и 3.0. Аппаратура, поддерживающая Shader Model 2.0, появилась еще в 2002 году. Подавляющее большинство современных GPU поддерживает Shader Model 2.0 или выше. Shader Model 2.0 включает в себя технологии улучшеного освещения и анимации, но имеет ограничения по длине и сложности кода шейдера, что ограничивает разнообразие возможных эффектов.

Технология Shader Model 3.0 призвана устранить часть ограничений Shader Model 2.0.

 

4.1.1.   Пиксельный шейдер версии 3.0

 

Ниже приведен список отличий пиксельного шейдера версий 2.0 и 3.0.

 

Параметр пиксельного шейдера

Shader 2.0

Shader 3.0

Описание

Длина шейдера

96

65535+

Позволяет создавать более сложный шейдинг

Динамическое ветвление

Нет

Да

Позволяет пропускать расчеты для не нуждающихся в этом пикселов

Устранение ступенчатых искажений (АА)

Нет поддержки

Встроенные инструкции

????

Регистр обратной стороны

Нет

Да

Позволяет расчитывать 2-стороннее освещение в одном проходе

Интерполированый формат цвета

Минимум 8-битное целое

Минимум 32-битное вещественное

Больший диапазон и точность цвета обеспечивает лучший расчет освещения вершин

Отреисовка многокомпонентных поверхностей

Возможна

Требуется 4

Позволяет выполнять более совершенные алгоритмы расчета света для экономии на фильтрации и на вершинном шейдере, больше источников света

Размерность текстур

8

10

Большее количество параметров текстур позволяет получить более реалистичное изображение, особенно кожи

 

 

4.1.2. Вершинный шейдер версии 3.0

 

Ниже приведен список отличий вершинного шейдера версий 2.0 и 3.0.

 

Параметр вершинного шейдера

Shader 2.0

Shader 3.0

Описание

Длина шейдера

96

65535+

Позволяет более сложный шейдин, освещение и фактуру материала

Динамическое ветвление

Нет

Да

Позволяет пропускать расчеты для не нуждающихся в этом пикселов

Вершиннаые текстуры

Нет

Любое количество чтений из максимум 4-х текстур

Позволяет создавать текстуры смещений, эффекты для частиц

Экземпляры обьектов

Нет

Требуется

Позволяет отрисовывать множество обьектов за одну команду

 

 

4.1.3. Динамическое ветвление

 

Одно из основных отличий шейдеров версии 3.0 наличие динамического ветвления. Это позволяет разработчикам использовать циклы и условные переходы в шейдерах. Например можно написать вершинный шейдер, который в цикле проанализирует несколько источников света, определит какие из них влияют на данную вершину, и затем передаст номера этих источников пиксельному шейдеру. Далее, пиксельный шейдер может использовать параметры этих источников при расчете освещения путем перебора в цикле всех активных источников света.

Как правило источник света освещает только одну сторону поверхности ту, которая обращена к источнику. Поэтому можно использовать динамическое ветвление чтобы избежать расчетов с источниками, находящимися с обратной стороны поверхности (используя регистр обратной стороны back-face). Таким образом шейдер может быть значительно ускорен. Подобным образом можно ускорить расчеты скелетной анимации и многих других алгоритмов.

 

4.1.4. Облегчение работы с кодом

 

По мере усложнения игровых движков, в них часто создаются несколько различных версий каждого шейдера с тем чтобы удовлетворит ограничения по размеру кода в шейдере версии 2.0. Это усложняет обслуживание кода, увеличивает время компиляции и загрузки уровней, а так же увеличивает расход памяти во время выполнения приложения. В Shader Model 3.0 это неудобство устранено благодаря наличию динамических ветвлений. Ветвления позволяют иметь один вершинный и один пиксельный шейдеры, содержащие сразу все необходимымые пути выполнения, и позволяют тем самым избежать комбинаторного взрыва.

 

4.1.5. Экземпляры объекта

 

Еще одним важным отличием Shader Model 3.0 является поддержка Microsoft DirectX Instancing API создание экземпляров обьектов. В настоящее время в играх ограничение на количество уникальных обьектов в сцене обусловлено не GPU, а недостаточной мощностью процессора при сохранении или отрисовке большого количества однотипных обьектов. Например лес, обычно, состоит из большого количества деревьев, но у каждого дерева свои координаты, размеры, цвет листьев и т. д. Чтобы деревья были разными разработчик должен или хранить в памяти каждое дерево, или многократно отрисовать одно дерево, каждый раз с различными параметрами, и каждый раз производить медленные переключения состояний отрисовки.

Создание экземпляров позволяет программисту хранить в памяти только одно дерево, и множество потоков вертексных данных для задания цвета, высоты, размеров ветки и т. п. для каждого экземпляра. Например, модель дерева может состоять из 1000 вершин и содержит координаты и нормали, а ориентацию, цвет и высоту можно поместить в массив из 200 вершинных потоков. Создание экземпляров позволит за одну отрисовку отобразить 200 деревьев, при этом для задания формы для каждого экземпляра используется один и тот же массив на 1000 вершин, а для создания вариаций используется массив из 200 вершинных потоков.

Пример работы с экземплярами можно посмотреть здесь:

http://download.nvidia.com/developer/SDK/Individual_Samples/samples.html

 

 

4.1.6. Заключение

 

Shader Model 3.0 в DirectX 9.0 является значительным шагом вперед в плане простоты использования, производительности и сложности шейдера. Динамические ветвления ускоряют расчеты для многих алгоритмов, в то же время упрощая код шейдера. Кроме того, экземпляры позволяют достичь высокой сложности сцен при малых затратах памяти и процессора.

 

Rambler's Top100