Файл программы Main.cpp

Функция WinMain()
Первой представляющей интерес функцией является WinMain(). Как и в любой программе для Windows, она является главной точкой входа в код. Вот как выглядит ее листинг:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
int iCmdShow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
RECT rcWindowClient;
// Инициализация класса окна
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = fnMessageProcessor;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = "Title Demo";
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// Регистрация класса окна
if(RegisterClassEx(&wndclass) == NULL) {
// Выход из программы при сбое
exit(1);
}
// Создание окна
hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
"Title Demo", "D3D_TitleScreen",
WS_OVERLAPPEDWINDOW, 0, 0,
g_iWindowWidth, g_iWindowHeight,
NULL, NULL, hInstance, NULL);
// Отображение окна
ShowWindow(hWnd, iCmdShow);
// Получение размеров клиентской области
GetClientRect(hWnd, &rcWindowClient);
// Вычисление смещения визуализации на основе размеров клиентской области
g_iXOffset = (g_iWindowWidth - (rcWindowClient.right - rcWindowClient.left));
g_iYOffset = (g_iWindowHeight - (rcWindowClient.bottom - rcWindowClient.top));
// Изменение размеров окна, чтобы они соответствовали желаемому разрешению
SetWindowPos(hWnd, NULL, 0, 0,
g_iWindowWidth + g_iXOffset, // Ширина
g_iWindowHeight + g_iYOffset, // Высота
NULL);
// Очистка структуры сообщения
ZeroMemory(&msg, sizeof(msg));
// Инициализация Direct3D
if(SUCCEEDED(InitD3D(hWnd))) {
// Инициализация виртуального буфера для отображения четырехугольников
vInitInterfaceObjects();
// Вход в цикл сообщений
while(msg.message != WM_QUIT) {
if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
// Визуализация сцены
vRender();
}
}
}
// Освобождение ресурсов и выход из приложения
vCleanup();
UnregisterClass("Title Demo", wndclass.hInstance);
return 0;
}

Первым интересным фрагментом кода является вызов функции GetClientRect(). Вы можете недоумевать, почему она внесена в список. Для лучшего понимания позвольте мне рассказать о некоторых особенностях написания программ для работы в оконном режиме.

Вычисление смещения клиентской области
Вы должны помнить, что у функции CreateWindowEx() есть параметры, задающие размер создаваемого окна. Эти параметры позволяют создать окно необходимого размера. Проблема заключается в том, что при этом не учитывается наличие у окна заголовка. В результате, если у вашего окна есть заголовок, вы получите меньше пространства для визуализации, чем рассчитывали.
Обратите внимание, что высота заголовка равна 24 точкам. В результате высота видимой области визуализации будет составлять только 456 точек, что представляет проблему как для художника, так и для программиста. Поскольку все ваши вычисления для визуализации графического интерфейса пользователя базируются на размерах экрана, проблема должна быть решена. Функция GetClientRect() — ваш выход.

Функция GetClientRect() сообщает вам действительный размер видимой области визуализации. Вооружившись этими размерами вы можете настроить размеры окна, чтобы устранить несоответствие. Для этого сперва надо взять желаемый размерокна и вычесть из него размер клиентской области, возвращенный функцией GetClientRect(). Получив два числа вы изменяете размер окна, делая его равным сумме желаемого размера и полученного на предыдущем этапе числа. Вот как выглядят формулы этого трюка:

Новый размер окна по X =
(Желаемый размер по X) + ((Желаемый размер по X) - (Размер клиентской области по X))

Новый размер окна по Y =
(Желаемый размер по Y) + ((Желаемый размер по Y) - (Размер клиентской области по Y))

Для примера, изображенного на рис. 6.13 формулы работают следующим образом:

Новый размер окна по X = 640 + (640 - 640)

Новый размер окна по Y = 480 + (480 - 456)

Ширина окна остается равной 640 точкам, а высота становится равной 504 точкам. Теперь размеры области визуализации в окне достаточны для корректного отображения изображений размером 640 на 480 точек.
Функция InitD3D()
Функция InitD3D() занимается трудной задачей создания среды визуализации Direct3D. Используемый в этом примере код выглядит очень простым, если его сравнивать с полноценной процедуой инициализации Direct3D. Например, я не потрудился перечислить доступные видеоадаптеры и устройства. Код просто настраивает среду выполнения и надеется, что все будет хорошо. Если у вас инициализация экрана не выполняется, можно попробовать изменить код. Это должно сработать, поскольку основная часть кода инициализации взята из DirectX SDK. Хватит предостережений, давайте взглянем на код функции:

HRESULT InitD3D(HWND hWnd)
{
D3DPRESENT_PARAMETERS d3dpp;
D3DXMATRIX matproj, matview;
D3DDISPLAYMODE d3ddm;

// Создание объекта D3D
if(NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
return E_FAIL;

// Получение видеорежима, используемого рабочим столом, чтобы мы могли
// установить такой же формат для вторичного буфера
if(FAILED(g_pD3D->GetAdapterDisplayMode(
D3DADAPTER_DEFAULT, &d3ddm)))
return E_FAIL;

// Создание вторичного буфера и установка формата
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3ddm.Format;
d3dpp.EnableAutoDepthStencil = FALSE;

// Создание D3DDevice
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
DS3DDEVTYPE_HAL, hWnd,
3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice)))
{
return E_FAIL;
}

