Мультимедиа
Мультимедийный рынок больше всего похож на пламя костра или бурный поток. Каждую секунду возникает новый образ, новая форма, готовая угаснуть в следующий момент.
Потребителю, сидящему дома у своего компьютера и играющему в очередную компьютерную игру, не видны глубинные источники происходящих изменений, а ведь они зачастую весьма любопытны и во многом поучительны. В самом деле, давайте посмотрим на историю развития рынка массовых трехмерных ускорителей (предназначенных в первую очередь для игр и приложений виртуальной реальности).
Итак, вначале был хаос. Очень многие фирмы пытались выпустить дешевые 3D-ускорители, каждый из них создавал собственный интерфейс и горделиво вкладывал в коробку с платой компакт-диск с одной-двумя играми, использующими возможности конкретного ускорителя. Мощности таких плат не хватало даже на простенькие задачки, и, как правило, к моменту широкого их распространения новое поколение процессоров уже позволяло делать более сложные вещи без дополнительной помощи со стороны аппаратуры. Среди примеров таких ускорителей - S3 Virge, Matrox Millennium и Mystique.
Отдельно (и абсолютно независимо) развивался рынок “настоящих”, дорогих и очень мощных ускорителей, реально способных превратить трехмерную графику в нормальную рабочую среду. Там вопросы стандартизации не стояли с самого начала - устоявшийся межплатформный стандарт OpenGL, разработанный фирмой Silicon Graphics, был привычен очень давно.
Фирма Microsoft, уловив приближающееся слияние этих двух миров, попыталась в очередной раз провести операцию по захвату потенциального рынка, используя не раз проверенную стратегию создания новых, ни с чем не совместимых стандартов, работающих только на платформе Windows. И возник Direct3D. Первое время он как бы существовал в безвоздушном пространстве, поскольку плат с достаточными возможностями и достаточно низкой ценой попросту не было.
В конце концов прорыв все же произошел, и самой яркой звездой в новом созвездии стал набор микросхем Voodoo фирмы 3dfx. Его производительности вполне хватало, чтобы любая современная 3D-игра ускорилась в несколько раз, при этом разрешение увеличивалось с привычного 320x200 точек до 640x480.
С этого момента и начинается рассказ о битве титанов. Представим действующих лиц. Итак, с одной стороны, - Microsoft, с другой - фирмыпроизводители игр, возглавляемые фирмой id Software (создавшей игры Castle Wolfenstein, Doom и Quake).
Небольшие фирмы пошли по традиционному пути и выпустили немало игр, работающих только на Voodoo, а их более крупные коллеги, понимая, что надолго фирма 3dfx на этом рынке в одиночестве не останется, пришли к выводу о необходимости стандартизации. Вот здесь-то и встал вопрос о выборе подходящего API.
В то время как Microsoft активно продвигала свой стандарт, привлекая на свою сторону умы менеджеров, владельцы id Software (сами пишущие игры) сделали сравнение самостоятельно. И пришли к выводу, что Direct3D им неудобен, а OpenGL - именно то, что надо. И выпустили GLQuake, версию игры Quake, работающую через интерфейс OpenGL. И все разработчики бытовых трехмерных ускорителей, понимая, что именно эта игра, самая популярная в жанре 3d-action, отлично демонстрирует возможности их аппаратуры, немедленно выпустили драйверы с поддержкой OpenGL.
И началось... Microsoft выступила с заявлениями об удобстве и мощности ее стандарта, о том, как легко его освоить. Silicon Graphics отреагировала довольно длинным рассказом о том, в чем, по ее мнению, различие стандартов. Один пример оттуда просто невозможно не привести. Он очень прост - программа должна вывести на экран неосвещенный, нетекстурированный куб, все стороны которого окрашены в разные цвета.
Легко заметить, что из-за ориентированного на макрокоманды стиля Direct3D гораздо меньше проверок могут быть выполнены во время компиляции, что приводит к большому числу ошибок программистов. Кроме того, буферы выполнения более чувствительны к ошибкам индексации и неверно назначенным указателям. Даже верно записать нужную цепочку указателей весьма непросто без постоянных консультаций с документацией.
Понятно, что программисты, задача которых - написать программу быстрее и с меньшим количеством ошибок, выбрали более удобный им интерфейс - OpenGL. Но столкнулись с твердым намерением Microsoft навязать им нечто иное. Последовало коллективное открытое письмо от разработчиков игр (подписанное представителями почти всех крупнейших фирм-разработчиков - всего 56 подписей), призвавшее Microsoft поддержать наряду с ее собственным стандартом и общепринятый и облегчить разработчикам драйверов для Windows написание таковых для OpenGL. С текстом письма можно ознакомиться по адресу: http://www.gamecenter.com/News/Item/Cont/0,4,929_2,00.html.
Увы, никакой конструктивной реакции они не дождались. Microsoft устроила встречу с разработчиками, где продемонстрировала эмулятор нужного для игры Quake подмножества OpenGL, работающий через Direct3D и “написанный за три недели стажером” (по заявлению фирмы). Показав, что он работает на одном и том же оборудовании всего на один кадр в секунду (около 4 - 5%) медленнее, чем “родной” драйвер OpenGL, она сочла свою задачу выполненной и на просьбу авторов письма больше никак не отреагировала.
Было написано еще одно письмо (с его текстом можно ознакомиться по адресу: http://www.multiweb. nl/~henk/OpenLetter/page.html), подписанное 254 разработчиками, но и оно не принесло никакого результата.
Впрочем, это не совсем так - по крайней мере одного человека изменения коснулись и весьма сильно. Алекс Сент-Джон, руководитель проекта Direct3D в течение четырех лет, имевший неосторожность публично признать, что в требованиях разработчиков есть зерно истины, был немедленно уволен из Microrosoft. По его словам, Джон Кармак (разработчик из id Software) абсолютно прав с технической точки зрения, но справиться с маркетинговой службой Microsoft не так просто, как ему кажется.
В итоге на сегодня мы имеем следующую ситуацию: программисты пишут на том, на чем им удобно. Фирмы (а недорогих ускорителей в наши дни уже почти десяток моделей) тратят время на разработку двух разных драйверов для каждой новой модели плат, пользователи эти самые два драйвера ставят и удивляются, почему вся эта конструкция так неустойчиво работает. По заявлениям руководителей id Software, они - реалисты и не собираются совать голову в петлю, если Microsoft задавит конкурирующий стандарт, но до тех пор они будут пользоваться тем, что удобнее и надежнее.
Павел Гродек
Вот так выглядит процедура рисования куба с использованием Direct3D
/* (материалы определены раньше) */
LPDIRECT3DEXECUTEBUFFER lpD3DExBuf;
D3DEXECUTEBUFFERDESC debDesc;
D3DEXECUTEDATA d3dExData;
int NumVertices = 8;
int NumTri = 12;
/* вычисляем размер буфера выполнения */
size = sizeof(D3DLVERTEX) * NumVertices;
size += sizeof(D3DSTATUS) * 1;
size += sizeof(D3DPROCESSVERTICES) * 6;
size += sizeof(D3DINSTRUCTION) * 15;
size += sizeof(D3DSTATE) * 6;
size += sizeof(D3DTRIANGLE) * NumTri;
/* создаем буфер выполнения */
memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
debDesc.dwFlags = D3DDEB_BUFSIZE; debDesc.dwBufferSize = size;
if (lpDev->lpVtbl->CreateExecuteBuffer(lpDev, &debDesc, &lpD3DExBuf, NULL) != D3D_OK)
return FALSE;
if (lpD3DExBuf->lpVtbl->Lock(lpD3DExBuf, &debDesc) != D3D_OK)
return FALSE;
lpBufStart = debDesc.lpData;
memset(lpBufStart, 0, size);
lpPointer = lpBufStart;
/* Вставляем в буфер инструкции, трансформирующие координаты и назначающие цвета вершинам */
/* Создаем и заполняем список вершин */
D3DCOLOR diff = RGB_MAKE(255,255,255);
D3DCOLOR spec = RGB_MAKE(0,0,0);
LPD3DLVERTEX v = lpPointer;
v[0].dvX = D3DVAL(-1.0); v[0].dvY = D3DVAL(1.0); v[0].dvZ = D3DVAL(1.0);
v[0].dcColor = diff; v[0].dcSpecular = spec;
v[1].dvX = D3DVAL(-1.0); v[1].dvY = D3DVAL(-1.0); v[1].dvZ = D3DVAL(1.0);
v[1].dcColor = diff; v[1].dcSpecular = spec;
v[2].dvX = D3DVAL(1.0); v[2].dvY = D3DVAL(-1.0); v[2].dvZ = D3DVAL(1.0);
v[2].dcColor = diff; v[2].dcSpecular = spec;
v[3].dvX = D3DVAL(1.0); v[3].dvY = D3DVAL(-1.0); v[3].dvZ = D3DVAL(-1.0);
v[3].dcColor = diff; v[3].dcSpecular = spec;
v[4].dvX = D3DVAL(-1.0); v[4].dvY = D3DVAL(1.0); v[4].dvZ = D3DVAL(-1.0);
v[4].dcColor = diff; v[4].dcSpecular = spec;
v[5].dvX = D3DVAL(-1.0); v[5].dvY = D3DVAL(-1.0); v[5].dvZ = D3DVAL(-1.0);
v[5].dcColor = diff; v[5].dcSpecular = spec;
v[6].dvX = D3DVAL(1.0); v[6].dvY = D3DVAL(1.0); v[6].dvZ = D3DVAL(1.0);
v[6].dcColor = diff; v[6].dcSpecular = spec;
v[7].dvX = D3DVAL(1.0); v[7].dvY = D3DVAL(1.0); v[7].dvZ = D3DVAL(-1.0);
v[7].dcColor = diff; v[7].dcSpecular = spec;
lpPointer = (void *) &v[8];
lpInsStart = lpPointer;
OP_SET_STATUS(D3DSETSTATUS_ALL, D3DSTATUS_DEFAULT, 2048, 2048, 0, 0, lpPointer);
for (i=0;i<6;i++) {int n = (i<5) ? 1 : 3;
OP_STATE_LIGHT(1, lpPointer);
STATE_DATA(D3DLIGHTSTATE_MATERIAL, D3DMaterialHandle[i], lpPointer);
OP_PROCESS_VERTICES(1, lpPointer);
PROCESSVERTICES_DATA(D3DPROCESSVERTICES_TRANSFORM, i, n, lpPointer);
if (QWORD_ALIGNED(lpPointer)) {
OP_NOP(lpPointer);
/* вставляем список треугольников */
OP_TRIANGLE_LIST(NumTri, lpPointer);
LP3DTRIANGLE tri = lpPointer;
WORD flags = D3DTRIFLAG_EDGEENABLETRIANGLE;
tri[0].v1 = 0; tri[0].v2 = 6; tri[0].v3 = 7; tri[0].wFlags = flags;
tri[1].v1 = 0; tri[1].v2 = 7; tri[1].v3 = 4; tri[1].wFlags = flags;
tri[2].v1 = 1; tri[2].v2 = 2; tri[2].v3 = 6; tri[2].wFlags = flags;
tri[3].v1 = 1; tri[3].v2 = 6; tri[3].v3 = 0; tri[3].wFlags = flags;
tri[4].v1 = 2; tri[4].v2 = 3; tri[4].v3 = 7; tri[4].wFlags = flags;
tri[5].v1 = 2; tri[5].v2 = 7; tri[5].v3 = 6; tri[5].wFlags = flags;
tri[6].v1 = 3; tri[6].v2 = 5; tri[6].v3 = 4; tri[6].wFlags = flags;
tri[7].v1 = 3; tri[7].v2 = 4; tri[7].v3 = 7; tri[7].wFlags = flags;
tri[8].v1 = 4; tri[8].v2 = 5; tri[8].v3 = 1; tri[8].wFlags = flags;
tri[9].v1 = 4; tri[9].v2 = 1; tri[9].v3 = 0; tri[9].wFlags = flags;
tri[10].v1 = 5; tri[10].v2 = 3; tri[10].v3 = 2; tri[10].wFlags =flags;
tri[11].v1 = 5; tri[11].v2 = 2; tri[11].v3 = 1; tri[11].wFlags =flags;
lpPointer = (void *) &tri[12];
OP_EXIT(lpPointer);
/* Настраиваем данные исполнения, описывающие буфер */
lpD3DExBuf->lpVtbl->Unlock(lpD3DExBuf);
memset(&d3dExData, 0, sizeof(D3DEXECUTEDATA));
d3dExData.dwSize = sizeof(D3DEXECUTEDATA);
d3dExData.dwVertexCount = NumVertices;
d3dExData.dwInstructionOffset = (ULONG)((char*)lpInsStart - (char*)lpBufStart);
d3dExData.dwInstructionLength = (ULONG)((char*)lpPointer - (char*)lpInsStart);
lpD3DExBuf->lpVtbl->SetExecuteData(lpD3DExBuf, &d3dExData);
/* Рисуем куб (т.е. выполняем буфер команд) */
if (lpDev->lpVtbl->BeginScene(lpDev) != D3D_OK)
return FALSE;
if (lpDev->lpVtbl->Execute(lpDev, lpD3DExBuf, lpView, D3DEXECUTE_CLIPPED) != D3D_OK)
return FALSE;
if (lpDev->lpVtbl->EndScene(lpDev) != D3D_OK)
return FALSE;
А так рисуется куб с помощью OpenGL
glShadeModel(GL_FLAT);
/* грани X/Z */
glBegin(GL_QUAD_STRIP);
glColor3f(1.0, 0.0, 0.0);
glVertex3f(1.0, 1.0, 1.0); glVertex3f(1.0, -1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); glVertex3f(1.0, -1.0, -1.0);
glColor3f(0.0, 1.0, 0.0); glVertex3f(-1.0, 1.0, -1.0); glVertex3f(-1.0, -1.0, -1.0);
glColor3f(0.0, 0.0, 1.0); glVertex3f(-1.0, 1.0, 1.0); glVertex3f(-1.0, -1.0, 1.0);
glColor3f(1.0, 1.0, 0.0); glVertex3f(1.0, 1.0, 1.0); glVertex3f(1.0, -1.0, 1.0);
glEnd();
/* грани Y */
glBegin(GL_QUADS);
glColor3f(1.0, 0.0, 1.0);
glVertex3f(1.0, 1.0, 1.0); glVertex3f(1.0, 1.0, -1.0);
glVertex3f(-1.0, 1.0, -1.0); glVertex3f(-1.0, 1.0, 1.0);
glColor3f(0.0, 1.0, 1.0);
glVertex3f(1.0, -1.0, 1.0); glVertex3f(1.0, -1.0, -1.0);
glVertex3f(-1.0, -1.0, -1.0); glVertex3f(-1.0, -1.0, 1.0);
glEnd();
if (glGetError()) return FALSE;