Загрузка...
 
Печать

DirectX 9 Graphics. Начало работы

Язык программирования: С++
Платформа: Win32
DirectX 9 Graphics:
  • Является одним из компонентов DirectX (версии 8 и более поздних), отвечающим за вывод на экран 2D и 3D-графики.1
  • В более ранних версиях DirectX состоял из двух отдельных компонентов: Direct3D и DirectDraw.
Закрыть
noteОбрати внимание

В литературе по DirectX Graphics и в исходных кодах примеров можно встретить множество отсылок именно к Direct3D. Часто пишут Direct3D, подразумевая его более современный аналог DirectX Graphics. И наоборот. Таким образом часто (но не всегда) эти два названия синонимичны.

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

Содержание



Intro

Прежде чем начать работать с DirectX Graphics, его необходимо грамотно инициализировать. В нюансах данный процесс изменяется от версии к версии DirectX. Но суть остаётся та же.
Мы изучим применение бэкбуферов для получения на экране плавной анимации. За основу возьмём исходный код базового приложения Windows из статьи Создание приложений Сpp Win32, в который позднее значительно модифицируем. В статье подробно изложены шаги, в ходе выполнения которых обычное приложение, показывающее окно с белым фоном, превращается во фреймворк (=скелет) игрового движка. Буквально на глазах у читателя обычное приложение превратится в игровое. К концу данной статьи мы напишем Windows-приложение, которое:
  • инициализирует Direct3D,
  • создаёт объект устройства (device object) Direct3D,
  • меняет цвет фона окна (с белого на синий) средствами DiretctX,
  • запрашивает у пользователя старт в полноэкранном режиме,
  • закрывает окно по нажатию Esc на клавиатуре.
Код инициализации DirectX-компонентов будет вынесен в отдельный файл исходного кода (со своим одноимённым заголовочным .h-файлом).
Для компиляции примера на понадобится:
  • MS Visual C++ 2010 Express,
  • Microsoft DirectX SDK 9.
Всё легко гуглится + есть в разделе "Софт" нашего сайта.
В конце статьи найдёшь ссылку на исходные коды примера (ZIP-архив с проектом для MSVC++2010).
Для успешного применения Direct3D, или любого другого компонента DirectX, важно знать, как правильно обращаться с подключаемыми библиотеками (libs; здесь размещаются сами функции) функций и их заголовочными файлами (headers; здесь размещаются интерфейсы вызова функций). Если кратко, то с каждой статичной библиотекой в наборе идёт заголовочный файл (с тем же именем), который легко правится в текстовом редакторе и содержит вызовы функций, содержащиеся в библиотеке. DirectX 9 хранит свои функции (большую часть) в библиотеке d3d9.lib. И, чтобы наше приложение их "увидело", в одном из файлов исходного кода проекта (или заголовке) включают вот такую ссылку на заголовочный файл d3d9.h:
#include <d3d9.h>


Интерфейсы Direct3D (DirectX 9)

Для написания Direct3D-приложений сперва создают две переменные:
  • одну для интерфейса Direct3D (LPDIRECT3D9)
  • и одну для объекта графического устройства (graphic device; LPDIRECT3DDEVICE9).
Вот пример:
LPDIRECT3D9 d3d = NULL;
LPDIRECT3DDEVICE9 d3ddev = NULL;


Префикс LP в именах объектов Direct3D

  • Означает "long pointer" (англ. "длинный указатель").
Таким образом объект LPDIRECT3D9 является длинным указателем на объект Direct3D9. Это и многие другие определения описаны в заголовочном файле d3d9.h, что расположен в подкаталоге include установленного DirectX SDK (в нашем случае (Win7 хб4): C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include). Вот его фрагмент:
Фрагмент d3d9.h
typedef struct IDirect3D9 *LPDIRECT3D9

На первых порах указатели могут запутать начинающих программеров. Но только до тех пор, пока те не начнут с ними плотно работать. Сейчас просто запомни:
  • IDirect3D9 - это сам интерфейс,
  • LPDIRECT3D9 - это длинный указатель на него.
По аналогии LPDIRECT3DDECICE9 является длинным указателем на интерфейс IDirect3DDevice9.
Закрыть
noteОбрати внимание

В литературе и исходных кодах примеров чаще можно встретить прямое указание интерфейсов, без длинных указателей. Но Microsoft рекомендует юзать именно длинные указатели. По крайней мере во всех примерах DirectX 9 SDK используются именно они.


Объект Direct3D (LPDIRECT3D9)

  • Является своеобразным "большим босом" библиотеки Diretct3D, т.к. он находится на вершине иерархии и контролирует абсолютно всё.
  • Главный (и обычно единственный) объект класса IDirect3D.
  • Создаётся на основе СОМ-интерфейса IDirect3D9.
  • Обычно инициализируется в начале кода приложения и удаляется при его закрытии.
  • Часто объявляется глобальным.
  • Служит для создания на его основе других объектов.
В случае его присутствия в коде, приложение как будто говорит: "Эй, смотри! Я приложение Direct3D!"
  • Инициализируется так:
d3d = Direct3DCreate9(D3D_SDK_VERSION);

,где D3D_SDK_VERSION - макрос, получающий актуальную версию DirectX SDK автоматически. По завершении выполнения функция возвращает указатель на валидный (=действующий) объект Direct3D.
После этого созданный объект (=инстанс) становится полностью готовым к применению. С этого момента обычное Windows-приложение становится Direct3D-приложением.2

Структура D3DPRESENT_PARAMETERS (DirectX 9)

  • Хранит параметры представления ( = презентации) создаваемого объекта устройства (device object) IDirect3DDevice9.
  • Указывает на то, как именно будет работать с графикой будущий объект устройства.
  • Создаётся и заполняется ДО вызова функции создания объекта устройства Direct3D (CreateDevice).
Объект устройства Direct3D основывается на данных структуры D3DPRESENT_PARAMETERS и не может быть создан без неё. Вот её шаблон:
Структура 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, *LPD3DPRESENT_PARAMETERS;

