Краткое описание по работе с Direct Draw

         

DirectDraw и MFC


1.

Особености встраивания объектов DirectDraw в МFС

2.       Поддержка оконного интерфейса при использовании DirectDraw в МFС

3.       Прямое рисование (минуя GDI)


·         переписан метод OnPaint() в котором создается указатель на CDC за экранной поверхности , вызывается метод OnDraw(CDC* pDC ), по окончании которого производится копирование содержимого за экранной поверхности в область текущего окна

·         переписан метод OnSize - при изменении размеров окна производится уничтожение старой за экранной поверхности и инициализация новой.

·         переписан метод OnEraseBackgraund()

Я думаю, для выяснения деталей лучше просто посмотреть исходники.

3 .Прямое рисование (минуя GDI)

Что такое мистические видео поверхности в DirectDraw. Войдите в панель управления Windows 95 , откройте панель ресурсов Вашего видеоадаптера и Вы увидите, что он использует значительного размера кусок памяти . Т.е. вся видеопамять как бы спроецирована на Ваше ОЗУ. DirectDraw - это механизм позволяющий Вам буквально напрямую использовать данное ОЗУ , при этом напрямую используя возможности видео ускорителя Вашего видеоадаптера. Все эти LPDIRECTDRAWSURFACE есть просто указатели на видеопамять отведенную вашему изображению. А отсюда зная его структуру можно напрямую в нем рисовать - просто записывая в него необходимые Вам значения. Ниже приведен участок кода для поддержки рисования точки (Примечание код взят из ответа на одной из конференций qiz@algonet.se).

Рисование точки для видеорежима 640x480x8

UINT

SetPixel( UINT

x, UINT

y, UINT

colorValue )



{

                     UINT

PointOffset;

                     UINT  RetColorValue=0;

                     DDSURFACEDESC DDSDesc;

                     __int8*  lpDstBits;

                     HRESULT ddrval;

                     DDSDesc.dwSize = sizeof(DDSDesc);

                     // Захват видеопамяти для записи

                     while(1)

                     {

                                           ddrval = lpDDSBack->Lock(NULL, &DDSDesc, 0, NULL);

                                           if(ddrval == DD_OK) break;

                                           if(ddrval != DDERR_WASSTILLDRAWING) return 0;

                     }

                     // set the destination pixel

                     // in this case resolution is 640 * 480 * 8

                     lpDstBits = (LPSTR) DDSDesc.lpSurface;

                     PointOffset = (640 * y) + x;

                     // если Вам нужен возврат цвета точки

                     RetColorValue=lpDstBits[Pointoffset];

                     // нарисуем точку

                     lpDstBits[Pointoffset] = (__int8) colorValue;

                     // now ulock the surface

                     lpDDSBack->Unlock(NULL);

                     return RetColorValue;

}


Краткий обзор DirectDraw


1.

DirectX API

2.       Что такое DirectDraw

3.       Взаимотношение с WinG

4.       Поверхности DirectDraw - вид доступа к видеопамяти

5.       От смены страниц (flipping) к анимации

6.       Интерфейсы DirectDraw и COM



Краткое описание по работе с DirectDraw


В этом разделе будет дан общий обзор DirectDraw, а также общие концепции.

1. DirectX API

Microsoft's DirectX API состоит из следующих групп:

·         DirectDraw - прямой доступ к видеопамяти

·         DirectSound - прямой доступ к звуковой карте

·         DirectPlay - прямой доступ к сетевым возможностям для обеспечения multiplayer mode

·         DirectInput - поддержка игровых устройств ввода

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

2. Что такое DirectDraw

DirectDraw

это обычный менеджер видеопамяти. Его основное назначение предоставить программисту прямой доступ к видеопамяти. Осуществлять такие операции, как копирование видео память -> видеопамять и т.п.. При этом напрямую могут использоваться возможности видеоконтроллера и освобождать от этих операций центральный процессор. Кроме того, DirectDraw напрямую использует и другие возможности Вашей виде окарты, как то спрайты, z - буферизацию и т.п.. Начиная с 5 версии DirectX дополнительно используются (во всяком случае декларируется использование) возможностей ММX - что позволяет за один цикл обрабатывать и передавать 64 бита видеоинформации.

3. Взаимоотношение с WinG

В основном идея DirectDraw взята с WinG. Отличие состоит в том, что в основу идеологии WinG положено копирование изображений из обычной памяти в видеопамять, в то время как основной идеей DirectDraw является копирование видеопамяти в видеопамять, хотя также может поддерживаться и режим память->видеопамять. Что, конечно, является необходимым в мире видео карт с 1 метром видеопамяти.

