15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Изучаем Direct X вместе на примерах  SDK 7.0

Создадим проект, который показывает первичный и вторичный буфера.

 

Перед началом работы возьмите DirectX SDK 7.0 (http://www.microsoft.com/, 128 891 648 байт или прямиком сюда), скиньте из директории Lib файлы в Visual C++, тоже в директорию Lib, и  перепишите Include в директорию Include. Откройте проект имеющий расширение *.mak, а не *.dsw. Или же, если у Вас нет терпения качать, а Шаг первый хотите сделать, возьмите отсюда: include.rar и lib.rar.

 

Затем загрузите этот проект: project.rar.

 

Откройте проект при помощи меню File->Open Workspace, и выберите ddex1.dsw. Проект загружен. Можете скомпилировать и посмотреть для начала, что происходит. Лично я применял Visual C++ 6.0 взятый из Visual Studio 6.0 (но может и на 5.0 компилироваться, если сделать выше указанные действия).

 

Итак, рассмотрим нижеследующий код:

 

#include <windows.h>

#include <ddraw.h>

#include <stdio.h>

#include <stdarg.h>

#include "resource.h"

 

int PASCAL

WinMain(HINSTANCE hInstance,

        HINSTANCE hPrevInstance,

        LPSTR lpCmdLine,

        int nCmdShow)

{

    MSG                         msg;

 

    InitApp(hInstance, nCmdShow);

 

    while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

    return msg.wParam;

}

 

РАЗЪЯСНЕНИЕ:

InitApp(hInstance, nCmdShow) создает класс окна, в котором инициализируется DirectDraw, ниже мы его создадим.

Цикл while – обработка сообщений с клавиатуры (постоянна, на протяжении всей программы, пока она не завершится).

 

Теперь рассмотрим InitApp более подробно:

 

static HRESULT

InitApp(HINSTANCE hInstance, int nCmdShow)

{

    HWND                        hWnd;

    WNDCLASS                    wc;

    DDSURFACEDESC2              ddsd;

    DDSCAPS2                    ddscaps;

 

    // Устанавливаем параметры окна

    wc.style = CS_HREDRAW | CS_VREDRAW;

    wc.lpfnWndProc = WindowProc;

    wc.cbClsExtra = 0;

    wc.cbWndExtra = 0;

    wc.hInstance = hInstance;

    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN_ICON));

    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.hbrBackground = (HBRUSH )GetStockObject(BLACK_BRUSH);

    wc.lpszMenuName = NAME;

    wc.lpszClassName = NAME;

    RegisterClass(&wc);

 

    // Создаем окно

    hWnd = CreateWindowEx(WS_EX_TOPMOST,

                          NAME,

                          TITLE,

                          WS_POPUP,

                          0,

                          0,

                          GetSystemMetrics(SM_CXSCREEN),

                          GetSystemMetrics(SM_CYSCREEN),

                          NULL,

                          NULL,

                          hInstance,

                          NULL);

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

 

    ///////////////////////////////////////////////////////////////////////////

    // Создаем главный DirectDraw объект

    ///////////////////////////////////////////////////////////////////////////

    DirectDrawCreateEx(NULL, (VOID**)&g_pDD, IID_IDirectDraw7, NULL);

 

    // Отображение окна: полноэкранный

    g_pDD->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

 

    // Устанавливаем графический режим 640x480x8

    g_pDD->SetDisplayMode(640, 480, 8, 0, 0);

 

    // Создаем первичный буфер, у которого есть вторичный

    ZeroMemory(&ddsd, sizeof(ddsd));

    ddsd.dwSize = sizeof(ddsd);

    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |

                          DDSCAPS_COMPLEX;

    ddsd.dwBackBufferCount = 1;

    g_pDD->CreateSurface(&ddsd, &g_pDDSPrimary, NULL);

 

    // Делаем вторичный буфер

    ZeroMemory(&ddscaps, sizeof(ddscaps));

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

    g_pDDSPrimary->GetAttachedSurface(&ddscaps, &g_pDDSBack);

 

    // Устанавливаем таймер     

    SetTimer(hWnd, TIMER_ID, TIMER_RATE, NULL);

 

    return DD_OK;

}

 

А также  после #include инициализируем следующие параметры:

 

#define NAME                         "DDExample1"

#define TITLE                          "Direct Draw Example 1"

 

#define TIMER_ID                    1

#define TIMER_RATE               500

 

LPDIRECTDRAW7                   g_pDD = NULL;                // Главный DirectDraw объект

LPDIRECTDRAWSURFACE7    g_pDDSPrimary = NULL;  // Первичная память DirectDraw

LPDIRECTDRAWSURFACE7    g_pDDSBack = NULL;      // Вторичная память DirectDraw

 

static char                 szMsg[] = "Page Flipping Test: Press F12 to exit";