// Установка двухмерного представления и состояния визуализации
D3DXMatrixIdentity(&matview);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &matview);

// Установка ортогональной проекции, т.е двухмерная графика в трехменом пространстве
D3DXMatrixOrthoLH(&matproj, (float)g_iWindowWidth, (float)g_iWindowHeight, 0, 1);
// Задание матрицы проецирования
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matproj);
// Выключение отбраковки
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// Выключение освещения
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
// Выключение Z-буфера
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

return S_OK;
}

Первым интересным моментом, который можно заметить на рисунке, является вызов единственной функции верхнего уровня с именем Direct3DCreate9(). После этого вызова все оставшиеся функции относятся к указателю на объект Direct3D с именем g_pD3D. Он обрабатывает все остальные, относящиеся к трехмерной графике вызовы функций, которые будут сделаны во время жизненного цикла программы.

Создание объекта Direct3D
Функция Direct3DCreate9() является самой важной в Direct3D, поскольку без нее ничего не сможет произойти. Это самая первая функция, которую вы вызываете в процессе настройки среды визуализации. Главной задачей функции является создание объекта IDirect3D9. Вот как выглядит ее прототип:

IDirect3D9 *Direct3DCreate9(
UINT SDKVersion
);

Вы должны полюбить его за простоту. Единственный параметр, являющийся беззнаковым целым числом, указывает используемую вами версию DirectX SDK. Для этого параметра всегда следует использовать значение D3D_SDK_VERSION.

Параметр предназначен для проверки кода программы на отсутствие тривиальных ошибок. Фактически, выполняется проверка переданного номера версии и его сравнение с номером версии, хранящимся в заголовочных файлах DirectX. Если номера версий не совпадают, код знает, что что-то в установленном DirectX вызывает подозрения. Не заморачивайте себе этим голову; лучше всего оставьте эту функцию в покое и используйте рекомендованное значение.

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

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

HRESULT GetAdapterDisplayMode(
UINT Adapter,
D3DDISPLAYMODE *pMode
);

У функции есть два параметра, Adapter и pMode. Параметр Adapter должен содержать порядковый номер используемой видеокарты. Поскольку в компьютере могут быть установлены несколько видеокарт, важно указать правильное значение параметра. Проще всего ограничиться поддержкой одного монитора, задав значение параметра равным D3DADAPTER_DEFAULT. В этом случае система будет использовать первичный видеоадаптер, который в системах с одним монитором является единственным устройством отображения.

Второй параметр, pMode, вызывает больше вопросов, поскольку является указателем на структуру данных D3DDISPLAYMODE. После завершения работы функции эта структура данных будет содержать информацию о текущем видеорежиме. На повестку дня выностися вопрос: «Как выглядит структура данных D3DDISPLAYMODE?» А вот и ответ на него:

typedef struct _D3DDISPLAYMODE {
UINT Width;
UINT Height;
UINT RefreshRate;
D3DFORMAT Format;
} D3DDISPLAYMODE;

Первый член структуры, Width, хранит ширину экрана в пикселях.

Второй член структуры, Height, хранит высоту экрана.

Третий член структуры, RefreshRate, содержит частоту кадров текущего видеорежима. Если его значение равно 0, значит установлено значение частоты по умолчанию.

Четвертое значение, Format, значительно сложнее, чем предыдущие. Эта переменная является перечислением типа D3DFORMAT и может содержать различные значения, описывающие формат поверхности для текущего видеорежима. Существует слишком много доступных значений, чтобы перечислять их здесь, так что за дополнительной информацией я рекомендую обращаться к документации DirectX SDK.

Теперь нам известен формат вторичного буфера и настало время инициализировать структуру параметров отображения. Следующий блок кода присваивает значения элементам структуры данных D3DPRESENT_PARAMETERS. Данная структура содержит значения различных параметров, необходимые для инициализации системы визуализации. Вот как выглядит ее описание:

typedef struct _D3DPRESENT_PARAMETERS_ {
UINT BackBufferWidth;
UINT BackBufferHeight;
D3DFORMAT BackBufferFormat;
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType;
DWORD MultiSampleQuality;
D3DSWAPEFFECT SwapEffect;
HWND hDeviceWindow;
BOOL Windowed;
BOOL EnableAutoDepthStencil;
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags;
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
} D3DPRESENT_PARAMETERS;

Первые два элемента структуры, BackBufferWidth и BackBufferHeight, достаточно просты; они просто хранят размеры буфера.

Следующий член данных, BackBufferFormat, содержит используемый для вторичного буфера формат поверхности. Здесь мы воспользуемся тем самым форматом, который получили ранее при вызове функции GetAdapterDisplayMode().

Затем мы задаем тип множественной выборки, хранящийся в переменной MultiSampleType. Эта переменная имеет тип D3DMULTISAMPLE_TYPE. Ух ты — еще одно перечисление!