HomeПриложения:Проекты:Документация:Форум |
/Главная/Документация/nv.html
Руководство по программированию GPU NVidia Глава 2. Как оптимизировать ваше приложение 2.1. Проведение точных измерений 2.2.1 Причины возникновения узких мест 2.2.3. Использование утилиты NVPerfHUD Глава 3. Общие рекомендации по улучшению производительности GPU 3.2.1. Используйте меньшее количество пакетов 3.3.1. Используйте вызовы с индексированными примитивами 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.10. Сбалансируйте вершинный и пиксельный шейдеры 3.4.11. При наличии узкого места в пиксельном шейдере поместите часть работы в вершинный шейдер 3.4.12. Используйте стандартную библиотечную функцию mul() 3.4.14. Используйте интертполянты с меньшими номерами в первую очередь 3.5.2. Аккуратно пользуйтесь трехлинейной и анизотропной фильтрацией 3.5.3. Замените сложные функции чтением из текстур 3.6.1. Удвоение скорости с Z-Only и трафаретной отрисовкой 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.4. Облегчение работы с кодом
Глава 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) позволяет измерить количество миллисекунд за кадр и отображает текущее количество кадров в секунду. Чтобы точно измерить производительность необходимо:
2.2 Поиск узких мест
2.2.1 Причины возникновения узких мест
Предположим, мы обнаружили ситуацию, где приложение работает медленно, и нам необходимо выяснить чем это вызвано. Обычно производительность сильно зависит от содержания сцены. Более того, она может изменяться в различных частях кадра. Поэтому поиск узкого места зачастую означает поиск наиболее критичного узкого места. Устранение наиболее критичных узких мест позволяет добится наибольшего прироста производительности.
Рис 1. Потенциальные узкие места
В идеальном мире нет узких мест: процессор, шина AGP и конвеер графического процессора загружены полностью (рис. 1). К сожалению, на практике это не достижимо, так как какой-то из компонентов всегда сдерживает остальные. Узким местом может быть процессор или GPU. Зеленая линия в NVPerfHUD (см. Главу 10.1) показывает в течение скольки миллисекунд GPU работает вхолостую на каждом кадре. Если GPU ожидает даже 1 миллисекунду за кадр, это говорит о том, что одно из узких мест - процессор. Если GPU ожидает большую часть времени, или даже всего лишь 1 миллисекунду за все кадры, но приложение не может синхронизировать процессор и GPU, значит самое критичное узкое место процессор. В таком случае улучшение производительности GPU лишь увеличит его время ожидания.
2.2.2. Основные тесты
Существуют несколько простых тестов, позволяющих обнаружить проблемные места. Для их проведения не нужны дополнительные инструменты или драйверы.
Изменение частоты процессора, 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. Скорость при работе с разными типами различна:
Вы можете использовать флаг /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. Тогда выражение можно записать так:
Обычно оно вычисляется по формуле (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. На решение использовать пиксельный шейдер или вершинный шейдер влияют несколько факторов:
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 работают с удвоенной скоростью при отрисовывании только значений глубины и трафарета. Чтобы включить этот режим выполните следующие условия:
3.6.2. Оптимизация Early-Z
Оптимизация Early-Z (иногда ее называют Z-Cull) позволяет улучшить производительность путем исключения невидимых поверхностей из процесса отрисовки. Если к невидимой поверхности подключен интенсивный шейдер, такая оптимизация позволит значительно сократить расчеты. Чтобы воспользоваться Z-Cull оптимизацией выполните следующие условия:
Нарушение этих правил приведет к тому, что данные, используемые GPU для Z-Cull оптимизации, будут неактуальны, что приведет к отключению Z-буфера вплоть до момента его очистки.
3.6.3. Сначала глубина
Наилучший способ воспользоваться двумя вышеописанными оптимизациями сначала просчитать глубину. То есть на первом проходе надо воспользоваться отрисовкой с удвоенной скоростью с тем чтобы отрисовать вашу сцену без шейдинга. Тем самым выявляются поверхности, видимые наблюдателю. Теперь вы можете отрисовать всю сцену заново, на этот раз с шейдингом. Z-Cull автоматически пропустит расчеты невидимых поверхностей. Расчет глубины требует дополнительного прохода отрисовки, но может в целом улучшить производительность, если у вас много невидимых поверхностей с интенсивными шейдерами. Отрисовка на двойной скорости становится менее эффективной с уменьшением размера треугольников, поэтому маленькие треугольники могут снизить эффективность данного подхода. Еще один подобный подход - Отложенный Шейдинг - вы можете найти в NVSDK версии 7.1 и выше.
3.6.4. Размещение памяти
Чтобы минимизировать шанс недостатка памяти лучше всего размещать шейдеры и отрисовывать сцену следующим образом:
Размещайте в порядке плотности (кол-во битов на пиксел * ширину) Отсортируйте разные плотностные группы по частоте использования. Поверхности, которые отрисовываются чаще других, следует размещать в первую очередь.
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.
4.1.2. Вершинный шейдер версии 3.0
Ниже приведен список отличий вершинного шейдера версий 2.0 и 3.0.
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 является значительным шагом вперед в плане простоты использования, производительности и сложности шейдера. Динамические ветвления ускоряют расчеты для многих алгоритмов, в то же время упрощая код шейдера. Кроме того, экземпляры позволяют достичь высокой сложности сцен при малых затратах памяти и процессора.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|