За кулисами графики 90-х: Раскрываем секреты работы 3D видеокарты S3 ViRGE
Автор monobogdan:
Друзья! Многие ли из вас застали такую легендарную видеокарту, как S3 ViRGE? Когда-то этот GPU стоял чуть ли не в каждом втором офисном компьютере: благодаря дешевизне и заявленной поддержке 3D-ускорения, эту видеокарту просто сметали с полок магазинов. Далеко не все могли себе позволить ATI Rage, Riva TNT и уж тем более 3dfx Voodoo и очень разочаровывались в свежекупленной видеокарте, когда пытались поиграть в новомодные игры тех лет. На момент написания статьи, в сети слишком мало материала о том, как работали видеокарты 90-х «под капотом», однако мне удалось найти даташит на видеочип, SDK для программирования 3D-графики специально под него и некоторую документацию. Я решил исправить это недоразумение и начать развивать отдельную рубрику о работе старых видеочипов: начиная от S3 ViRGE и заканчивая GPU PS2 и PSP. Сегодня мы с вами: вспомним о S3 ViRGE, узнаем о том, как работали видеокарты в 90-х годах, затронем 2D и 3D режим и почему они тесно связаны между собой, посмотрим на проприетарное графическое API S3 ViRGE и раскроем причину, почему же этот GPU был таким медленным!
В начале 90-х годов 3D-графика на обычных домашних компьютерах была редкостью. Профессиональные GPU применялись только на дорогущих графических станциях, которые использовались в кинематографе или различных симуляциях, а также на дорогих японских игровых автоматах. У простого обывателя не было доступа к аппаратным средствам рендеринга 3D-графики.
Однако это не значит, что 3D-графики не было вообще. Прогресс развития домашних процессоров шёл семимильными шагами и гиганты рынка —Intel,AMDи в некоторой степени Cyrix, выпускали всё новые и новые процессоры с повышенными тактовыми частотами, а ближе к середине 90-х — и с SIMD (MMX). Поскольку многие техники для отрисовки трехмерного изображения были разработаны ещё в 60-х — 70-х годах, игроделы к началу-середине 90-х во всю использовали некоторые наработанные техники из кинематографа для растеризации 3D-графики прямо на процессоре — так называемыйсофтварный рендеринг.
Одной из самых известных техник 90-х являлась 2.5D графика с использованием рейкастинга — когда картинка на экране выглядит как трёхмерная, однако по факту весь мир представлен в виде 2D-координат, а эффект «пола и потолка» был как бы фейковым. Принцип его работы довольно прост: от глаз игрока для каждого горизонтального пикселя (т. е. при разрешении 240х320, у нас будет 240 проходов) пускаются «лучи» и ищется пересечение с ближайшей стеной относительно угла обзор из глаз игрока. Из этого пересечения берется дистанция до этой стены (на основе дистанции и угла считается «высота» данной строчки стены) и считается какую строчку текстуры необходимо вывести в этой точке. Одними из первых игр с применением этой технологии стал Hovertank и Wolfenstein 3D, а технология применялась практически до конца 90-х. Одной из самых лучших реализаций рейкастинга — движок Duke Nukem 3D, Build Engine, написанный Кеном Сильверманом.
Однако не одним 2.5D мы были едины. Шли годы, в СНГ многие люди продолжали наслаждаться 8-битными и 16-битными играми на клонахNESи SMD. У некоторых уже появлялась PS1, которая позволяла играть в игры с довольно хорошей 3D-графикой, однако на ПК 3D-игры были доступны не всем. Но в 1996 году выходитQuake— новейший шутер от первого лица от id Software с настоящей, трушной 3D-графикой и переворачивает всю индустриюFPSс ног на голову. Посудите сами: Джон Кармак умудрился реализовать достаточно быстрый софтварный рендерер, который мог вполне сносно работать на Pentium 75Мгц в разрешении 320x240. А ведь помимо отрисовки кадра, игре нужно было просчитывать логику монстров (довольно примитивную, к слову), обрабатывать столкновения, просчитывать видимую геометрию с помощью BSP-дерева и обрабатывать клиент-серверную логику самой игры. Это была самая настоящая революция в мире 3D игр на ПК.
В 1997 году, id Software выпустили glQuake — порт Quake с софтрендера на OpenGL, плюс своеобразную прослойку для совместимости с API 3dfx Glide (на видеокартах Voodoo) и подмножества OpenGL, используемым в игре. Порт на OpenGL позволял разгрузить ЦПУ, перенеся всю отрисовку графики с процессора на 3D-ускоритель. Сам по себе, OGL как графическое API, представлял из себя лишь набор спецификаций, который мог быть реализован как в программном виде, так и в аппаратном производителем видеокарты (на примере Windows — OpenGL32.dll это программная реализация, которая при необходимости обращается к atioglxx.dll/nvoglvxx.dll — аппаратной реализации OpenGL от вендора видеочипа). Однако, OpenGL корнями уходил именно в отрисовку промышленной графики, а DirectX всё ещё находился в зачаточной форме, из-за чего многие производители разрабатывали собственное графическое API: из известных мне, могу подчеркнуть ATI CIF (C Interface), 3dfx Glide и проприетарное SDK S3 ViRGE. Некоторые вендоры поддерживали целые игровые движки — например, BRender и RenderWare.
Отдельные 3D-акселлераторы потихоньку начали завоевывать сердца геймеров и создавать новый сегмент рынка. Серьезные видеокарты от известных производителей, такие как 3dfx Voodoo, ATI Rage и Riva TNT стоили достаточно дорого и многим были не по карману. Зато существовало множество видеокарт с 3D-ускорителями от других производителей, про некоторые из них вы могли даже не слышать: отдельные дискретные видеокарты Intel (i740), видеокарты от производителя чипсетов SiS и конечно же, видеокарты от S3 с сериями ViRGE и Savage. Видеочипы от Intel и SiS делали упор на D3D 7.
S3 ViRGE была весьма неплохой видеокартой с точки зрения 2D-ускорения. Сейчас 2D принято считать частным случаем 3D (по факту, 2D-спрайты — это 3D-квады, состоящие из двух треугольников), однако в то время для работы с памятью видеокарты и аппаратного ускорения некоторых операций, таких как блиттинг (BitBlt) существовало отдельное графическое API — DirectDraw. С этим у ViRGE было всё хорошо — он поддерживал довольно высокое разрешение экрана (при желании, объём видеопамяти можно было нарастить и установить разрешение ещё выше) и умел ускорять часть операций как DDraw, так и GDI.
Однако, ViRGE разочаровывал многих геймеров 90х своей производительностью в 3D-графике. На коробке с бюджетной видеокартой красовались красивые надписи о 3D-графике следующего поколения, а на фотографии можно было увидеть некую игру про мехов с невиданной графикой!
По факту, ViRGE подходил для 3D-игр не особо хорошо. Конечно в те годы никто особо не плевался от FPS и при желании, игру могли пройти и в 15, и в 20 FPS. Однако производительность софтварного рендерера иногда была даже выше, чем у растеризатора ViRGE, а игры должны были быть специально адаптированы под неё (т. е. портированы для использования S3DTK). Тайтлов с адаптацией по этот GPU было немало: как минимум, Tomb Raider и MechWarrior 2 (который шел в комплекте с игрой). Польские ребята из известной многим Techland даже написали прослойку S3D -> OpenGL, позволявшей запускать Quake на ViRGE. Производительность была не ахти…
Видеокарт от S3 нашлись и у меня, причём сразу несколько — ViRGE в PCI-исполнении и Trio в AGP-исполнении! Иногда я их использую для проверки старых материнских плат, которых у меня не так уж и много — рабочих на PGA370 и ниже у меня совсем нет. Однако остаётся вопрос, как эти видеокарты работали под капотом? Давайте узнаем!
Исторически сложилось так, что 3D и 2D акселераторы могли быть отдельными и формально не зависящими друг от друга устройствами. Архитектура IBM PC в зависимости от «поколения», предполагала сразу несколько типов видеоадаптеров, которые были стандартизированы под определенный тип мониторов. Один из таких адаптеров, VGA, стал стандартом на долгие годы, в то время как два других использовались в совсем ранних машинах. Их ключевое отличие было в организации видеопамяти и цветности — CGA/EGA предполагал разбитие пространства экрана на т. н. битплейны (один байт содержал информацию о нескольких пикселях и если не ошибаюсь, для сохранения адресного пространства сегменты экрана необходимо было переключать аля банки памяти) и былпалитровым, в то время как VGA предполагал как палитровый режим, так и полноценный RGB и мог отразить весь фреймбуфер в линейную область адресного пространства. Кроме того, долгое время VGA использовался для обозначения разрешения дисплея: QVGA — половина VGA (320x240), VGA (640x480), широкоформатный WVGA (800x480) и т. п.
Другой особенностью была полная (насколько мне известно) обратная совместимостью друг с другом. Например, GeForce 7xx, как один из последних GPU, который поддерживал Legacy BIOS, теоретически вполне мог работать и с EGA режимами, и с CGA через соответствующие видеорежимы int 10h!
3D-режимы же никак не были стандартизированы и каждый производитель реализовывал работу с ними по разному — как уже говорилось ранее, кто-то реализовывал поддержку совсем молодого D3D и OpenGL (насколько мне известно, лучше всего с OpenGL было у NVidia. Остальные вендоры поддерживали OGL, но были свои болячки — у ATI они тянулись чуть ли не до середины-конца нулевых), а кто-то делал собственное графическое API и работал с видеочипом почти напрямую. Первые 3D GPU использовали шину PCI, которую почти сразу заменила более скоростная, но интерфейсно и софтварно почти идентичная шина AGP, а затем уже появился PCI-E, который оставался тем же PCI в софтовом плане, но был дифференциальным и последовательным, а не параллельным как интерфейсы-предшественники.
Дабы понять, как работают первые видеокарты, необходимо узнать о том, как происходит процесс отрисовки 3D-графики в общем случае. В мире программирования графики это называетсяконвейероми состоит он как минимум из нескольких этапов:
Установка состояний: Программа задаёт источники света на сцене, параметры Z-буфера и Stencil-буфера, какую текстуру(ы) следует наложить на рисуемую геометрию и с какой фильтрацией, какой тип аппаратного сглаживания использовать и т. п.
Ранее, каждый стейт необходимо было устанавливать отдельно, при необходимости — для каждого DrawCall'а. После подготовки состояния, программа вызывает соответствующую функцию отрисовки.
Обработка геометрии: Геометрия не поступает в растеризатор «как есть», в мировых координатах. Растеризатор оперирует вершинами в нормализованныхClip Spaceкоординатах — обычно, это [-1, -1… 1, 1], где 0.5 — центр экрана по каждой оси. Именно поэтому сначала необходимо провести этап трансформации геометрии для перевода из некой глобальной системы координат (которая может выражаться в метрах или, например, в пикселях) в Clip Space. Для этого чаще всего координаты (корректнее — трансформации) представляются в виде трех перемноженных матриц — model (мировые координаты геометрии), view (положение «глаз» в мире, или по простому камера. Умножая model на неё, мы получаем координаты объекта в пространстве камеры) и projection (матрица проекции, которая преобразовывает координаты из пространства глаз в тот самый Clip Space. Именно в этой матрице задается FOV для перспективной проекции и виртуальные размеры экрана для ортографической матрицы). После этого, координаты каждой вершины трансформируются полученной ModelViewProjection матрицей и получается финальная позиция для Clip Space. Звучит как сложный учебник матану, по факту всё очень просто. :)
Детали реализации низкоуровневого матана, в том числе перемножения матриц и построения матриц трансформаций и проекции знать желательно, но необязательно. Сейчас этим занимаются очень удобные математические библиотеки — например, glm, dxmath или d3dx.
Кроме того, ранее именно на вершинном этапе считалось освещение для уровня. В некоторых видеочипах была возможность аппаратного расчета источников света, в некоторых — только программная на ЦПУ.
На видеокартах тех лет, в том числе и S3 Virge, трансформацией вершин занимался центральный процессор, из-за чего было довольно серьёзное ограничение на количество вызовов отрисовки и число треугольников в одной модели. Видеокарты с аппаратной, но всё ещё не программируемой трансформацией вершин появились лишь к GeForce 2 — называлась эта технология T&L (Transform and Lightning) и её преимущество было в том, что у видеокарты были специализированные векторные сопроцессоры, способны быстро пересчитывать векторные операции (а у ЦПУ, в свою очередь, развивались SIMD наборы инструкций, позволяющие выполнять несколько операций над float одновременно). В некоторых случаях, был даже отдельный программируемый векторный сопроцессор как, например, в PlayStation 2, что позволяло реализовать вершинные шейдеры ещё в 2000 году! На современных видеокартах, этапом трансформации в самом простом случае управляют вершинные шейдеры. Помимо этого, есть возможность создания геометрии «на лету» с использованием тесселяции и геометрических шейдеров, а совсем недавно появились Mesh-шейдеры, которые объединили несколько подэтапов конвейера в один.
Растеризация: Сам процесс отрисовки геометрии на дисплей с данными, полученными с прошлого этапа. Именно на этом этапе треугольники (или иные геометрические примитивы) закрашиваются определенным цветом или на них накладывается текстура. В процессе растеризации есть такое понятие, как интерполятор — специальный модуль, который интерполирует несколько значений в барицентрических координатах растеризуемого треугольника, дабы текстурный юнит мог наложить определенный участок текстуры на фрагмент треугольника.
В современных видеокартах этот этап конвейера программируется пиксельными (или фрагментными) шейдерами. В старых видеочипах (исключение — вроде-бы частично программируемый GPU Nintendo 64, поправьте в комментариях, если не прав) этот процесс строго определен в каждом GPU и не программировался. Именно поэтому такой подход к рисованию графики назывался Fixed function pipeline. Были ещё комбайнеры, но они появились заметно позже — когда в видеокартах появилось уже несколько текстурных юнитов, способных смешивать несколько текстур одновременно.
Делая вывод, мы можем понять, что S3 Virge и другие видеочипы были устройствами, которые умели рисовать лишь тот уровень графики, который был заложен производителем с завода. Такой подход называется фиксированным конвейером — Fixed Function Pipeline. Сейчас разработчики видеочипов перешли с фиксированного конвейера на программируемый (шейдерный). Уже начиная с SM2.0-SM3.0, на современных видеокартах появилась возможность создавать крутое и достаточно сложное освещение и различные эффекты, которые стали неотъемлемыми в современных играх.
Кроме того, важно понимать, что в видеопамяти ранних видеочипов хранился только фреймбуфер, а немного позже — текстуры, именно поэтому VRAM в старой документации называют «текстурной памятью». Вообще, некоторые нюансы первых версий OpenGL тянуться именно из особенностей работы первых видеокарт. Вспомнить хотя бы первые функции для старта отрисовки геометрии и загрузке вершин на видеокарту — это были связки glBegin/glVertex/glEnd:
Даже сам glBegin/glVertex/glEnd появились не спроста. Геометрию на видеокарте начали хранить только в начале нулевых (и то не везде — привет встройкам Intel и S3).
Но перейдем к особенностям работы S3 ViRGE. Даташит лежит в свободном доступе, благодаря чему мы можем более подробно ознакомиться с характеристиками этого видеочипа и о том, как он работал под капотом.
В основе у нас лежит 64-х битное ядро, которое могло обрабатывать как 2D-графику с аппаратным ускорением, так и 3D-графику. Ядро работало на частоте 135МГц с встроенным RAMDAC (модуль, отвечающий за вывод картинки на аналоговые разъемы — VGA и DVI, однако выводом на TV-тюльпаны занимался отдельный чип TV-энкодер). Современные видеочипы перешагнули планку 1ГГц, однако сравнение исключительно по частоте некорректны — архитектуры очень сильно отличаются. Помимо этого, видеочип умел декодировать видео с интерполяцией и аппаратно «помогать» процессору с скейлингом видео (например, когда вы разворачиваете плеер на весь экран) и даже рендерить видео в текстуру (что позволяло реализовать, например, телевизоры в играх)!
3D движок поддерживал следующие возможности:
- Затенение по Гур.о
- Маппинг текстур с перспективной коррекцией и билинейной/трилинейной фильтрацией, а также мипмаппингом.
- Depth-буфер, сэмплинг тумана и поддержка альфа-блендинга (прозрачной геометрии).
Помимо этого, у чипа не было встроенной памяти — к нему необходимо было подключать внешнюю DRAM-память 2/4/8Мб. От её количества зависело максимально-поддерживаемое разрешение экрана. Текстуры при необходимости хранились в ОЗУ.
Поддерживаемые разрешения экрана:
Для DirectDraw и ускорения 2D-графики в Windows была реализация аппаратного BitBLT — копирования пикселей в точку на экране. Она поддерживала все режимы, которые были в реализации этой функции в Windows — от монохромных, до 24-х битных. Без альфа-блендинга, само собой. Но тут нет ничего необычного — многие видеочипы тех лет предоставляли простое 2D-ускорение.
Интереснее реализация отрисовки 3D-графики. Каждый треугольник описывался 3-мя регистрами на каждый параметр — координата X, Y для каждой точки, текстурные координаты и т. п. Всего для отрисовки одного треугольника могло потребоваться до 43 регистров! Весьма немало. И именно из-за этого в свое время появились glBegin/glVertex/glEnd!
Параметры сэмплера (текстурного юнита) задавались регистрами, которые определяли формат пикселя текстуры и сам тип фильтрации. Как я уже говорил выше — поддерживалась билинейная и трилинейная фильтрация и проприетарный формат сжатия текстур, который стал стандартом: S3TC или DXT.
Для программирования S3 ViRGE было разработано собственное C SDK — S3DTK, которое состояло из сэмплов и заголовочных файлов для общения с GAPI видеочипа (или видеочипом напрямую, если игра предназначена для DOS). При этом вполне не исключено, что GAPI для Windows работало с видеокартой напрямую, предоставляя PCI-драйвер лишь как прослойку для обмена данными. Поскольку это не D3D, для игр с поддержкой видеоускорения требовалось качать специфические версии. Некоторые игры (как Quake 2) поддерживали мультирендер, но не поддерживали S3 ViRGE.
Весь графический API помещался в один заголовочный файл. API было не простым, а очень простым и понятным — думаю, даже разработчикам-новичкам было легко начать программировать под ViRGE!
Формат вершин был фиксированным и зависел от того, как вы рисовали геометрию на экране:
GAPI поддерживало различные типы треугольных списков, а также точки (POINT для спрайтов и систем частиц) и линии:
Фактическое API для рисования умещалось в 9 функций и ещё несколько функций для инициализации библиотеки, преобразования адресного пространства и работы с Windows.
Для работы с состоянием видеочипа служили две функции — SetState и GetState. Именно они отвечали за то, как рисовалась геометрия на экране:
А для фактического рисования примитивов служили функции TriangleSet и TriangleSetEx! Да, это альтернатива DrawPrimitives/DrawArrays в современных GAPI. Никаких индексов тогда ещё не использовалось! Функции принимали указатель на массив вершин и их количество, а также на тип рисуемой геометрии (треугольники, линии и т. п.). В Ex версии, можно было «пачкой» установить стейты параллельно с рисованием — такой подход используется в DX10+ API — стейты тоже задаются исключительно «пачками», только теперь они поделены на подгруппы.
Для 2D-рисования были свои, отдельные функции — для блиттинга. Поддерживался ColorKey/хромакей — прозрачным считался определенный цвет, переданный как параметр функции
Основной причиной медлительности S3 ViRGE был низкий филлрейт. При отрисовке примитивов, которые занимают большое пространство экрана, FPS резко просаживался даже с примитивными кубиками и пирамидками. Однако, если не насаживаться на филлрейт и делать что-то типа 2D-поля и 3D-танчиков, то производительность оставалась вполне приемлимой.
История S3 закончилась поглощением компанией VIA. После этого, компания разрабатывала интегрированную графику специально для чипсетов VIA, а материнские платы на этих чипах пользовались довольно высоким спросом. Поэтому нередко взяв старый бюджетный ноутбук, года эдак 2005, можно найти в нём VIA Chrome — наследника легендарного S3 Savage! Проблемы у такого подхода тоже были — из-за наследия из конца 90х, ранние Chrome по сути поддерживали только D3D 7.0 и OpenGL ~1.4. Несколько позже, в 2009 году, компания выпустила S3 Chrome 540 GTX — одну из последних видеокарт на собственной архитектуре. Этот видеочип был достаточно современным и поддерживал DX10.1, OpenGL 3.0. Интересно, реально ли найти эту видеокарту сейчас?
По итогу мы можем сделать вывод, что первые 3D-ускорители были относительно простыми устройствами «под капотом» и их можно было программировать чуть ли не «напрямую». Многие старые видеочипы получили свои локальные прозвища и стали легендарными, однако их архитектура и принцип работы оставались тайной. По крайней мере, в рунете точно.
Насколько я понимаю, неравнодушные инженеры после закрытия 3dfx и слияния S3 с VIA решили «слить» даташиты в сеть, за что им большое спасибо! Ведь теперь мы имеем возможность посмотреть на принцип работы таких устройств сами!