Вот описание её параметров:
ПАРАМЕТР ОПИСАНИЕ
BackBufferWidth Ширина бэкбуфера (в пикселах). 0 для оконного приложения.
BackBufferHeight Высота бэкбуфера (в пикселах). 0 для оконного приложения.
BackBufferFormat Формат цветности пикселей бэкбуфера. Например D3DFMT_R8G8B8 - 24 бит, по 8 бит на цвет (красный, зеленый и голубой).
BackBufferCount Кол-во бэкбуферов.
MultiSampleType Кол-во уровней мультисемплирования для полноэкранного сглаживания (antialiasing). Типичное значение здесь D3DMULTISAMPLE_NONE.
MultiSampleQuality Качество мультисемплирования. Типичное значение здесь 0.
SwapEffect Метод свопа (перелистывания, смены) бэкбуферов. Обычно D3DSWAPEFFECT_DISCARD.
hDeviceWindow Родительское (=основное для вывода) окно для данного объекта устройства.
Windowed Булево значение, назначающее запуск в окне либо в полноэкранном режиме. Ставь TRUE для запуска игры в окне.
EnableAutoDepthStencil Булево значение, разрешающее Direct3D контролировать глубину экранных буферов. Обычно ставят в TRUE.
AutoDepthStencilFormat Формат цветности экранных буферов.
Flags Дополнительные флаги. Обычно ставят в 0.
FullScreen_RefreshRateInHz Частота обновления при работе в полноэкранном режиме. В случае запуска в окне выставляем 0!
Presentationlnterval Контролирует частоту смены (свопов) экранных буферов.

Перед применением её создают и инициализируют:
D3DPRESENT_PARAMETERS d3dpp;

Имя произвольное.
Затем сразу обнуляют все её члены:
ZeroMemory(Sd3dpp, sizeof(d3dpp));

В шаблоне структуры D3DPRESENT_PARAMETERS указано 14 параметров. На деле в самом простом случае (оконный режим) можно указать только 4:
// Заполняем структуру D3DPRESENT_PARAMETERS
D3DDISPLAYMODE	d3ddm;

if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
	return FALSE;

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(&d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3ddm.Format;
d3dpp.EnableAutoDepthStencil = FALSE;


Объект Direct3D Device Object (LPDIRECT3DDEVICE9)

  • Создаётся на основе СОМ-интерфейса IDirect3DDevice9.
  • Берёт свои настройки из структуры параметров презентации D3DPRESENT_PARAMETERS (см. выше).
  • Представляет собой программное воплощение (прослойку) одной видеокарты ПК.
Если в ПК установлено две и более видеокарты (либо одна многоядерная) для каждой из них необходимо создавать отдельный объект устройства Direct3D.
После создания объекта Direct3D создаётся программный объект устройства (Direct3D device object), через который и будет рендериться вся графика. Вот пример создания объекта устройства Direct3D путём вызова функции CreateDevice:
d3d->CreateDevice(
	D3DADAPTER_DEFAULT, // Используем видеокарту, установленную в Windows по умолчанию (= в качестве основной).
	D3DDEVTYPE_HAL, // Используем аппаратный рендеринг (Hardware Abstraction Layer).
	hWnd, // Дескриптор) окна
	D3DCREATE_SOFTWARE_VERTEXPROCESSING, // Не использовать "фишки" T&L для лучшей совместимости с бОльшим числом видеоадаптеров.
	&d3dpp, // Указатель на заранее заполненную структуру параметров отображения (D3DPRESENT_PARAMETERS).
	&d3ddev // Указатель на создаваемый объект устройства Direct3D.
	);

В последних двух параметрах стоит &d3dpp (указатель на заранее заполненную структуру параметров отображения) и &d3ddev (собственно, имя новосозданного объекта устройства).

Что такое T&L?

T&L (англ. Transform & Lighting - трансформация и освещение) - это аппаратная поддержка видеокартой манипуляций с полигонами + добавление к ним эффектов освещения (Не путать с RTX!).
Появление первых видеоадаптеров от компании 3Dfx произвело революцию на рынке компьютерных игр. Именно тогда принцип "хочешь играть - купи мощный компьютер" сменилось на "хочешь играть - купи видеокарту с 3D-ускорителем". Далее эстафету переняла NVIDIA, в начале 2000-ных явив миру T&L. Уже к 2002 году все более или менее игровые видеокарты шли с обязательной поддержкой T&L.
Суть технологии - перенести расчёт трансформаций полигонов и (часть) освещения с центрального процессора на видеокарту. При этом аппаратные (т.е. рассчитанные видеокартой) сцены как правило выглядят в разы эффектнее. Кроме того использование T&L значительно разгружает процессор (CPU), что позволяет загрузить его чем-нибудь ещё (например наделить ботов искусственным интеллектом или реализовать в игре крутую физику). Во многом благодаря этому современные игры стали в разы эффектнее.
Сегодня едва ли можно найти компьютер с видеокартой, не поддерживающей аппаратный T&L. Если, конечно, это не "жёсткий китай" с Али.
Так вот. DirectX 9 позволяет:
  • Включать аппаратную поддержку T&L (если поддерживается видеокартой).
  • Программно эмулировать T&L с переложением расчётов на процессор (CPU).
Это осуществляется в функции CreateDevice, путём указания в четвёртом параметре одного из вариантов:
  • D3DCREATE_SOFTWARE_VERTEXPROCESSING (для программной эмуляции),
  • D3DCREATE_HARDWARE_VERTEXPROCESSING (для аппаратной обработки).
В нашем случае для простоты изложения мы выбрали программную обработку вершин D3DCREATE_SOFTWARE_VERTEXPROCESSING.

Аппаратная (HAL) и программная (Reference Device) обработка графики

Во втором параметре функции CreateDevice задаётся программная, либо аппаратная обработка графики создаваемым объектом устройства Direct3D.

Аппаратная обработка (HAL device)
Поддерживает только те технологии, которые "знает" и поддерживает видеокарта. Рекомендуемый тип объекта устройства для современых ПК. Активирует аппаратное ускорение графики, предусмотренное большинством видеокарт с поддержкой DirectX9 (и выше). HAL - Hardware Abstraction Layer (слой аппаратной абстракции). Аппаратная обработка эффектов даёт очень красивый результат при рендеринге.

Программная обработка (Reference Device)
Поддерживает абсолютно все фишки Direct3D. Применяется в случае, когда какие-либо эффекты не поддерживаются данной видеокартой. Тогда их расчёт производится центральным процессором (CPU), что заметно снижает быстродействие. Чаще применяется разработчиками для тестов.

В данной статье (как видно из кода реализации функции CreateDevice) мы работаем с HAL-устройством.

Пример приложения инициализирующего объект устройства Direct3D (DirectX 9).

Создадим простой Проект приложения, инициализирующего объект устройства Direct3D (DirectX 9). За основу возьмём исходный код базового приложения Windows из статьи Создание приложений Сpp Win32, который затем слегка дополним.
Перед началом проверь, что у тебя установлены следующие программные компоненты:
  • MS Visual C++ 2010;
  • DirectX SDK 9
  • Windows SDK (для программирования под Win7/8/10).
Все эти штуки:
  • + инструкции по их установке ты найдёшь в разделе "Софт" нашего сайта;
  • Бесплатны;
  • Без труда гуглятся.

Создаём Проект приложения D3D9Init01

  • Создай пустой Проект с именем D3D9Init01.
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта D3D9Init01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем исходный код простейшего оконного приложения, взятого из статьи Создание приложений Сpp Win32:
WinMain.cpp
// Файл: WinMain.cpp
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow); 

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно белым цветом
	wndClass.lpszMenuName=NULL;	// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",		// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,	// Обычное окно с кнопками в правом верхнем углу.
		0, 0,				// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}

	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

  • Сохрани Решение (File -> Save All).