static char                 szFrontMsg[] = "Front buffer (F12 to quit)";

static char                 szBackMsg[] = "Back buffer (F12 to quit)";

 

РАЗЪЯСНЕНИЕ:

 

Создание объекта DirectDraw, определяем  режим отображения и разрешаемую графическую способность

 

DirectDrawCreateEx(NULL, (VOID**)&g_pDD, IID_IDirectDraw7, NULL);

Перед тем как пользоваться возможностями DirectDraw, нужно его создать, что и делает эта функция. g_pDD – содержит сам объект на DirectDraw.

 

g_pDD->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

Устанавливаем полноэкранный графический режим.

DDSCL_EXCLUSIVE – разрешает прямой доступ к видеокарте. Т.е. все операции, которые мы будем проводить позднее, они будут обрабатываться значительно быстрее. Доступен только при параметре DDSCL_FULLSCREEN.

DDSCL_FULLSCREEN – создаем полноэкранный графический режим.

 

g_pDD->SetDisplayMode(640, 480, 8, 0, 0);

Устанавливаем графический режим.

640 – точек по горизонтали.

480 – точек по вертикали.

Режимы могут быть: 320x200, 640x480, 800x600, 1024x768, 1280x1024, 1600x1280)

8 – бит цветов или 256. Также могут быть 16 бит, 24 бита и 32 бита.

 

Создаем первичный буфер

 

ZeroMemory(&ddsd, sizeof(ddsd));

Выделяем память в видеобуфере, если ее не хватит, то в оперативную или на жестком диске.

ddsd указывает на структуру DDSURFACEDESC2, которая в свою очередь содержит характеристики  буфера . Ее свойства описаны ниже:

 

ddsd.dwSize = sizeof(ddsd);

Присваиваем размер ddsd.

 

ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

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

 

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

Эта поверхность используется в качестве первичного видеобуфера (DDSCAPS_PRIMARYSURFACE), применяется переключение видеостраниц (DDSCAPS_FLIP). Параметр DDSCAOS_COMPLEX нужен, если ставишь DDSCAPS_FLIP, для быстродействия.

 

ddsd.dwBackBufferCount = 1;

Указываем, что эта поверхность будет использовать второй буфер. Если параметры второго буфера не задаем, то они ставятся по умолчанию, в зависимости от первичного буфера.

 

g_pDD->CreateSurface(&ddsd, &g_pDDSPrimary, NULL);

Выше мы определили параметры памяти ddsd, теперь их переносим в g_pDDSPrimary, которая и будет первичным видеобуфером (тот который отображается на экране). А ddsd можно использовать для другого…

 

Создаем вторичный буфер

 

ZeroMemory(&ddscaps, sizeof(ddscaps));

Выделяем память для вторичного видеобуфера. Он должен иметь структуру не LPDIRECTDRAWSURFACE7, а DDSCAPS2, хотя в большинстве параметров они схожи J

 

ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

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

 

g_pDDSPrimary->GetAttachedSurface(&ddscaps, &g_pDDSBack);

g_pDDSBack теперь содержится вторичный буфер, который, в данном моменте, подключается к первичной памяти при помощи функцииGetAttachedSurface. И стоит вызвать функцию flip, как они поменяются на экране, т.е. невидимая, станет видимой, а видимая невидимой, но это ниже…

 

SetTimer(hWnd, TIMER_ID, TIMER_RATE, NULL);

Устанавливаем время. Через определенное миллисекунд, Windows отправляет сообщение WM_TIMER, которая нам в дальнейшем пригодится.

TIMER_ID – идентификатор часов, в нашем случае он равняется 1. Можно поставить одновременно несколько таймеров, задав функцию SetTimer но уже с индификатором 2, будет создан второй таймер, и при помощи функции KillTimer(hWnd, 2), его удаляем, а первый остается.

TIMER_RATE – ставим число в миллисекундах,  1с = 1000 мс.

 

Ниже следующий код расположите перед функцией InitApp:

 

Обработка сообщений Windows

 

long FAR PASCAL

WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    switch (message)

    {

         // Завершение программы

        case WM_DESTROY:

            PostQuitMessage(0);

            return 0L;

 

         // Если нажали клавишу, в данном случае Esc и F12 завершает программу

        case WM_KEYDOWN:

            switch (wParam)

            {

                case VK_ESCAPE:

                case VK_F12:

                    PostMessage(hWnd, WM_CLOSE, 0, 0);

                    return 0L;

            }

            break;

 

         // Скрываем курсор с экрана

        case WM_SETCURSOR:

            SetCursor(NULL);

            return TRUE;

       

        // Перерисовываем экран по таймеру, таймер автоматически посылает WM_TIMER

        case WM_TIMER:

            // Перерисовываем вторичный буфер

            UpdateFrame(hWnd);

 

            // Меняем местами, первичный со вторичным буфером

            g_pDDSPrimary->Flip(NULL, 0);

 

    }

    return DefWindowProc(hWnd, message, wParam, lParam);

}

 

