Это вторая статья из серии «3D-карты конца 90-х, на которых работал Quake». В первой части мы рассмотрели Rendition Vérité 1000 конца 1996 года и специальный порт игры для неё под названием vQuake. Rendition удалось победить всех на рынке Quake. На короткий промежуток времени она оставалась единственной платой, способной запускать блокбастер id Software с аппаратным ускорением.
Но всё изменилось в январе 1997 года, когда id Software выпустила новую версию Quake под названием GLQuake. Так как порт был создан с помощью miniGL (подмножества стандарта OpenGL 1.1), любой производитель аппаратных ускорителей мог написать драйвера miniGL и принять участие в гонке 3D-карт. С этого момента возможность конкуренции была открыта каждому. Цель заключалась в генерации как можно большего количества кадров в секунду. Наградой была слава и деньги покупателей. Вкратце изучив историю, можно понять, что два авторитета того времени без сомнений считали царями горы двух производителей.
Пока что в этом нет никаких сомнений: миром Quake правит Voodoo. А так как Quake правит миром игр, то покупка 3Dfx Voodoo почти неизбежна для геймеров.
— Tom's Hardware, 30 ноября 1997 года
3DFX Voodoo 1
— Эталон, по которому меряются все остальные карты.
— Файл .plan Джона Кармака. 12 февраля 1998 года[2]
Только взглянув на спецификации[3], в которых заявлялось о скорости заполнения в 50 мегапикселей/с, я сразу же захотел изучить эту карту и понять, что же сделала 3dfx, чтобы создать настолько мощный продукт.
3dfx Interactive
Росс Смит, Скотт Селлерс и Гари Таролли познакомились, когда вместе работали в SGI[4]. Поработав немного в Pellucid, где они пытались продавать платы IrisVision для PC (в 1994 году такие платы стоили по 4000 долларов за штуку), коллеги основали собственную компанию при поддержке компании Горди Кэмпбелла TechFarm. 3dfx Interactive, головной офис которой находился в Сан-Хосе (Калифорния), была основана в 1994 году.
Изначально компания намеревалась создавать мощные аппаратные системы для аркадных автоматов, но изменила свой курс, занявшись разработкой плат для PC. На то было три причины.
- Достаточно низкая цена ОЗУ.
- Начиная с FastPage RAM, а затем и EDO RAM, задержки в ОЗУ снизились на 30%. Теперь память могла работать с частотой до 50 МГц.
- Игры в 3D (или в псевдо-3D) становились всё более и более популярными. Успех таких игр, как DOOM, Descent и Wing Commander III показал, что скоро должен возникнуть рынок для 3D-ускорителей.
Основатели компании поняли, что им нужно создать нечто мощное, предназначенное для игр и с розничной ценой в пределах 300-400 долларов. В 1996 году компания объявила о создании архитектуры SST1 (названной в честь основателей — Sellers-Smith-Tarolli-1), которую вскоре лицензировали несколько OEM, такие как Diamond, Canopus, Innovision и ColorMAX. Для их творения придумали маркетинговое название «Voodoo1», подчёркивающее его волшебную производительность.
Как и в случае с V1000, при создании карт производители могли менять только выбранный тип ОЗУ (EDO или DRAM), цвет плат и физическое расположение чипов. Почти всё остальное было стандартизировано.
Diamond Monster 3D, изображение взято с vgamuseum.info.
Canopus Pure3D, изображение взято с vgamuseum.info.
BIOSTAR Venus 3D, изображение взято с vgamuseum.info.
ORCHID Righteous 3D, изображение взято с vgamuseum.info.
При взгляде на плату SST1 поражало то, насколько она отличается от своих конкурентов — Rendition Verite 1000 и NVidia NV1.
Во-первых, 3dfx сделала смелый шаг, отказавшись от поддержки 2D-рендеринга. У Voodoo1 было два VGA-порта, один использовался как выход, а другой — как вход. Карта разрабатывалась как дополнение, она брала в качестве входных данных выходные из двухмерной VGA-карты, уже установленной в компьютере. Когда пользователь работал с операционной системой (DOS или Windows), то Voodoo1 просто перенаправляла сигнал со своего VGA-входа на VGA-выход. При переключении в 3D-режим Voodoo1 брала контроль над VGA-выходом и игнорировала сигнал со своего VGA-входа. У некоторых плат был механический переключатель, производивший щелчок при переключении между режимами 2D и 3D. Такое решение означало, что карту можно использовать только для полноэкранного рендеринга, «оконного» режима не было.
Вторым примечательным аспектом SST1 было то, что она была сделана не из одного ЦП, а из двух непрограммируемых ASIC (Application-Specific Integrated Circuit, интегральных схем специального назначения). Если пройти по дорожкам шин, то можно увидеть, что у каждого из чипов, помеченных как «TMU» и «FBI», есть собственная ОЗУ. На карте с памятью 4 мебибайта ОЗУ было разделено поровну: 2 мебибайта TMU для хранения текстур и 2 мебибайта FBI для хранения буфера цветов и z-буфера, при этом значения хранились соответственно как 16-битные RGBA и 16-битные integer/half-float. Карта с памятью 4 мебибайта поддерживала разрешение до 640x480 (2 буфера цветов (640x480x2) для двойной буферизации + 1 буфер глубин (640x480x2) = 1 843 200). Более поздние модели с 4 мебибайтами ОЗУ FBI позволяли использовать разрешение до 800x600 (2x800x600x2 + 800x600x2 = 2 880 000).
Конвейер рендеринга SST1
Конвейер в спецификациях подробно не описан. Согласно моей интерпретации, жизнь треугольника состояла из пяти этапов.
- Треугольник создаётся и преобразуется в основном процессоре компьютера (обычно Pentium). Такие операции включают в себя умножение на матрицу пространства модели/проецирования, усечение, повершинное перспективное деление, отсечение однородных координат и преобразования поля видимости. В конце этого процесса остаются только видимые треугольники пространства экрана (из-за отсечения один треугольник может оказаться двумя).
- Командой triangleCMD треугольники передаются по шине PCI в интерфейс буфера кадров (Frame Buffer Interface, FBI). Они преобразуются в запросы растровых строк, создаваемые Texture Mapping Unit. Для каждого элемента растровой строки (называемого фрагментом) TMU выполняет до четырёх запросов поиска на пиксель, если разработчику требуется билинейная фильтрация. Пофрагментное перспективное деление тоже выполняется в TMU.
- TMU отправляет фрагменты в FBI в виде текстурированного 16-битного значения цвета RGBA + 16-битного z-значения.
- FBI выполняет тесты фрагментов в z-буфере, сравнивая их с выделенной ОЗУ, хранящей значения RGBA и z-значения буфера кадра.
- Наконец, к фрагменту применяется освещение на основании его атрибута цвета и поиска в таблице тумана из 64 элементов. Если требуется смешение, то FBI комбинирует получаемый фрагмент с тем, что уже находится в буфере цвета.
Интересный факт: если вы любитель 3D, то, вероятно, знаете о коде быстрого обратного квадратного корня, получившем известность благодаря исходному коду Quake 3:
float Q_rsqrt(float number) {
long i;
float x2, y;
const float threehalfs = 1.5f;
x2 = number * 0.5f;
y = number;
i = * (long*) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
return y;
}
В поисках [5] первоисточника Q_rsqrt Rys for Software связался c Гари Таролли, который сообщил, что использовал этот код, ещё работая в SGI. Так что справедливо будет предположить, что он применялся и в конвейере SST1.
Что-то не совпадает
Познакомившись с конвейером и зная, что каждый компонент (TMU, FBI, EDO RAM) работает с частотой 50 МГц, мы можем понять, что в расчётах есть какая-то ошибка и карта не может достичь скорости в 50 мегапикселей/с. Здесь нужно было решить две проблемы.
Во-первых, устройство TMU должно было считывать четыре тексела для выполнения билинейной фильтрации текстур. Это означает, что необходимы четыре цикла обращения к ОЗУ, что привело бы к нехватке данных для TMU и скорости заполнения 50/4 = 12,5 мегапикселя/с.
Есть и другое узкое место на уровне FBI. Если включена проверка z-буфера, то перед записью или отбрасыванием входящее z-значение фрагмента должно сравниваться с тем, что уже находится в z-буфере. Если проверка прошла успешно, то значение нужно записать. Это две операции с ОЗУ, которые привели к снижению скорости заполнения вдвое: 50/2= 25 мегапикселей/с.
Четырёхстороннее чередование TMU
Решение проблемы четырёх сэмплов на этапе TMU упомянуто в спецификации SST1.
В пути данных памяти текстур реализовано полное чередование, что позволяет отдельному банку выполнять доступ к данным вне зависимости от адреса, использованного для доступа к данным в других банках.
— Спецификация SST1
В ней не указано, используется ли в шине мультиплексирование адресов, или общие шины данных и адресов. Проще разобраться, если нарисовать их без мультиплексирования и без разделения.
Вне зависимости от подробностей, архитектура TMU позволяла за такт получать 4 x 16-битных текселов. Если входные данные поступают с правильной частотой, то TMU может выполнять пофрагментное деление на w, а затем генерировать z-значение фрагмента (16-битное) и цвет фрагмента (16-битный), которые передавались в FBI.
Двухстороннее чередование FBI
Решение проблемы двух операций доступа к ОЗУ на этапе FBI тоже не описано в спецификации. Однако в документе упоминается скорость заполнения 100 мегапикселей/с, достигаемая с помощью glClear благодаря возможности записывать два пикселя за такт, и это даёт нам понять, что здесь использовалось двухстороннее чередование.
FBI читал и записывал по два пикселя за раз (2 x 1 пикселя, состоящих из 16-битного цвета и 16-битный z = 64 бита). Для этого 21-битный адрес генерирует два 20-битных, в которых наименее значимый бит отбрасывается для чтения/записи двух идущих по порядку пикселей. Так как алгоритм растровых линий, необходимый для записи/чтения в горизонтальных линиях, двигается слева направо, чтение двух порядковых пикселей за раз срабатывало очень хорошо.
64-битная шина TMU->FBI
Последний фрагмент головоломки — это 64-битная шина FBI-TMU. О ней в спецификации практически ничего не написано, но в её поведении можно разобраться по тем данным, которые потребляет FBI. Так как FBI обрабатывает по два пикселя за раз, разумно предположить, что TMU не отправляет текселы как можно быстрее, а объединяет их по два как два 16-битных цвета + 16-битное z-значение.
Программирование Voodoo1
На самом низком уровне программирование Voodoo1 выполнялось с помощью регистров с отображением в памяти. API состоит из удивительно малого количества команд, их всего пять: TRIANGLECMD (с фиксированной запятой), FTRIANGLECMD (с плавающей запятой), NOPCMD (no-op), FASTFILLCMD (очистка буфера) и SWAPBUFFERCMD, связанная с загрузкой регистров данных для настройки смешения, z-теста, загрузки цветов тумана и многого другого. Загрузка текстур во VRAM выполнялась через 8 мебибайт write-only ОЗУ PCI с отображением в память.
(Настоящее) программирование Voodoo1
Разработчики программировали Voodoo1 через Glide API[6]. Логика дизайна API была вдохновлена IRIS GL/OpenGL, в нём использовался конечный автомат и префиксы для всего (только вместо «gl» применялся «gr», и программистам нужно было управлять VRAM, как это сейчас делается в Vulkan.)
#include <glide.h>
void main( void ) {
GrHwConfiguration hwconfig;
grGlideInit(void);
grSstSelect( 0 );
grSstQueryHardware(&hwconfig);
grSstSelect(0);
grSstWinOpen(null, GR_RESOLUTION_640x480, GR_REFRESH_60HZ,
GR_COLORFORMAT_RGBA, GR_ORIGIN_LOWER_LEFT, 2, 0);
grBufferClear(0, 0, 0);
GrVertex A, B, C;
... // Init A, B, and C.
guColorCombineFunction( GR_COLORCOMBINE_ITRGB );
grDrawTriangle(&A, &B, &C);
grBufferSwap( 1 );
grGlideShutdown();
}
«Стандарт» MiniGL
Хоть MiniGL и был подмножеством стандарта OpenGL 1.1, для него никогда не выпускали спецификацию. MiniGL был «только теми функциями, что использует Quake». Запустив objdump для двоичного файла quake.exe, легко построить «официальный» список.
$ objdump -p glquake.exe | grep " gl" glAlphaFunc glDepthMask glLoadIdentity glShadeModel glBegin glDepthRange glLoadMatrixf glTexCoord2f glBlendFunc glDisable glMatrixMode glTexEnvf glClear glDrawBuffer glOrtho glTexImage2D glClearColor glEnable glPolygonMode glTexParameterf glColor3f glEnd glPopMatrix glTexSubImage2D glColor3ubv glFinish glPushMatrix glTranslatef glColor4f glFrustum glReadBuffer glVertex2f glColor4fv glGetFloatv glReadPixels glVertex3f glCullFace glGetString glRotatef glVertex3fv glDepthFunc glHint glScalef glViewport
Если вы начинали учить OpenGL недавно, то вас должны заинтриговать такие названия функций, как glColor3f, glTexCoord2f, glVertex3f, glTranslatef, glBegin и glEnd. Они использовались для режима под названием «Immediate mode», в котором координата вершины, координата текстуры, манипуляции с матрицами и цвет указывались одним вызовом функции за раз.
Вот как «в те времена» отрисовывался один текстурированный и затенённый по Гуро треугольник.
void Render {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glShadeModel(GL_SMOOTH);
glBindTexture(GL_TEXTURE_2D, 1); // Assume a texture was loaded in textureId=1
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-1.0f,-0.25f,0.0f);
glColor3f(0.0f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f);
glVertex3f(-0.5f,-0.25f,0.0f);
glColor3f(0.5f, 0.5f, 0.5f);
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-0.75f,0.25f,0.0f);
glEnd();
GLQuake
Теоретический максимум скорости заполнения в 50 мегапикселей/с должен был обеспечивать почти 50 кадров в секунду в разрешении 640x480. Однако поскольку Quake комбинировал по два слоя текстур на поверхность (одну для цвета, другую для карты освещения), карте SST1 приходилось отрисовывать каждый кадр дважды с дополнительным смешением во втором проходе. В результате на P166Mhz Quake работал со скоростью 26 fps.
Снизив на той же машине разрешение до 512x384, можно было добиться плавных 41 fps[7], которых в то время не мог обеспечить ни один конкурент.
Программный рендеринг
GLQUAKE VOODOO1
Интересный факт: SST1 была не для всех. Некоторым людям нравились пиксели и они находили билинейную фильтрацию «размытой». Других раздражала потеря гамма-коррекции.
Glquake выглядит отстойно. Думаю, кто-то может с этим поспорить, но давайте признаемся — он выглядит ужасно, особенно на картах NVidia. На платах 3dfx всё не так плохо… но цвета всё равно размыты. На TNT2 картинка отвратительна; она слишком тёмная и мрачная.
— @Frib, Unofficial Glquake & QW Guide[8]
3fdx Voodoo2
Если бы я сказал, что 3dfx правила на рынке с 1996 до 1998 года, то это было бы преуменьшением. После SST1 технология Voodoo2 подняла планку производительности ещё выше благодаря 100-мегагерцовой EDO RAM, ASIC с частотой 90 МГц, и не одним, а двумя TMU, позволяющими отрисовывать многотекстурный кадр Quake (цвет + освещение) за один проход[9]. Эта технология была настоящим монстром, и даже сами графические карты выглядели роскошно.
Скорость заполнения в Voodoo2 почти удвоилась, достигнув 90 мегапикселей/с. Бенчмарки Quake взлетели до потрясающих 80 fps на Pentium II 266 MMX (по сравнению с 56 fps с Voodoo1), по сути, дойдя до пределов игровой логики и возможностей мониторов.
Super Voodoo 2 12MB, изображение взято с vgamuseum.info.
К сожалению, после выпуска в 1999 году Voodoo3 история 3dfx совершила резкий поворот. Она начала стремиться к разработке собственных универсальных карт и прекратила продавать OEM свои технологии, столкнувшись с нарастающей конкуренцией.
Этот переход завершился не так, как ожидалось, и производительность Voodoo3 разочаровывала по сравнению с GeForce 256 компании NVidia, способной обеспечивать аппаратную тесселяцию и освещение (эту часть в конвейере выполнял Pentium).
В качестве ответа NVidia компания 3dfx отменила разработку Voodoo4, чтобы приступить к созданию Voodoo5 с технологией VSA-100 (Voodoo Scalable Architecture). Результат оказался неожиданным: после выхода «Napalm» (кодовое имя карты) она столкнулась с более мощными картами NVidia GeForce 2 и ATI Radeon. В конце концов 28 марта 2000 года 3dfx заявила о своём банкротстве и была куплена NVidia.
Для тех, кто жил в конце 90-х и имел удовольствие играть на Voodoo1 или Voodoo2, 3dfx остаётся знаковой компанией, символизирующей превосходство. Она стала одой заслуженному успеху, достигнутому благодаря смелости, выдающемуся таланту и упорной работе. Спасибо, вам, ребята!
Справочные материалы
[1] Источник: The story of the Rendition Vérité 1000
[2] Источник: John Carmack .plan. Feb 12, 1998
[3] Источник: SST-1, HIGH PERFORMANCE GRAPHICS ENGINE FOR 3D GAME ACCELERATION
[4] Источник: 3dfx Oral History Panel
[5] Источник: Origin of Quake3's Fast InvSqrt()
[6] Источник: Glide Programming Guide
[7] Источник: Comparison of Frame-rates in GLQuake Using Voodoo & Voodoo 2 3D Cards
[8] Источник: Frib, Unofficial Glquake & QW Guide
[9] Источник: VOODOO2 GRAPHICS HIGH PERFORMANCE GRAPHICS ENGINE FOR 3D GAME ACCELERATION