Готовим Проект D3D9Init01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта, созданного в MSVC++2010.

Выбираем многобайтовую кодировку

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.

Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9Init01.
В Обозревателе решений видим: "Решение "D3D9Init01"", а строкой ниже жирным шрифтом название Проекта (тоже D3D9Init01).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта D3D9Init01.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все)

Отключаем инкрементную компоновку (incremental linking)

Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:
Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден


Закрыть
warningОшибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.

ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework(external link), установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".

ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.

Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq(external link). После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).


Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта. Обычно с тем же названием (если не менял вручную).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
  • Жмём ОК.

Удаляем файл cvtres.exe

В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe
В Win32-кодинге он особо ни на что не влияет. Зато без него линковка идёт как по маслу.

Отключаем использование компоновщиком библиотеки libci.dll

Да, даже на данном этапе компиляция Проекта выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно, и в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.
ОК, начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).
В правой части, внизу, видим поле ввода "Дополнительные параметры".
  • Пишем в него строку: /NODEFAULTLIB:libci
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект D3D9Init01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
Image
Если весь код был введён без ошибок, после компиляции запустится приложение, отображающее окно с белым фоном.
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug .
Закрыть
noteОбрати внимание

Если попытаться запустить скомпилированный .exe-файл на компьютере без установленной MSVC++2010, то экзешник (скорее всего) "ругнётся" на отсутствующую библиотеку MSVCR100D.dll. Буква D в её имени означает Debug, т.е. данное приложение скомпилировано с отладочной конфигурацией (профилем), которая выставляется по умолчанию у каждого вновь создаваемого Проекта/Решения. При релизе игры приложение напротив, компилируют с конфигурацией Release (релиз). При этом при создании инсталлятора в дистрибутив с игрой добавляют т.н. набор библиотек времени выполнения (в нашем слчае MS Visual C++ 2010 Runtime Redistributable). Он устанавливается вместе с игрой, т.к. без него игра тупо не стартанёт. Если для Release-профиля такой набор нетрудно нагуглить (например здесь: https://www.mydigitallife.net/visual-c-2010-runtime-redistributable-package-x86-x64-ia64-free-download(external link), 4,8 Мб для 64-разрядной версии ОС), то для запуска Debug-версии на отдельном компе на него потребуется установить целую MSVC++2010. Всё так сложно в том числе с целью не допустить утечек предрелизных разработок с компов игрокодерских компаний. Релиз есть релиз. А с дебаг-версиями, как правило, работают только сами игрокодеры, на своих компах отлавливая ошибки.
Конфигурации Debug и Release легко переключаются на странице свойств открытого Проекта (в MSVC++2010 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список "Конфигурация").


Добавляем в Проект функцию GameInit, инициализирующую Direct3D (DirectX 9)

Функция GameInit является авторской функцией-обёрткой Direct-X-функций. В DirectX её никогда не было. В ней мы аккуратно разместим реализации функций инициализации DirectX + небольшой таймер для замеров производительности. Её вызов мы разместим сразу после показа окна, но ДО начала процедуры выборки сообщений. Название GameInit произвольное (у одного автора она называлась DoInit). Главное - чтобы по имени функции можно было понять её назначение.
Закрыть
noteПримечание

По аналогии нетрудно придумать "говорящие" имена других гипотетических функций (GamePreProc, GameDraw, GamePostProc, GameEnd и т.д.), которые можно реализовать в будущем движке при необходимости.

Но сперва проинициализируем Direct3D.

Добавляем заголовки d3d9.h и time.h

Заголовочный файл d3d9.h нужен для вызова функций DirectX 9. Он расположен в подкаталоге Include установленного DirectX SDK 9 (в нашем случае C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include).
time.h - заголовочный файл из стандартной библиотеки С++. Расположен в подкаталоге Include установленной MS Visual С++ 2010 Express (в нашем случае C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include). В принципе с DirectX никак не связан. Но игрокодеры часто его применяют для замера времени подготовки и вывода очередного кадра.
  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков:
Фрагмент WinMain.cpp
...
#include <windows.h>
...

  • Ниже строки #include <windowsx.h> добавь следующие строки:
#include <d3d9.h>
#include <time.h>

  • Сохрани Решение (Файл->Сохранить все).

Создаём объект класса IDirect3D9 и объект устройства Direct3D

Без них функция GameInit не будет работать.
  • В WinMain.cpp найди строку объявления прототипа функции (обратного вызова) обработки сообщений WindowProc:
Фрагмент WinMain.cpp
...
// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
...

Забегая вперёд, скажем, что реализация оконной процедуры WindowProc в нашем случае размещена в самом конце WinMain.cpp . Это обычная практика.
Но вернёмся к области объявлений. Код будет намного читабельнее, если разместить все объявления в одном месте исходника.
В нашем случае в области глобальных объявлений стоит всего 1 строка - объявление оконной процедуры.
  • Чуть ниже объявления оконной процедуры (LRESULT CALLBACK WindowProc...) размести строки создания объекта класса IDirect3D9 и объекта устройства Direct3D соответственно:
// Объекты Direct3D (DirectX 9)
IDirect3D9 *d3d = NULL;
IDirect3DDevice9 *d3ddev = NULL;

// Разрешение экрана
int iWidth = 800;
int iHeight = 600;

Вообще имена экземпляров d3d и d3ddev выбираются программером произвольно и сильно варьируются у разных авторов. В данном уроке оставь их такими.
Значения по умолчанию у обоих объектов сразу выставляются в NULL.
Переменные iWidth (ширина) iHeight (высота) будут активно применяться для расчёта размеров окна. Конечно, они задают лишь начальные размеры окна и, при необходимости, позднее их можно будет изменить. Причём как вручную (в коде), так и программно (например средствами DirectX).

Указываем переменные iWidth и iHeight в параметрах функции CreateWindow()

В базовом Windows-приложении размер будущего окна вычисляется из текущего разрешения Рабочего стола. Т.е. окно будет раскрываться на весь рабочий стол. При желании вызовы API-функций GetSystemMetrics можно разместить на месте объявлений iWidth и iHeight. Но для наглядности мы проставим переменные iWidth и iHeigth, объявленные в начале листинга, везде, где это потребуется. Согласно замыслу, в полноэкранный режим окно будет переводить именно DirectX. Но об этом позже.
  • В WinMain.cpp найди следующий фрагмент:
Фргамент WinMain.cpp
...
	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",		// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,	// Обычное окно с кнопками в правом верхнем углу.
		0, 0,				// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные
...

  • Замени параметр GetSystemMetrics(SM_CXSCREEN) на iWidth.
  • Замени параметр GetSystemMetrics(SM_CYSCREEN) на iHeight.
  • Сохрани Решение (File -> Save All).

Пишем объявление функции GameInit

  • В самом начале WinMain.cpp найди список подключаемых заголовков:
Фргамент WinMain.cpp
...
#include <windows.h>
#include <d3d9.h>
#include <time.h>
...

  • Ниже строки #include <time.h> добавь объявление функции GameInit:
// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.

  • Сохрани Решение (File -> Save All).

Изменяем параметры функции ShowWindow

  • В WinMain.cpp найди следующий фрагмент:
Фргамент WinMain.cpp
...
	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
...

  • Замени строку ShowWindow... на следующую:
ShowWindow(hWnd, SW_NORMAL);


Пишем реализацию функции GameInit

В теории реализацию GameInit можно разместить в любом месте WinMain.cpp. Но, как правило, для наглядности, реализации больших или сторонних функций пишут в конце листинга. В нашем случае мы поступим так же.
  • В самом конце WinMain.cpp, после реализации WindowProc, набери код реализации функции GameInit:
HRESULT GameInit(HWND hWnd)
{
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
		
	// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm) ; // Установка параметров
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
	
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}

  • Сохрани Решение (File -> Save All).
Обрати внимание на структуру d3ddm. Сначала мы её инициализируем:
...
D3DDISPLAYMODE	d3ddm;
...

Потом заполняем текущими настройками рабочего стола:
...
d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, d3ddm) ; // Установка параметров
...

