Я понимаю, как писать программы OpenGL / DirectX, и я знаю математику и концептуальный материал, лежащий в основе этого, но мне любопытно, как связь GPU-CPU работает на низком уровне.
Скажем, у меня есть программа OpenGL, написанная на C, которая отображает треугольник и поворачивает камеру на 45 градусов. Когда я компилирую эту программу, будет ли она преобразована в серию ioctl-вызовов, и драйвер графического процессора затем отправит соответствующие команды на графический процессор, где вся логика поворота треугольника и установки соответствующих пикселей в соответствующий цвет подключена в? Или программа будет скомпилирована в «программу для графического процессора», которая загружается в графический процессор и вычисляет вращение и т. Д.? Или что-то совсем другое?
Изменить : несколько дней спустя я нашел эту серию статей, которая в основном отвечает на вопрос: http://fgiesen.wordpress.com/2011/07/01/a-trip-through-the-graphics-pipeline-2011-part- 1 /
Ответы:
На этот вопрос практически невозможно ответить, потому что OpenGL сам по себе является всего лишь интерфейсом API, и до тех пор, пока реализации придерживаются спецификации, а результат соответствует этому, это можно сделать любым удобным для вас способом.
Может возникнуть вопрос: как драйвер OpenGL работает на самом низком уровне. Теперь на это снова невозможно ответить в целом, поскольку драйвер тесно связан с каким-то аппаратным обеспечением, которое может снова делать что-то, независимо от того, спроектировал его разработчик.
Поэтому вопрос должен был быть таким: «Как это в среднем выглядит за кулисами OpenGL и графической системы?». Давайте посмотрим на это снизу вверх:
На самом нижнем уровне есть графическое устройство. В настоящее время это графические процессоры, которые предоставляют набор регистров, управляющих их работой (которые именно регистры зависят от устройства), имеют некоторую программную память для шейдеров, объемную память для входных данных (вершины, текстуры и т. Д.) И канал ввода-вывода для остальных. системы, через которую он получает / отправляет потоки данных и команд.
Графический драйвер отслеживает состояние графических процессоров и все прикладные программы ресурсов, которые используют графический процессор. Также он отвечает за преобразование или любую другую обработку данных, отправляемых приложениями (преобразование текстур в формат пикселей, поддерживаемый графическим процессором, компиляция шейдеров в машинный код графического процессора). Кроме того, он предоставляет некоторый абстрактный, зависящий от драйвера интерфейс для прикладных программ.
Затем существует клиентская библиотека / драйвер OpenGL, зависящая от драйвера. В Windows он загружается через прокси-сервер через opengl32.dll, в системах Unix он находится в двух местах:
В MacOS X это «OpenGL Framework».
Именно эта часть преобразует вызовы OpenGL в том, как вы это делаете, в вызовы специфичных для драйвера функций в той части драйвера, которая описана в (2).
Наконец, собственно библиотека API OpenGL, opengl32.dll в Windows и в Unix /usr/lib/libGL.so; в основном это просто передает команды самой реализации OpenGL.
Невозможно обобщить, как происходит реальное общение:
В Unix соединение 3 <-> 4 может происходить либо через сокеты (да, может, и действительно идет по сети, если хотите), либо через общую память. В Windows интерфейсная библиотека и клиент-драйвер загружаются в адресное пространство процесса, так что не столько общение, сколько простые вызовы функций и передача переменных / указателей. В MacOS X это похоже на Windows, за исключением того, что нет разделения между интерфейсом OpenGL и клиентским драйвером (вот почему MacOS X так медленно успевает за новыми версиями OpenGL, он всегда требует полного обновления операционной системы для предоставления новых фреймворк).
Связь между 3 <-> 2 может происходить через ioctl, чтение / запись или через отображение некоторой памяти в адресное пространство процесса и конфигурирование MMU для запуска некоторого кода драйвера при каждом изменении этой памяти. Это очень похоже на любую операционную систему, поскольку вам всегда нужно пересекать границу ядра / пользовательского пространства: в конечном итоге вы выполняете некоторый системный вызов.
Связь между системой и графическим процессором осуществляется через периферийную шину и методы доступа, которые она определяет, например, PCI, AGP, PCI-E и т. Д., Которые работают через порт ввода-вывода, ввод-вывод с отображением памяти, DMA, IRQ.
источник
Вы не за горами. Ваша программа вызывает устанавливаемый клиентский драйвер (который на самом деле не является драйвером, это общая библиотека пользовательского пространства). Это будет использовать ioctl или аналогичный механизм для передачи данных драйверу ядра.
Что касается следующей части, это зависит от оборудования. Старые видеокарты имели так называемый «конвейер с фиксированной функцией». На видеокарте были выделенные области памяти для матриц и выделенное оборудование для поиска текстур, смешивания и т. Д. Видеодрайвер загружал нужные данные и флаги для каждого из этих блоков, а затем настраивал DMA для передачи данных вершин (положение , цвет, координаты текстуры и т. д.).
Новое оборудование имеет процессорные ядра («шейдеры») внутри видеокарты, которые отличаются от вашего ЦП тем, что каждое из них работает намного медленнее, но гораздо больше из них работают параллельно. Для этих видеокарт драйвер подготавливает двоичные файлы программ для работы с шейдерами графического процессора.
источник
Ваша программа не скомпилирована для какого-либо конкретного графического процессора; он просто динамически связан с библиотекой, которая будет реализовывать OpenGL. Фактическая реализация может включать отправку команд OpenGL на графический процессор, запуск резервных программных приложений, компиляцию шейдеров и их отправку на графический процессор или даже использование резервных вариантов шейдеров для команд OpenGL. Графический ландшафт довольно сложен. К счастью, связывание изолирует вас от большей части сложности драйверов, позволяя разработчикам драйверов использовать любые методы, которые они сочтут нужными.
источник
Компиляторы / компоновщики C / C ++ делают только одно: они преобразуют текстовые файлы в серию машинно-зависимых кодов операций, которые запускаются на ЦП. OpenGL и Direct3D - это просто API C / C ++; они не могут волшебным образом преобразовать ваш компилятор / компоновщик C / C ++ в компилятор / компоновщик для графического процессора.
Каждая строка кода C / C ++, которую вы пишете, будет выполняться на ЦП. Вызов OpenGL / Direct3D будет вызывать библиотеки C / C ++, статические или динамические, в зависимости от обстоятельств.
Единственное место, где может вступить в игру «программа с графическим процессором», - это если ваш код явно создает шейдеры. То есть, если вы делаете вызовы API в OpenGL / D3D, которые вызывают компиляцию и связывание шейдеров. Для этого вы (во время выполнения, а не во время компиляции C / C ++) либо генерируете, либо загружаете строки, представляющие шейдеры на каком-либо языке шейдеров. Затем вы пропускаете их через компилятор шейдера и возвращаете объект в этом API, который представляет этот шейдер. Затем вы применяете один или несколько шейдеров к конкретной команде рендеринга. Каждый из этих шагов выполняется явно по указанию вашего кода C / C ++, который, как было сказано ранее, выполняется на ЦП.
Многие языки шейдеров используют синтаксис, подобный C / C ++. Но это не делает их эквивалентными C / C ++.
источник