Deep DIP

Наверное, я уже надоел вам со своим nouveau, но в очередной раз не могу молчать..

Речь в посте пойдет про сокровенные неонки  графической продукции от NVidia.

Предыстория ( для тех кто не читал предыдущие серии ) очень простая. Как-то я проходил мимо магазина М-Видео, там на витрине стояли черные консольки от Сони. Я нащупал кредитку в кармане и решил “надо брать”. Притащил домой, поставил линукс. А потом случайно обнаружил, что RSX под линуксом отлично живет и умеет 3D. Вопреки досужему мнению что, якобы, какой-то там гипервизор не пущает и не позволяет. Графическая железка очень похожа на NV40, а специальные люди с http://nouveau.freedesktop.org/wiki/ эту железку давно уже расхачили. Но не до конца, и место для подвига остается.

Что умеет графическая 3D железка? Она умеет рисовать треугольники. Был такой интерфейс OpenGL для трехмерной графики ( отдельные люди утверждают, что он и сейчас жив ), он тоже умел рисовать треугольники. Там было много способов сделать это, но самый старый и надежный способ  – это сказать сначала glBegin(GL_TRIANGLES); потом 3 раза сказать glVertex(); потом сказать glEnd();

Так вот, в низкоуровневом интерфейсе к графической железке есть тот самый glBegin()/glEnd() блок. Такие специальные токены в командном буфере. Командный буфер – просто кусок памяти, куда складываются разные токены, а железка их асинхронно хавает. По DMA. Сказали мы glVertex4f(….), в блок положился специальный токен и данные. Получается хардверный OpenGL, дело доходит до смешного. Константы, которые задают тип примитива, разные GL_TRIANGLES, GL_QUADS, GL_LINES, GL_TRIANGLE_STRIP, GL_QUAD_STRIP – прямо и кладутся в командный буфер. Смешное продолжается тем, что у карточек от NVidia есть ограничение на число вершин внутри glBegin()/glEnd() блока, и это даже ограничение не OpenGL, а IrisGL. 

Следующий  исторически способ нарисовать треугольник в OpenGL – это сначала сказать gl*Pointer() на массивы вершинных данных, а потом много раз внутри glBegin()/glEnd() блока сказать glArrayElement. И такой способ поддерживается железкой. Есть шестнадцать что ли вершинных атрибутов, для каждого можно указать тип, число компонент, разницу в байтах между последовательными значениями атрибута, указатель на нулевой элемент.

С указателем на нулевой элемент случилась маленькая закавыка. Этот самый указатель – в абстрактном адресном пространстве видеокарточки, память нужно специально мапить. В железе есть два DMA канала, по которым оно умеет считывать вершинные данные. Для этих двух каналов нужно указать DMA объекты, которые определяют права доступа к памяти – начальный и конечный адрес куска памяти, права чтения и записи. Так вот, каждый указатель на нулевой элемент – это смещение в адресном пространстве GPU да еще битик, который говорит, использовать ли права доступа первого или второго канала вершинных данных. Почему каналов два? Cовершенно понятно - потому что есть системная и видеопамять. Зачем права доступа? Тоже совершенно понятно – чтобы в многопоточном приложении я случаем не прихватил кусок чужих данных. Какое-нить там стойкое крипто, которое считается CUDA, гы. Да и ловить выходы индексов за пределы массива тоже очень удобно. Прерыванием ( и специальные люди из Нуво расхачили даже это ).

У меня на RSX тоже есть два куска. Первый кусок - вся видеопамять целиком, на которую добрые программисты Сони дали доступ чтения/записи ( и за это я почти люблю тех безымянных героев ). Второй кусок – 20 мегабайт xdr памяти, заботливо отмапленные на графическую железку.

Ну так вот, в результате мы умеем рисовать индексированную геометрию индексами, которые складываются прямо в glBegin/glEnd блок, в командный буфер. А индексные буфера не умеем использовать. Точнее, люди из Нуво не умели.

Теперь самое время рассказать про техпроцесс расхачки. Есть специальная дампилка, которая умеет считывать командный буфер карточки после вызовов OpenGL драйвера. По просьбе специального человека мне дали возможность туда закоммитить тест с индексным буфером. Другие специальные люди запускают дампилку и посылают дампы, которые другой специальный человек выкладывает в специальном репозитарии http://people.freedesktop.org/~kmeyer/renouveau_dumps/ .

И вуаля – индексный буфер! Чтобы нарисовать индексированную геометрию надо сначала установить указатель на индексный буфер в регистр с номером 0x181c. Что надо делать дальше – вы не поверите. Внутри glBegin()/glEnd() пары надо говорить “нарисуй n примитивов начиная с такого-то смещения”. Причем число n не больше 255, это то самое прекрасное ограничение на число примитивов времен IrisGL. В результате вызовы DIP разбиваются на кусочки не больше 255 индексов, в таком виде и складываются в командный буфер. Внутри glBegin()/glEnd() пары. Технология ниппель. Через тернии к звездам.