4. Поверхности DirectDraw - вид доступа к видеопамяти

Вашей основной задачей, как программиста, поместить в видеопамять столько изображений, необходимых для выполнения Вашей программы, как только возможно. Благо при использовании DirectDraw вся видеопамять доступна для Вас. Вы можете использовать ее для запоминания различных изображений (bitmap) , спрайтов и тп. Все эти видео элементы в терминологии DirectDraw называются поверхностями (surfaces). Основная последовательность при загрузке изображений следующая - Вы создаете поверхность(просто область видеопамяти (или системной памяти - если ресурсы видеопамяти исчерпаны)) , и загружаете в нее необходимое изображение.


DirectDraw

обеспечен функциями определения доступной видео памяти - таким образом Вы можете оптимизировать ее использование для конкретной видео карты. Минимальным для нормального использования DirectDraw представляется использование 2 M видео карт, хотя это субъективное мнение

5. От смены страниц (flipping) к анимации

Стандартным методом организации анимации является перенос видеобуферов изображения из видеопамяти в за экранную поверхность (offscreen buffer) с последующим переключением экранной и за экранной поверхности (flipping) которое выполняется простой функцией Flip(). При этом тот видеобуфер который был экранной поверхностью - становится за экранной поверхностью и наоборот .

Частота смены буферов может быть достаточно высокой и ограничена частотой кадровой развертки монитора (60 - 100 Гц). Поддержка DirectDraw

вертикальной развертки приводит к более гладкой смене кадров.

Интерфейсы DirectDraw и COM

Все DirectX объекты являются порождениями от COM объектов.

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

 


Некоторые детали


1.       Вопросы отладки

2.       Выполнение в окне

3.       Потеря поверхности

4.       Что еще почитать

 



Программирование DirectDraw


1.       Что Вам необходимо для использования DirectDraw

2.       Инициализация DirectDraw

3.       Создание первичной поверхности и установки для обеспечения смены страниц (flipping)

4.       Загрузка изображений в видеопамять (bitmap)

5.       Загрузка палитры

6.       Настройка прозрачного цвета

7.       Собрать все вместе и готово. Построение динамики.

8.       Очистка по окончании использования


Таким образом мы проинициализировали DirectDraw и изменили видео моду (в отладчике Developer Studio режим 640х480 устанавливать не рекомендую, эффект потрясающий - практически становятся видны только меню и туулбары)

3. Создание первичной и вторичной поверхностей и установки для обеспечения смены страниц (flipping)

В следующем участке кода мы рассмотрим создание первичной и вторичной поверхностей (видеобуферов) . Их объединение часто называют комплексной поверхностью

Пример инициализации поверхностей (первичной и вторичной) DirectDraw

 

 

 

LPDIRECTDRAWSURFACE

lpDDSPrimary; // указатель на первичную поверхность DirectDraw

LPDIRECTDRAWSURFACE

lpDDSBack;    // указатель на вторичную поверхность DirectDraw

BOOL CreatePrimarySurface()

{

    DDSURFACEDESC ddsd;

    DDSCAPS ddscaps;

    HRESULT

ddrval;

    // создадим первичную поверхность с одной вторичной

    memset( &ddsd, 0, sizeof(ddsd) );

    ddsd.dwSize = sizeof( ddsd );

    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;    // поле  ddsCaps

принимается в рассмотрение и в

                                                        // нем имеет значение только количество вторичных

                                                        //буферов(поверхностей)

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

    ddsd.dwBackBufferCount = 1;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );

    if( ddrval != DD_OK )

    {

        lpDD->Release();

        return FALSE;

    }

    // Теперь получим вторичный буфер (поверхность)

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);

    if( ddrval != DD_OK )

    {

        lpDDSPrimary->Release();

        lpDD->Release();

        return FALSE;

    }

    return TRUE;

}

<


 

4. Загрузка изображений в видеопамять (bitmap)

Следующим шагом является загрузка изображений в видеопамять. Идеальным является случай, когда в видеопамяти достаточно места для того чтобы там ужились все необходимые Вам для разработки изображения. Если ее не хватает , тоже особенно беспокоится не о чем, CreateSurface

создаст поверхности в обычной памяти (правда, это чуть замедлит).

Пример кода загрузки изображений в DirectDraw

 

 

 

/*

 * Функция DDLoadBitmap(IDirectDraw *pdd, LPCSTR

szBitmap) создает поверхность

 * и загружает файл с диска. Параметр szBitmap имя файла изображения.

 */

IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap)