Потом присваиваем элементу структуры d3dpp.BackBufferFormat значение элемента той самой структуры текущих настроек рабочего стола (d3ddm.Format):
...
d3dpp.ВасkBufferFormat	= d3ddm.Format;
...


Пишем вызов функции GameInit

  • В WinMain.cpp найди процедуру выборки сообщений:
Фрагмент WinMain.cpp
...
	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}
...

  • Замени этот фрагмент кода на следующий:
// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	if(GameInit(hWnd) == TRUE)
	{
		while(TRUE)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				// Есть сообщение! Обработаем его как обычно...
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if(msg.message == WM_QUIT) break;
			}
		}
	}


Пишем вызов CoInitialize

  • В WinMain найди фрагмент:
Фргамент WinMain.cpp
...
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
...

  • Ниже строкой MSG msg; добавь строку:
CoInitialize(NULL);


Пишем вызов CoUninitialize

  • Чуть ниже реализации процедуры выборки сообщений найди код:
Фрагмент WinMain.cpp
...
	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
...

  • Перед вызовом UnregisterClass... добавь строку:
CoUninitialize ();

  • Сохрани Решение (File -> Save All).

Исходный код WinMain.cpp после всех внесённых изменений

WinMain.cpp
// Файл: WinMain.cpp
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow); 

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Объекты Direct3D (DirectX 9)
IDirect3D9 *d3d = NULL;
IDirect3DDevice9 *d3ddev = NULL;

// Разрешение экрана
int iWidth = 800;
int iHeight = 600;

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	CoInitialize(NULL);

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;	// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",		// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,	// Обычное окно с кнопками в правом верхнем углу.
		0, 0,				// Координаты X и Y
		iWidth,	// Ширина окна
		iHeight,	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

	if(!hWnd) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, SW_NORMAL);
	UpdateWindow(hWnd);

	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	if(GameInit(hWnd) == TRUE)
	{
		while(TRUE)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				// Есть сообщение! Обработаем его как обычно...
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if(msg.message == WM_QUIT) break;
			}
		}
	}

	CoUninitialize ();
	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

HRESULT GameInit(HWND hWnd)
{
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
		
	// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm) ; // Установка параметров
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
	
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}


Готовим Проект D3D9Init01 к перекомпиляции


Указываем пути к DirectX SDK 9

Если попытаться скомпилировать Проект D3D9Init01 в таком виде, то ничего не выйдет.
В единственном файле исходного кода WinMain.cpp можно увидеть директиву включения (#include) заголовочного файла d3d9.h:
Фрагмент WinMain.cpp
...
#include <d3d9.h>
...

На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Здесь всё делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9Init01.
  • Убедись, что DirectX SDK 9 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "D3D9Init01"", а строкой ниже жирным шрифтом название Проекта (тоже D3D9Init01).
  • Жмём правой кнопкой мыши по названию Проекта D3D9Init01. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC+ + . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) DirectX SDK 9
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 9 (include). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) DirectX SDK 9
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 9 (lib). В нашем случае это C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).
Готово.

