Меню

Как сделать шейдеры в видеокарте



Кэширование шейдеров NVIDIA. ВКЛ или ВЫКЛ

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

Эта статья расскажет о кэшировании шейдеров NVIDIA. Нужно определиться, что лучше, включить или отключить функцию. На видеокарте Gigabyte GTX 1080 и последних драйверах параметр по умолчанию включён. В большинстве игр настройка не влияет на частоту кадров.

Что такое кэширование шейдеров NVIDIA

Параметр в некоторых приложениях снижает нагрузку ЦП путём сохранения скомпилированный шейдеров на диске. Используется только при запуске игры на первом построении шейдеров. Ранее собранный шейдер будет уже просто извлечён из дискового кэша.

Типичные ситуации применения:

Включено увеличивает плавность изображения и снижает время загрузки;
Отключено используется для экономии места на локальном диске.

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

Как отключить кэширование шейдеров

NVIDIA

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

В классической Панели управления NVIDIA откройте раздел Параметры 3D > Управление параметрами 3D. Измените значение параметра Кэширование шейдеров на Выключено. В целом это может освободить дополнительно несколько гигабайт дискового пространства.

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

Steam

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

Перейдите в раздел Steam > Настройки > Кэш шейдеров. Чтобы отключить кэширование, снимите отметку Включить кэш шейдеров. При включении нагрузка на сеть и дисковое пространство может незначительно вырасти. Без поддержки драйверов функция не работает.

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

Источник

Разделы

Шейдеры

Это пока черновой вариант. Материал будет дорабатываться в течение семестра.

Шейдеры

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

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

Существует несколько языков программирования шейдеров. Для OpenGL «родным» является GLSL (OpenGL Shading Language).

Существует 5 мест в графическом конвейере, куда могут быть встроены шейдеры. Соответственно шейдеры делятся на типы:

  • вершинный (vertex)
  • фрагментный (fragment)
  • геометрический (geometry)
  • 2 тесселяционных шейдера (tesselation), отвечающие за 2 разных этапа тесселяции
Читайте также:  Как понизить в вот видеокарту

Дополнительно существуют вычислительные (compute) шейдеры, который выполняются независимо от графического конвейера.

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

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

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

Сначала рассмотрим, как шейдеры используются из основной программы на C++, а затем ниже разберем, как пишутся сами шейдеры на языке GLSL.

Компиляция, линковка и использование шейдеров

Сначала нужно создать новый (пустой) шейдерный объект и получить его идентификатор:

Для создания вершинного шейдера указывается константа GL_VERTEX_SHADER, для фрагментного — GL_FRAGMENT_SHADER.

Далее в шейдерный объект копируется текст шейдера на языке GLSL. Текст шейдера может быть разбит на несколько строк. В данном примере одна строка:

Далее рекомендуется выполнить проверку на возможные ошибки компиляции:

Аналогично делается для фрагментного шейдера.

После этого нужно собрать всё в шейдерную программу и слинковать её:

Перед использованием шейдерной программы нужно её активировать командой glUseProgram:

Для рендеринга разных 3D-моделей можно использовать разные шейдерные программы. Рекомендуется минимизировать переключения между шейдерными программами во время рендеринга одного кадра.

Язык GLSL

Язык GLSL очень похож на язык C/C++: объявления переменных, условия, циклы, функции, препроцессор.

Отличия: нет указателей и ссылок, нет строк, нет классов и ООП (но есть структуры и массивы).

Базовые типы данных: bool, int, uint, float, double.

Важное замечание: использовать тип double не рекомендуется, т.к. операции с ним значительно медленнее, чем с типом float.

Поскольку GLSL предназначен для 3D-вычислений, то в язык встроены производные типы данных: векторные и матричные:

  • vec2 — вектор из 2х-компонент типа float
  • vec3 — вектор из 3х-компонент типа float
  • vec4 — вектор из 4х-компонент типа float

Аналогично dvec2, dvec3, dvec4, ivec2, ivec3, ivec4, uvec2, uvec3, uvec4, bvec2, bvec3, bvec4 — вектора с компонентами типов double, int, uint, bool .

  • mat2 — матрица 2х2 с компонентами типа float
  • mat3 — матрица 3х3 с компонентами типа float
  • mat4 — матрица 4х4 с компонентами типа float

Также есть матрицы mat2x3, mat2x4, mat3x2, mat3x4, mat4x2, mat4x3 с компонентами типа float. Первое число — количество столбцов, второе — количество строк.

Аналогично типы dmat2, dmat3, dmat4, dmat2x3, dmat2x4, dmat3x2, dmat3x4, dmat4x2, dmat4x3 — матрицы с компонентами типа double.

Для инициализации векторов и матриц существует множество конструкторов на все случаи жизни. Можно передавать данные в конструктор, почти любые комбинации чисел и векторов. Вот несколько примеров:

К элементам вектора можно обращаться как к элементам массива:

Также к элементам вектора можно обращаться, как к полям структуры. Причем существует 3 равнозначные группы полей:

x, y, z, w используются, если вектор содержит пространственные координаты или нормаль
r, g, b, a используются, если вектор содержит цвет
s, t, p, q используется, если вектор содержит текстурные координаты
Читайте также:  Acer aspire z3100 драйвера видеокарты

Т.е. выражения v[0] , v.x , v.r , и v.s эквивалентны. Это нужно для облегчения чтения текста шейдера. Когда читатель видит v.r , он понимает, что вектор v содержит цвет.

GLSL позволяет одновременно обращаться сразу к нескольким полям как на чтение, так и на запись:

Большинство операций с векторами — поэлементные. Векторы складываются и умножаются поэлементно:

Функции в GLSL устроены аналогично C/C++. Из функции можно вернуть значение заданного типа, либо void. Но иногда необходимо передать значение обратно через аргументы. Поскольку в GLSL нет указателей и ссылок, то применяются модификаторы out и inout.

  • out означает, что переменная будет проинициализирована в функции и потом скопирована наружу.
  • inout означает, что переменная будет скопирована в функцию, там возможно будет изменена, и потом скопирована наружу.

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

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

Юниформ-переменные

В шейдере можно объявлять константы, которые будут иметь фиксированное значение:

Но можно также объявлять константы, которые будут проинициализированы основной программой до запуска шейдеров. Такие константы называют юниформ-переменными. Они объявляются вне тел функций с помощью модификатора uniform:

Каждая юниформ-переменная имеет свой номер (location). Если номер не указан, то он назначается автоматически при линковке шейдерной программы. Вручную номер можно указать так:

В основной программе значение юниформ-переменной задается по её номеру. Если номер не известен, то его можно запросить командой glGetUniformLocation:

Примеры задания значений векторных и матричных юниформ-переменных будут в следующих частях.

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

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

Задача вершинного шейдера — подготовить данные для следующих шагов графического конвейера. Эта подготовка включает обработку вершинных атрибутов.

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

Каждый атрибут в шейдере имеет свой номер (location). Его можно:

  • задать вручную в коде шейдера (как в примере выше)
  • задать в основной программе с помощью функции glBindAttribLocation
  • он может быть назначен автоматически при линковке шейдерной программы

Номер атрибута указывается первым аргументом команды glVertexAttribPointer. Если номер атрибута не известен, то его можно запросить командой glGetAttribLocation.

Выходные переменные объявляются с модификатором out.

Также существует одна встроенная выходная переменная gl_Position, неявно определенная так:

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

Простейший вершинный шейдер просто копирует выходные значения на выход:

Фрагментный шейдер

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

Читайте также:  Physx не использует видеокарту

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

Имена и типы должны совпадать у выходных переменных вершинного шейдера и входных переменных фрагментного шейдера.

Можно выбирать из 3х вариантов интерполяции путем указания специального модификатора:

  • flat — интерполяция не производится, выходные значения одной из вершин присваиваются всем фрагментам треугольника;
  • noperspective — интерполяция производится в экранной системе координат;
  • smooth — интерполяция производится с учетом эффекта перспективы (это значение по умолчанию).

Модификатор должен быть указан перед объявлением переменной в обоих шейдерах (вершинном и фрагментном):

Также существует ряд неявных входных переменных, например:

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

Шейдер должен записать выходной цвет фрагмента в выходную переменную. Название может быть произвольным:

Есть также неявная встроенная переменная gl_FragDepth:

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

Также шейдер может забраковать фрагмент по некоторому условию путем указания ключевого слова discard:

Забракованный фрагмент отбрасывается и далее не обрабатывается.

Простейший фрагментный шейдер выглядит следующим образом:

Uniform Buffer Object

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

Чтобы ускорить копирование юниформ-перменных в оперативную память, а также, чтобы можно было использовать одни и те же значения в разных шейдерных программах, применяют Uniform Buffer Object (UBO).

В шейдере UBO задается в виде блока юниформ-переменных, например, так:

Этот блок содержит 2 переменные и имеет название Matrices. Параметр std140 задает схему выравнивания переменных внутри блока. Есть разные схемы. Некоторые зависят от реализации. Схема std140 не зависит от конкретной реализации, она описана в спецификации OpenGL.

В основной программе необходимо создать буфер и заполнить его данными. Тип буфера GL_UNIFORM_BUFFER:

Здесь используется переменная camera, которая определяется так:

Каждый кадр, после изменения положения виртуальной камеры, нужно перезаливать новые значения матриц в буфер:

Функция glBufferSubData не выделяет новую память на видеокарте, а обновляет содержимое существующего участка памяти.

Теперь осталось только связать буфер, заданный переменной ubo, и блок с именем Matrices в шейдере. Для этого применяется 2хступенчатая схема. Вводится набор возможных точек привязки 0, 1, 2, … Буфер ubo привязывается к некоторой выбранной точке привязки. Далее номер точки привязки привязывается к юниформ-блоку. Для этого нужно сначала получить индекс (номер) юниформ-блока в шейдере:

Но что делать, если блок содержит большое число переменных, или схема выравнивания не std140. В этом случае, чтобы заполнить буфер значениями, нужно получить сдвиги отдельных юниформ-переменных:

Источник