{

    HBITMAP hbm;

    BITMAP bm;

    IDirectDrawSurface *pdds;

    // загрузим с диска

    hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0,

    LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)        return NULL;

    GetObject(hbm, sizeof(bm), &bm); // получим размер

    //создадим поверхность для данного изображения

    pdds = CreateOffScreenSurface(pdd, bm.bmWidth, bm.bmHeight);

    if (pdds)

    {

        DDCopyBitmap(pdds, hbm, bm.bmWidth, bm.bmHeight);

    }

    DeleteObject(hbm);

    return

pdds;

}

/*

 * Функция создания поверхности для изображения заданного размера

 * Эта поверхность окажется в видеопамяти, если ее еще достаточно,

 * в противном случае - в системной памяти

 */

IDirectDrawSurface * CreateOffScreenSurface(IDirectDraw *pdd, int dx, int dy)

{

    DDSURFACEDESC ddsd;

    IDirectDrawSurface *pdds;

    //

    // Создание поверхности

    //

    ZeroMemory(&ddsd, sizeof(ddsd));

    ddsd.dwSize = sizeof(ddsd);

    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH; // имеет значение высота и ширина

    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // это заэкранная поверхность

    ddsd.dwWidth = dx;

    ddsd.dwHeight = dy;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)

    {

        return NULL;

    }

    else

    {

        return pdds;

    }

}

/*

 * Функция копирования ранее загруженного изображения в видео поверхность

 * (Используется обычный GDI

интерфейс)

 *  Это, конечно, не самый быстрый способ - но зато надежный

 *  A в данный момент спешить некуда. Если у Вас происходит динамическая подгрузка

 *  изображений в процессе то лучше эту функцию переписать аккуратнее

 */

HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int dx, int dy)

{

    HDC hdcImage;

    HDC hdc;

    HRESULT hr;

    HBITMAP

hbmOld;

    //

    // привяжем контекст изображения (DC) к загруженному изображению

    //

    hdcImage = CreateCompatibleDC(NULL);

    hbmOld = (HBITMAP)SelectObject(hdcImage, hbm);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)

    {

        BitBlt(hdc, 0, 0, dx, dy, hdcImage, 0, 0, SRCCOPY);

        pdds->ReleaseDC(hdc);

    }

    SelectObject(hdcImage, hbmOld);

    DeleteDC(hdcImage);

    return hr;

}

<


Таким образом, мы имеем возможность загрузить все необходимые изображени я в видео поверхности DirectDraw. Для создания динамического изображения остается только организовать их копирование в на поверхности видеоизображения (первичный или вторичный буфер).

5. Загрузка палитры

Теперь для правильного отображения всех загруженных изображений нужно установить палитру для первичной видео поверхности. Все изображения, загружаемые для работы данного приложения должны иметь одну и туже палитру. Ниже приведена функция создания палитры для данного изображения

Пример кода создания палитры в DirectDraw

 

 

 

 

 

/*

 * Создание палитры DirectDraw для файла загружаемого с диска

 *  параметр szBitmap имя файла

 */

IDirectDrawPalette * LoadPaletteFromDibFile(IDirectDraw *pdd, LPCSTR

szBitmap)

{

    IDirectDrawPalette* ddpal;

    int i;

    int n;

    int fh;

    PALETTEENTRY ape[256];

    if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)

    {

        BITMAPFILEHEADER bf;

        BITMAPINFOHEADER bi;

        _lread(fh, &bf, sizeof(bf));

        _lread(fh, &bi, sizeof(bi));

        _lread(fh, ape, sizeof(ape));

        _lclose(fh);

        if (bi.biSize != sizeof(BITMAPINFOHEADER))

            n = 0;

        else if (bi.biBitCount > 8)

            n = 0;

        else if (bi.biClrUsed == 0)

            n = 1 << bi.biBitCount;

        else

            n = bi.biClrUsed;

      //

      // цветовая таблица DIB имеет  BGR кодировку а не RGB

      // сделаем необходимую транспозицию.

      //

        for(i=0; i<N; (pdd- if } ape[i].peBlue = r; ape[i].peRed = ape[i].peBlue; r = ape[i].peRed; BYTE { ) i++CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL) != DD_OK)

    {

        return NULL;

    } else {

        return ddpal;

    }

}

<


В исходный код инициализации DirectDraw необходимо добавить следующие две строчки:

Пример кода загрузки палитры в DirectDraw

 

 

 

    ......................

    lpDDPal        = LoadPaletteFromDibFile(lpDD, szBitmap);   // Создадим и Получим указатель на палитру

    if (lpDDPal)    lpDDSPrimary->SetPalette(lpDDPal);            // и установим эту палитру для первичной видео поверхности

    ....................       

 