Указываем пути к Windows SDK

Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо указать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:
$(WindowsSdkDir)include

В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но! Заголовочным файлам DirectX SDK "жизненно важно", чтобы они включались после включений заголовков Windows SDK.
Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).

В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express). Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK. Для путей к Windows SDK это делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта с тем же названием (если не менял вручную).
  • Жмём правой кнопкой мыши по названию Проекта. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++ . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас, как и в прошлый раз, интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".
Каталог включений DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\MicrosoftSDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна.
Каталог библиотек DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All). Готово.

Прописываем библиотеки d3d9.lib и WinMM.LIB в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Библиотека d3d9.lib расположена в папке с установленным DirectX SDK 9 (в нашем случае по пути C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86), пути к которому мы прописали выше.
Библиотека WinMM.lib отвечает за мультимедиа-возможности приложения и расположена в каталоге с установленным Windows SDK (в нашем случае здесь: C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib).
Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам данные библиотеки не ищет. Поэтому их необходимо указывать отдельно в окне настроек Проекта, в разделе "Компоновщик". ОК, начинаем.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
  • В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
  • В всплывающем списке жмём "Изменить".
  • В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем в столбик (один под другим) имена файлов трёх библиотек:
d3d9.lib
WinMM.lib
Image
Библиотека Winmm.lib отвечает за мультимедиа-возможности приложения и расположена в каталоге с установленным Windows SDK.
  • Жмём ОК, ОК.
  • Сохрани Решение (Файл->Сохранить все)

Перекомпилируем Проект D3D9Init01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми правой кнопкой по названию Проекта в Обозревателе проектов и выбирай "Перестроить".
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug .
  • Перейди в каталог Проекта и запусти перекомпилированное приложение.
На экране появится окно с белым фоном размером 800х600 точек.

Добавляем в Проект D3D9Init01 функцию рендеринга GameRun

Данная функция также является авторской функцией-обёрткой. В DirectX её никогда не было. По аналогии с GameInit, в ней мы аккуратно разместим реализации функций вывода изображения средствами DirectX. А именно - зальём фон окна определённым цветом. Вызов GameRun мы разместим внутри условного перехода while цикла выборки сообщений, дополнив условный переход блоком if...else. Но обо всём по порядку.

Пишем объявление функции GameRun

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди список подключаемых заголовков и чуть ниже объявление функции GameInit:
Фрагмент WinMain.cpp
...
// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.
...

  • Сразу под ними пишем объявление функции GameRun:
void GameRun(HWND);

  • Сохрани Решение (Файл -> Сохранить все).

Пишем реализацию функции GameRun

В теории реализацию GameRun можно разместить в любом месте WinMain.cpp. Но, как правило, для наглядности реализации больших или сторонних функций пишут в конце листинга. В нашем случае мы поступим так же.
  • В самом конце WinMain.cpp ниже реализации функции Gamelnit пишем реализацию функции GameRun:
void GameRun(HWND hWnd)
{
 // Проверяем, что объект устройства Direct3D создан и готов к работе.
 if(d3ddev == NULL)
 return;

 // Очищаем бэкбуфер в синий цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0);

 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

  // Завершаем рендеринг.
  d3ddev->EndScene();
 }

 // Выводим содержимое бэкбуфера на экран.
 d3ddev->Present(NULL, NULL, NULL, NULL);
}

Функция Clear очищает бэкбуфер, заливая его синим цветом.
  • Сохрани Решение (Файл -> Сохранить все).

Пишем вызов функции GameRun

  • В WinMain.cpp найди фрагмент процедуры выборки сообщений, модифицированной выше:
Фрагмент WinMain.cpp
...
if(GameInit(hWnd) == TRUE)
	{
		while(TRUE)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				// Есть сообщение! Обработаем его как обычно...
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if(msg.message == WM_QUIT) break;
			}
		}
	}
...

  • Добавь в условном переходе if (PeekMessage... второй блок else, внутри которого размести вызов GameRun:
if(GameInit(hWnd) == TRUE)
	{
		while(TRUE)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				// Есть сообщение! Обработаем его как обычно...
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if(msg.message == WM_QUIT) break;
			}
			else
			{
				// Главный цикл игры (mainloop)
				GameRun(hWnd);
			}
		}
	}

  • Сохрани Решение (Файл -> Сохранить все).
Здесь сначала обрабатываются сообщения от ОС Windows, а затем выполняется критичный ко времени выполнения код (например рендеринг). Это, конечно, не отменяет необходимость корректного освобождения ресурсов Direct3D при завершении работы программы. Вот почему для этих целей мы создадим ещё одну функцию GameEnd. Но об этом читай ниже.

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".
Если весь код был введён без ошибок, по завершении перекомпиляции в логе в нижней части IDE будет следующая строка.
Перестроение всей: успешно: 1, с ошибками: 0, пропущено: 0

  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Init01\Debug) и запусти скомпилированный исполняемый файл D3D9Init01.exe .
На экране появится окно с синим фоном.

Добавляем в Проект D3D9Init01 функцию освобождения ресурсов GameEnd

Данная функция также является авторской разработкой. В DirectX её никогда не было. По аналогии с GameInit и GameRun, в ней мы аккуратно разместим реализации функций освобождения ресурсов DirectX.

Пишем объявление функции GameEnd

  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В самом начале WinMain.cpp найди объявления функций Gamelnit и GameRun:
Фрагмент WinMain.cpp
...
// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.
void GameRun(HWND);
...

  • Сразу под ними пишем объявление функции GameEnd:
void GameEnd(HWND);

  • Сохрани Решение (Файл -> Сохранить все).

Пишем реализацию функции GameEnd

В теории реализацию GameEnd можно разместить в любом месте WinMain.cpp. Но, как правило, для наглядности реализации больших или сторонних функций пишут в конце листинга. В нашем случае мы поступим так же.
  • В самом конце WinMain.cpp, сразу после реализации функции GameRun добавь реализацию функции GameEnd:
void GameEnd(HWND hWnd)
{
	// Показываем сообщение о закрытии.
	MessageBox(hWnd, "Программа завершает свою работу", "GameEnd", MB_OK);

	// Освобождаем ресурсы, выделенные ранее под объект устройства Direct3D.
	if(d3ddev != NULL)
		d3ddev->Release();
	
	// Освобождаем ресурсы, выделенные ранее под объект (инстанс) класса IDirect3D9.
	if(d3d != NULL)
		d3d->Release();
}

  • Сохрани Решение (Файл -> Сохранить все).

При завершении работы программы выскочит сообщение (MessageBox) с заданным текстом.

Пишем вызов функции GameEnd

  • В WinMain.cpp найди фрагмент реализации оконной функции обратного вызова:
Фрагмент WinMain.cpp
...
// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}
...

  • Перед строкой PostQuitMessage... набери код вызова функции GameEnd:
GameEnd(hWnd);

  • Сохрани Решение (Файл -> Сохранить все).
Функция очистки ресурсов GameEnd вызывается в случае (case) поступления сообщения WM_QUIT. Обычно оно генерируется при нажатии кнопки с крестиком в правом верхнем углу окна приложения либо при нажатии клавиш Alt+F4 на клавиатуре.

Исходный код WinMain.cpp после всех внесённых изменений

WinMain.cpp
// Файл: WinMain.cpp
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <d3d9.h>
#include <time.h>

// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.
void GameRun(HWND);
void GameEnd(HWND);

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow); 

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Объекты Direct3D (DirectX 9)
IDirect3D9 *d3d = NULL;
IDirect3DDevice9 *d3ddev = NULL;

// Разрешение экрана
int iWidth = 800;
int iHeight = 600;

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	CoInitialize(NULL);

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;	// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",		// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,	// Обычное окно с кнопками в правом верхнем углу.
		0, 0,				// Координаты X и Y
		iWidth,	// Ширина окна
		iHeight,	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

	if(!hWnd) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, SW_NORMAL);
	UpdateWindow(hWnd);

	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	if(GameInit(hWnd) == TRUE)
	{
		while(TRUE)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				// Есть сообщение! Обработаем его как обычно...
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if(msg.message == WM_QUIT) break;
			}
			else
			{
				// Главный цикл игры (mainloop)
				GameRun(hWnd);
			}
		}
	}

	CoUninitialize ();
	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		GameEnd(hWnd);
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

HRESULT GameInit(HWND hWnd)
{
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
		
	// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm) ; // Установка параметров
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
	
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}

void GameRun(HWND hWnd)
{
 // Проверяем, что объект устройства Direct3D создан и готов к работе.
 if(d3ddev == NULL)
 return;

 // Очищаем бэкбуфер в синий цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0);

 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

  // Завершаем рендеринг.
  d3ddev->EndScene();
 }

 // Выводим содержимое бэкбуфера на экран.
 d3ddev->Present(NULL, NULL, NULL, NULL);
}

void GameEnd(HWND hWnd)
{
	// Показываем сообщение о закрытии.
	MessageBox(hWnd, "Программа завершает свою работу", "GameEnd", MB_OK);

	// Освобождаем ресурсы, выделенные ранее под объект устройства Direct3D.
	if(d3ddev != NULL)
		d3ddev->Release();
	
	// Освобождаем ресурсы, выделенные ранее под объект (инстанс) класса IDirect3D9.
	if(d3d != NULL)
		d3d->Release();
}


Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".
Если весь код был введён без ошибок, по завершении перекомпиляции в логе в нижней части IDE будет следующая строка.
Перестроение всей: успешно: 1, с ошибками: 0, пропущено: 0

  • Перейди в каталог Проекта и запусти скомпилированный исполняемый файл D3D9Init01.exe .
Появится окно с синим фоном. При закрытии окна появится сообщение (MessageBox) с заданным текстом. Его появление свидетельствует о том, что программа корректно выполнила вызовы всех трёх авторских функций: Gamelnit, GameRun и GameEnd.
Закрыть
noteОбрати внимание

Задание вызова таких сообщений - отличный способ проверить успешность выполнения той или иной функции. Если сообщение появилось, значит весь код ДО его вывода выполнен успешно.

  • По желанию убери из кода WinMain.cpp фрагмент вызова окна сообщения:
Фрагмент WinMain.cpp
...
	// Показываем сообщение о закрытии.
	MessageBox(hWnd, "Программа завершает свою работу", "GameEnd", MB_OK);
...

Шаблонное приложение, инициализирующее Direct3D (DirectX 9), готово.

Выносим код инициализации Direct3D9 в отдельные файлы dxfunc.h и dxfunc.cpp

Делать это необязательно. Но мы сделаем, т.к. хранить весь код в одном исходнике неудобно уже сейчас. По мере наращивания функционала код фреймворка будет значительно увеличиваться, что, при размещении всего кода в WinMain.cpp, заметно снизит его (кода) читабельность. Модульность кода - один из признаков профессионализма.
В С++ принято:
  • в заголовочных .h-файлах хранить объявления переменных, функций и классов.
  • в .срр-файлах исходного кода с тем же именем хранить реализации функций и классов, объявленных в h-файле.
Сделаем так же. Создадим пару файлов dxfunc.h и dxfunc.cpp, в которых будем сохранять все объявления и реализации функций, связанных с DirectX. Причём это будут не только графические функции, но и ввод, звук, сеть и т.д. Идея подсмотрена в книге М. Фленова "Искусство программирования игр на С++".3

Создаём dxfunc.h

dxfunc.h содержит объявления DirectX-функций, реализованных в dxfunc.cpp.
ОК, приступаем.
  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" Проекта D3D9Init01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "dxfunc.h".
  • Жмём "Добавить".
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле dxfunc.h набираем следующий код:
dxfunc.h
#ifndef _DXFUNC_H_
#define _DXFUNC_H_

#include <d3d9.h>

HRESULT d3d9Init(IDirect3D9 **d3d, 
                IDirect3DDevice9 **d3ddev,
                HWND hWnd, 
				DWORD iWidth,
				DWORD iHeight,
				BOOL bFullScreen
				);

#endif

  • Сохрани Решение (Файл->Сохранить все).