На этом небольшом примере превосходно видно ( другой отличный пример – отжиг Семена про комбайнеры http://sim0nsays.livejournal.com/18587.html?nc=43) как развивается железо и интерфейсы. И как внутри современной железки притаились архитектурные решения 10, 15, 20 летней давности. Притаились, прикрылись слоями абстракции, археологическими наслоениями и множественными воркэраундами.

  • Vespertinus

    Читаю как роман :)
    Пеши еще!

    P.S. думал что OpenGL еще жив :(

  • dDIMA

    Ну а как ты думал – что оно там на самом низком уровне имеет аппаратную реализацию pD3D9Device->QueryInterface()? ;)

  • Sergei_am

    Петр, регулярно меняй линии метро и час ухода с работъ – а то к тебе придут и спросят про RSX и Курильские острова…
    А так – респект!

  • IronPeter

    Vespertinus, оно постепенно. Сейчас самое интересное, что осталось – это настройки L3 кеширования и компрессии памяти. Но ковырять такое сложно. Оно и на PC не отковырнуто по большому счету. Людяи из Нуво не интересно, чтобы быстро. Им интересно, чтобы хоть как-то.

    Sergei_am, а также крайне внимательно осматриваю туалет. А то вдруг притаившийся нинзя снизу пикой ткнет!

  • IronPeter

    Вот какие у нас ништяки есть!

    http://youtube.com/watch?v=vuRLsB2q7QY

  • http://www.dreamwoods.ru Black Angel

    IronPeter, как я понимаю сейчас всё общение с RSX происходит через командный буфер? А как получить какие-либо данные от карточки? Например, как узнать где в памяти у нас бэк-буфер? Карточка его просто отмапит в системную память или вернёт указатель на область в видео памяти? Мне собственно интересно, как происходит locking RSX ресурсов, там мапинг в системку или прямая запись в видео?

  • IronPeter

    Сейчас общение происходит через командный буфер и через вызов функции гипервизора, который в принципе умеет то же что и я, за небольшой добавкой – он умеет писать во “внешние” регистры видеокарточки. Там впрочем живет очень мало функциональности.
    У железки нет понятия “бэк буфер”, у нее есть понятие области сканирования, откуда RAMDAC берет картинку. Если у тебя экран 1024 x 768 x RGBA8888 и ты хочешь сделать двойную буферизацию – ты в цикле меняешь смещение с нуля до 0×300000, переключая области видимости. У железки нет понятия “фронт буфер”. У нее есть понятие активной rendering texture, у которой есть width, height, pitch, format, которая бывает swizzled или linear. Все указатели ты ставишь сам. Можешь установить область сканирования на z surface. В отладочных целях.
    С маппингом весело. Конечно вся видеопамять ( по крайней мере нижние 254 мегабайта, выше – служебная информация ) может быть отмаплена на CPU. DMA доступ на запись туда достаточно быстр, гигабайты в секунду. Но! Данные там могут лежать совсем не такие, какими их видит железка. Есть два уровня, на которых происходит интерпретация данных в памяти.
    Первый – логический уровень. Мы говорим “а вот по такому смещению лежит swizzled текстура”. Если будем делать DMA – то должны аккуратно перетасовать данные в текстурке. Тайликами. Внутренний формат такой – что поделаешь.
    Второй – уровень контроллера памяти. Тут совсем весело ( и достаточно тонко, впрочем ребята из Нуво расковыряли ). На железке есть что ли 16 каналов, на каждый из которых может быть отмаплен свой кусок видеопамяти. С этим куском памяти связывается кусок L3 кеша. Становится быстрее. И память выглядит по-другому. Она тайлуется/сжимается на уровне контроллера памяти. Разные агенты ( RAMDAC сканнер, GPU, CPU ) могут воспринимать эту память по-разному. Это впрочем актуально только для экранных буферов. Обычные текстуры, вершинные и индексные буфера, пиксельные шейдеры – они обычно лежат в линейной памяти. В них можно делать DMA записи процессором. А еще лучше писать на SPU.
    Никакого lock’a нету. Можешь писать в любые данные в любое время. В командный buffer ( доступ кешируется вперед, будь аккуратен! ). В текстуры ( есть текстурный кеш ). В пиксельные шейдеры ( команды тоже кешируются, шейдер может перегрузиться посередине рисования треугольника ). В вершинные и индексные буфера ( имеется post и pre tnl кеши ). Некоторые кеши переживают полную инициализацию железки. Для кешей есть команды для сбрасывания – надо вызывать. Иногда надо вызывать два раза – один раз сбрасывает не полностью.
    Еще есть XDR память. Сказочно быстрая на доступ SPU. И просто быстрая на доступ GPU. Если посмотреть в открытую документацию, что у GPU два равноценных контроллера памяти, один из которых смотрит в видеопамять, а второй в системную – то становится понятно, что весь вершинный процессинг надо делать SPU. И использовать только динамическую геометрию. Желательно с back face cull на SPU, чтобы меньше читать и писать.
    Синхронизация GPU с CPU жжот. Можно приказать железке – “а исполни мне командный буфер, вот начало и конец”. Но такой метод просасывает. Можешь сказать тыщу раз в секунду такое. Грамотно сказать железке – “а исполни мне командный буфер, вот начало и конец плюс один” – а самом в конце буфера поставить прыжок на начало. Командный буфер зациклится и будет исполняться вечно. Вечно не надо – надо один раз. Для этого можно вставить команду в командный буфер – “а ну-ка, железка, напиши мне по такому-то адресу такой-то dword”. Если адрес – это адрес в командном буфере а dword – это прыжок на тот самый адрес – то железка остановится на семафоре. Семафор можно переписать CPU. После того, как 6 SPU заполнят командный буфер вперед. А также заполнят вершинные буфера и индексные буфера динамической геометрией. SPU синхронизируются с CPU через mailboxы. Параллельно идет vsync. И надо или успевать рисовать все за кадр – или рисовать с двойной буферизацией. Синхронно с vsync переключая RAMDAC сканнер.
    В реальности все веселее – я знаю верхи. Выглядит как айсберг.