РАЗЪЯСНЕНИЕ:

Здесь вроде и так все понятно, читай комментарии.

 

Смотрим дальше, что у нас:

 

static void

UpdateFrame(HWND hWnd)

{

    static BYTE                 phase = 0;

    HDC                         hdc;

    DDBLTFX                     ddbltfx;

    RECT                        rc;

    SIZE                        size;

 

    // Заполняем выделенную память черным цветом и затем копируем ее во вторичный буфер

    ZeroMemory(&ddbltfx, sizeof(ddbltfx));

    ddbltfx.dwSize = sizeof(ddbltfx);

    ddbltfx.dwFillColor = 0;

    g_pDDSBack->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

 

    // Отрываем контекст устройства (Кто забудет это сделать, тот не сможет вывести символы)

    g_pDDSBack->GetDC(&hdc);

    SetBkColor(hdc, RGB(0, 0, 255));

    SetTextColor(hdc, RGB(255, 255, 0));

        if (phase)

        {

            // Первый раз она пропускается, так как phase = 0. Заносим текст во вторичный буфер

            GetClientRect(hWnd, &rc);

            GetTextExtentPoint(hdc, szMsg, lstrlen(szMsg), &size);

            TextOut(hdc, (rc.right - size.cx) / 2, (rc.bottom - size.cy) / 2,

                    szMsg, sizeof(szMsg) - 1);

            TextOut(hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg));

            phase = 0;

        }

        else

        {

            // При первом обращении выполняется эта функция, заносит текст во вторичный

            // буфер

TextOut(hdc, 0, 0, szBackMsg, lstrlen(szBackMsg));

            phase = 1;

        }

        // Закрываем контекст устройства

        g_pDDSBack->ReleaseDC(hdc);

}

 

РАЗЪЯСНЕНИЕ:

 

Очищаем вторичную память.

 

ZeroMemory(&ddbltfx, sizeof(ddbltfx));

Выделяем свободную память.

ddbltfx.dwSize = sizeof(ddbltfx);

Ставим ее размер.

ddbltfx.dwFillColor = 0;

Цвет заполнения ставим 0, т.е. черный.

 

g_pDDSBack->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

Заполняем вторичную память черным цветом, т.е. очищаем.

 

Рисуем во вторичной памяти:

 

g_pDDSBack->GetDC(&hdc);

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

 

SetBkColor(hdc, RGB(0, 0, 255));

Устанавливаем фон текста, RGB – красный, зеленый, синий.

 

SetTextColor(hdc, RGB(255, 255, 0));

Устанавливаем цвет самого текста.

 

if (phase)

   {

       GetClientRect(hWnd, &rc);

       GetTextExtentPoint(hdc, “Тест переключения страниц: нажмите F12, чтобы выйти”,

lstrlen(“Тест переключения страниц: нажмите F12, чтобы выйти”),

&size);

       TextOut(hdc, (rc.right - size.cx) / 2, (rc.bottom - size.cy) / 2, “Тест переключения страниц:

нажмите F12, чтобы выйти”, sizeof(“Тест переключения страниц: нажмите F12,

чтобы выйти”) - 1);

       TextOut(hdc, 0, 0, “Первичный буфер: (F12 выход)”.szFrontMsg, lstrlen(“Первичный буфер:

(F12 выход)”.));

       phase = 0;

   }

Выполняется в том случае, если phase равна 1 (при первом обращении она не выполняется).

 

GetClientRect(hWnd, &rc);

Заносит в структуру rc параметры окна, левого верхнего угла, и правого нижнего.

 

GetTextExtentPoint(hdc, “Тест переключения страниц: нажмите F12, чтобы выйти”,

lstrlen(“Тест переключения страниц: нажмите F12, чтобы выйти”), &size);

Заносит в структуру size ширину и высоту данной строки. Которая используется для вывода текста по середине экрана.

 

TextOut(hdc, (rc.right - size.cx) / 2, (rc.bottom - size.cy) / 2, “Тест переключения страниц:

нажмите F12, чтобы выйти”, sizeof(“Тест переключения страниц: нажмите F12,

чтобы выйти”) - 1);

Выводим текст в координатах х и y, задав его текст и размер текста. Один вычитаем, чтобы не было иероглифа.

 

Переключение страниц:

 

Затем выводим вторую строку на вторичный буфер, выполняется команда                 g_pDDSPrimary->Flip(NULL, 0); и вторичный буфер мы с Вами увидим на экране, а первичный “прячется”. Через некоторое время Windows посылает команду WM_TIMER и на этот раз выполняется if, куда заносятся две строки.