Для кода инициализации Direct3D здесь объявлена отдельная авторская функция d3d9Init аж с шестью вводными параметрами. Первые 5 мы уже применяли выше. Шестой - булево значение, отслеживающее использование полноэкранного режима. При bFullScreen=TRUE приложение работает в полноэкранном режиме.

Создаём dxfunc.cpp

В заголовочном файле dxfunc.cpp содержатся реализации функций, объявленных в dxfunc.h.
ОК, приступаем.
  • Убедись, что MSVC++2010 запущена и вней открыт Проект D3D9Init01, созданный выше.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта D3D9Init01.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл С++ (.срр)" и в поле "Имя" введи "dxfunc.cpp".
  • Жмём "Добавить".
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле dxfunc.cpp набираем следующий код:
dxfunc.cpp
// Реализация DX-функций, объявленный в dnfunc.h

#include "dxfunc.h"

НRESULT d3d9Init(IDirect3D9 **d3d, IDirect3DDevice9 **d3ddev,
	HWND hWnd, DWORD iWidth, DWORD iHeight, BOOL bFullScreen)
{
	//
}

Код "тела" функции возьмём из WinMain.cpp.
  • В WinMain.cpp найди фрагмент реализации функции Gamelnit:
Фрагмент WinMain.cpp
...
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
		
	// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm);

       // Установка параметров оконного режима
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
	
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
...

  • Вырежи его в буфер обмена (Ctrl+X).
  • Вставь в теле реализации функции d3d9Init файла dxfunc.cpp:
dxfunc.cpp
// Реализация DX-функций, объявленный в dnfunc.h

#include "dxfunc.h"

НRESULT d3d9Init(IDirect3D9 **d3d, IDirect3DDevice9 **d3ddev,
	HWND hWnd, DWORD iWidth, DWORD iHeight, BOOL bFullScreen)
{
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
		
	// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	(*d3d)->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm);
	
	// Установка параметров оконного режима
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
	
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
}

  • Сохрани Решение (Файл->Сохранить все).
Реализация функции GameInit (WinMain.cpp) заметно уменьшилась. По сути там осталась лишь функция инициализации таймера:
Фрагмент WinMain.cpp
...
HRESULT GameInit(HWND hWnd)
{	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}
...


Корректируем указатели на d3d и d3ddev

В dxfunc.cpp в параметрах функции d3d9Init объекты d3d и d3dev указаны в виде "указателя на указатель" (т.е. с двойными астериксами (**). Это требует внесение ряда исправлений в вызовы важных DX-функций.
  • В dxfunc.cpp найди фрагмент:
Фрагмент dxfunc.cpp
...
	// Инициализируем Direct3D
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		return E_FAIL;
	}
...

  • Замени его на следующий:
// Инициализируем Direct3D
	if ((*d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
	{
		return E_FAIL;
	}

  • В dxfunc.cpp найди фрагмент:
Фрагмент dxfunc.cpp
...
d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm); // Установка параметров
...

  • Замени его на следующий:
(*d3d)->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm); // Установка параметров

  • В dxfunc.cpp найди фрагмент:
Фрагмент dxfunc.cpp
...
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev) ) )
	{
		return E_FAIL;
	}
...

  • Замени его на следующий:
if(FAILED((*d3d)->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, d3ddev)))
 {
	 return E_FAIL;
 }

  • Сохрани Решение (Файл->Сохранить все).

Изменяем тип функции Gamelnit

По разным причинам вызывать d3d9Init напрямую сильно не желательно. Например потому, что d3d9Init имеет тип HRESULT, а при вызове она проверятся TRUE/FALSE. В этом случае мы используем нашу авторскую функцию GameInit в качестве функции-обёртки (wrapper-function) для функции d3d9Init. Но для этого изменим её тип (=тип возвращаемого значения) на BOOL.
  • В WinMain.cpp найди фрагмент:
Фрагмент WinMain.cpp
...
// Здесь идут всякие объявления
HRESULT GameInit(HWND); // Инициализирует игру.
...

  • Измени тип функции GameInit с HRESULT на BOOL:
BOOL GameInit(HWND); // Инициализирует игру.

  • В WinMain.cpp найди фрагмент реализации функции GameInit:
Фрагмент WinMain.cpp
...
HRESULT GameInit(HWND hWnd)
{	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
...

  • Измени тип функции GameInit с HRESULT на BOOL:
HRESULT GameInit(HWND hWnd)


Добавляем в реализацию функции GameInit (WinMain.cpp) вызов функции d3d9Init

  • В WinMain.cpp найди фрагмент реализации функции GameInit:
Фрагмент WinMain.cpp
...
HRESULT GameInit(HWND hWnd)
{	
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}
...

  • Замени его на следующий:
BOOL GameInit(HWND hWnd)
{
	if(d3d9Init (&d3d, &d3ddev, hWnd, iWidth, iHeight, FALSE)!=S_OK)
	{
		MessageBox(hWnd, "Ошибка инициализации DirectX", "Error", MB_OK);
		return FALSE;
	}

	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
}


Добавляем в WinMain.cpp директиву #include dxfunc.h

  • В самом начале WinMain.cpp найди фрагмент со списком подключаемых заголовков:
Фрагмент WinMain.cpp
...
#include <windows.h>
#include <d3d9.h>
#include <time.h>
...

  • Строку #include <d3d9.h> замени на эту:
#include "dxfunc.h"

Ссылка на d3d9.h "переехала" в dxfunc.h. Подключаемый заголовок dxfunc.h указан в кавычках т.к. он расположен в одном каталоге с Проектом D3D9Init01.
  • Сохрани Решение (Файл->Сохранить все).

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".
Если весь код был введён без ошибок, по завершении перекомпиляции в логе в нижней части IDE будет следующая строка.
Перестроение все::: успешно: 1, с ошибками: 0, пропущено: 0

  • Перейди в каталог Проекта и запусти скомпилированный исполняемый файл D3D9Init01.exe .
На экране появится всё то же окно приложения с синим фоном.

Добавляем вариант запуска в полноэкранном режиме.

Автор Harbour J.S.1 рекомендует сначала разрабатывать игру в оконном режиме, а тестировать релиз в полноэкранном. В идеале "прикрутить" к окну приложения диалоговое окно, запрашивающее у пользователя нужный режим (и настройки видеографики). Но, в целях простоты изложения, сейчас мы не полезем в дебри бинарных ресурсов Windows, а лишь перепишем код таким образом, чтобы наше приложение при запуске выдавало сообщение (MessageBox) с вопросом, перейти ли в полноэкранный режим или остаться в оконном.

Изменения в функции d3d9Init (dxfunc.cpp)

  • В dxfunc.cpp найди фрагмент заполнения структуры D3DPRESENT_PARAMETERS:
Фрагмент dxfunc.cpp
...
// Заполняем основные параметры представления
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory ( &d3dpp, sizeof( d3dpp ) );
	
	d3dpp.BackBufferWidth = iWidth;
	d3dpp.BackBufferHeight = iHeight;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	
	// Доп. параметры для оконного режима
	
	// Получаем формат пикселя
	D3DDISPLAYMODE d3ddm;
	(*d3d)->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm);
	
	// Установка параметров оконного режима
	d3dpp.BackBufferFormat	= d3ddm.Format;
	d3dpp.SwapEffect		= D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed			= TRUE;