5. Настройка прозрачного цвета

В DirectDraw

возможно использование прозрачного цвета(ов) (входа в палитру (color key)) используемого при копировании с поверхности на поверхность. Обычно для этого задается диапазон цветов (входов в палитру), которые не переносятся при копировании (Blt функциях).

Пример кода установки прозрачных цветов в DirectDraw

 

 

 

 

 

    // Установим прозрачный цвет для данного изображения .

    //

    DDCOLORKEY

ddColorKey;

     ddColorKey.dwColorSpaceLowValue = 0xff; // весь диапазон прозрачности - 255 вход в палитру (обычно он черный)

     ddColorKey.dwColorSpaceHighValue = 0xff;

    //  Далее необходимо сделать что-то типа для вех поверхностей (изображений ) для которых нужен эффект

    // прозрачности при выполнении операций копирования

  [Имя указателя на DirectDraw

поверхность]->SetColorKey( DDCKEY_SRCBLT, &ddck );

 

7. Собрать все вместе и готово. Построение динамики.

Теперь осталось только организовать цикл по обновлению вторичной поверхности и после его обновления проведения смены экранов (Flip) . Этот цикл лучше всего организовывать на цикле обработки сообщений Вашего приложения (OnIdle), таким образом Вы получите максимальную производительность для конкретной машины на которой выполняется ваше приложение. Сажать это на таймер я не советую - Вам просто будет сложно разобраться и все так подогнать, чтобы с одной стороны по максимуму использовать возможности машины, а с другой не перегружать очередь сообщений. Для общей синхронизации разумно использовать функции мультимедиа типа функции timeGetTime() которая выдает время в мс. Также учтите что чем больше нитей (THREAD) Вы организуете для обеспечения расчетов расположения изображений тем сложнее Вам будет добиться плавности (Windows 95 совсем не многозадачка с вытеснением и квантом времени я не нашел как управлять). Единственное ,что можно использовать для увеличения плавности - Sleep(0) для текущей нити приложения. Но это о другом - просто лирическое отступление - плач по отсутствию возможностей.



Обновления экрана

 

 

 

 

 

void

UpdateScreen( void )

{

    HRESULT ddrval;

    RECT mRect;

    int xpos, ypos;

   // Выберем весь экран и скопируем на вторичную поверхность изображения фона

   // если Вы, конечно, его используете

    SetRect(&mRect, 0, 0, 640, 480);

    // lpDDBackgraund - здесь поверхность фонового изображения

    // кстати оно может быт больше экрана - тогда возможно копирование только его части - типа перемещения по карте

    ddrval = lpDDSBack->BltFast( 0, 0, lpDDBackgraund, &mRect, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

    if( ddrval != DD_OK )

    {

        // не смогли - вернемся       

        return;

    }

   while ([цикл по всем изображениям, которые должны быть перенесены на экран])     

   {   

        // Получить текущее изображение (указатель lpDDCurrentImage) из списка, массива - чего было задумано,

        // а также его размера mWidth, mHigh, и местоположения x и y

        .......................

        // после получения указанных параметров

        SetRect(&mRect, 0, 0,mWidth , mHigh);  

        ddrval = lpDDSBack->BltFast( x, y, lpDDCurrentImage,    &mRect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );

           if( ddrval != DD_OK )

         {

        //не смогли - вернемся       

              return;

         } 

    }          

    // Теперь все нарисовано - можно поменять экран

    ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT );

}

К данному коду надо сделать ряд замечаний :

·         BltFast - функция, конечно, хорошая и по названию зазывающая, но не всегда идет - тогда используй обычный Blt (он видимо помедленнее - но надежней)



·         DDXX_WAIT флаг необходим для медленных видео карт и шустрых машин - просто без его использования функция возвращает управление сразу после запуска , а при его использовании только после окончания процесса копирования. - просто пока идет одно копирование вызов другого вроде может привести к ошибке. Память то захвачена другим процессом.

 

8. Очистка по окончании использования

По окончании работы не забудьте вызвать Release() для всех порожденных Вами объектов DirectDraw. Что то типа приведенного ниже кода.

Очистка после себя

 

 

 

void finiObjects( void )

{

    if( lpDD != NULL )

    {

       

        if( lpDDSPrimary != NULL )

        {

            lpDDSPrimary->Release();

            lpDDSPrimary = NULL;

        }

        if( lpDDSOne != NULL )

        {

            lpDDSOne->Release();

            lpDDSOne = NULL;

        }

        if( lpDDPal != NULL )

        {

            lpDDPal->Release();

            lpDDPal = NULL;

    }

    lpDD->Release();

    lpDD = NULL;

}