Мультимедиа

 

Мультимедийный рынок больше всего похож на пламя костра или бурный поток. Каждую секунду возникает новый образ, новая форма, готовая угаснуть в следующий момент.

 

Потребителю, сидящему дома у своего компьютера и играющему в очередную компьютерную игру, не видны глубинные источники происходящих изменений, а ведь они зачастую весьма любопытны и во многом поучительны. В самом деле, давайте посмотрим на историю развития рынка массовых трехмерных ускорителей (предназначенных в первую очередь для игр и приложений виртуальной реальности).

 

Итак, вначале был хаос. Очень многие фирмы пытались выпустить дешевые 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;

Версия для печати