...

  • Замени его на следующий:
// Заполняем основные (общие) параметры представления
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof( d3dpp ) );

  d3dpp.BackBufferWidth = iWidth;
  d3dpp.BackBufferHeight = iHeight;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
  d3dpp.EnableAutoDepthStencil = TRUE;

  // Запрос на отображение в полноэкранном режиме
  int  iRes;
  if (!bFullScreen)
      iRes=MessageBox(hWnd, "Перейти в полноэкранный режим?", "Screen", MB_YESNO | MB_ICONQUESTION);
  else
	  iRes = IDYES;

  if(iRes == IDYES)
  {
      //////////////////////////////////////////////////////////
	  // Полноэкранный режим
      //////////////////////////////////////////////////////////
	  // Установка параметров полноэкранного режима
      d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
	  d3dpp.SwapEffect       = D3DSWAPEFFECT_FLIP;
	  d3dpp.Windowed         = FALSE;
      d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
      d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
  } 
  else 
  {
  // Доп. параметры для оконного режима
  // Получить формат пикселя
  D3DDISPLAYMODE d3ddm;
  (*d3d)->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

  // Установка параметров оконного режима
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
  d3dpp.Windowed         = TRUE;
  }

  • Сохрани Решение (Файл->Сохранить все).
Видим, что добавился вывод окна сообщения с кнопками Да и Нет. В WinMain.cpp функция d3d9Init вызывается с параметром bFullScreen=FALSE (т.е. в оконный режим). При нажатии "Да" выполняется первый блок условного перехода if...else и приложение переходит в полноэкранный режим, окрашивая экран в синий цвет. При этом процесс заполнения структуры D3DPRESENT_PARAMETERS для оконного режима остался без изменений.

Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".
Если весь код был введён без ошибок, по завершении перекомпиляции в логе в нижней части IDE будет следующая строка.
Перестроение все::: успешно: 1, с ошибками: 0, пропущено: 0

  • Перейди в каталог Проекта и запусти скомпилированный исполняемый файл D3D9Init01.exe .
Приложение покажет окно + сообщение с запросом на переход в полноэкранный режим. В полноэкранном режиме приложение также работает в исходном разрешении 800x600 точек, при необходимости растягивая изображение. При этом пользователь не видит кнопки закрытия окна. Ниже мы это исправим, добавив функцию завершения работы по нажатию Esc на клавиатуре. На данном этапе из программы в полноэкранном режиме можно выйти путём нажатия Windows-прерываний Alt+F4 или путём вызова диспетчера задач путём нажатия Ctrl+Alt+Delete.

Добавляем выход из программы по нажатию Esc на клавиатуре

  • В самом начале WinMain.cpp найди фрагмент макроопределений #define:
Фрагмент WinMain.cpp
...
#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.
...

  • Сразу после них добавь строки:
// Макрос асинхронного считывания нажатий клавиатуры
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1:0)

  • В WinMain.cpp найди фрагмент окончания реализации функции GameRun:
Фрагмент WinMain.cpp
...
// Выводим содержимое бэкбуфера на экран.
d3ddev->Present(NULL, NULL, NULL, NULL);
...

  • Ниже строки d3ddev->Present... добавь строки:
// При нажатии Esc на клавиатуре завершаем работу приложения
 if(КЕY_DOWN(VK_ESCAPE))
 {
	 PostMessage(hWnd, WM_DESTROY, 0, 0);
 }


Перекомпилируем Проект D3D9Init01

  • В Обозревателе Решений щёлкни правой кнопкой мыши (ПКМ) по названию активного Проекта (или Решения) D3D9Init01.
  • Во всплывающем контекстном меню выбери "Перестроить".
  • Перейди в каталог Проекта и запусти скомпилированный исполняемый файл D3D9Init01.exe .
Приложение покажет окно + сообщение с запросом на переход в полноэкранный режим. По нажатию Esc на клавиатуре приложение завершит работу, закрыв окно. ЗдОрово, что выход по нажатию Esc работает как в оконном, так и в полноэкранном режиме.

Ссылка на исходные коды примера

Исходные коды примера (ZIP-архив с проектом для MSVC++2010) забираем здесь(external link).

Заключение

В этой статье мы изучили:
  • интерфейсы объектов DirectX и их применение в реальном коде Windows-приложения;
  • функцию CreateDevice и её параметры;
  • структуру параметров презентации D3DPRESENT_PARAMETERS (для оконного и полноэкранного режима);
  • где именно в коде Windows-приложения размещаются функции инициализации (Gamelnit), процессинга (GameRun) и завершения работы (GameEnd) главного игрового цикла (gameloop).
Параллельно изучили применение модальных окон сообщений (MessageBox).

Источники


1. Harbour J.S. Beginning Game Programming. - Thomson Course Technology, 2005
2. Thorn A. DirectX 9 graphics: The definitive guide to Direct3D. - Wordware Publishing, 2005
3. Фленов M.E. Искусство программирования игр на С++. - СПб.: БХВ-Петербург, 2006


Последние изменения страницы Понедельник 06 / Июнь, 2022 13:53:59 MSK

Последние комментарии wiki

No records to display

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++