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

DirectX 9 Graphics. Поверхности и растры (Surfaces & Bitmaps)

Язык программирования: С++
Платформа: Win32
Закрыть
noteОбрати внимание

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

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

Содержание



Intro

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

Бэкбуфер (backbuffer) также является DirectDraw-поверхностью. Второе его название - вторичная внеэкранная поверхность (Secondary Offscreen Surface).

Монитор показывает то, что на него отправляет видеоадаптер. Видеоадаптер отображает содержимое фреймбуфера, которое отправляется на монитор по одному пикселю за раз (чаще всего это группа пикселей, но передаются они невероятно быстро).
Фреймбуфер хранится в памяти видеокарты (=видеопамяти), которую обычно ставят на базе максимально быстрых модулей RAM. В стародавние времена видеопамяти в видеокартах было очень мало и стоила она заметно дороже обычной оперативы. Сегодня ситуация не особо изменилась. Так вот. Фреймбуфер (frame buffer) "сидит" в видеопамяти и представляет собой конечную "картинку", которую пользователь видит на мониторе. Отсюда следует, что самый быстрый способ создавать графику - это просто напрямую редактировать фреймбуфер. В этом случае изменения будут отображаться на экране с минимальной задержкой. Но на деле напрямую во фреймбуфер не "рисуют", т.к. при смене кадров в этом случае заметно перелистывание ("моргание", flick). Вместо этого изображение формируют в специальном бэкбуфере, который затем целиком отправляют во фреймбуфер. DirectX поддерживает двойную (2 бэкбуфера, постоянно сменяющих друг друга) и тройную (3 бэкбуфера) буферизации. Также существует пара других методов смены кадров без моргания. Но в данной статье они не рассматриваются.

Первичная поверхность (Primary Surface)

  • Та, которая отправляется во фреймбуфер и оттуда - на монитор.
  • Обычно хранится в памяти видеоадаптера (=видеопамяти).
В статье DirectX 9 Graphics. Начало работы. Начало работы в одном из абзацев автор создаёт бэкбуфер и заполняет структуру PRESENTATION_PARAMETERS (параметры отображения). Далее, с помощью функции Clear он заливает бэкбуфер в определённый цвет и вызывает функцию Present для отображения кадра на экране. Это как раз и было применение двойного буфера (double buffering). В Direct3D уже "встроен" программный бэкбуфер. Оно и понятно, уже много лет без двойной буферизации не обходится ни одна игра. Упомянутый выше фреймбуфер (frame buffer) также называют передним (фронтальным) буфером (front buffer). Именно в него копируется содержимое бэкбуфера перед отправкой на экран очередного кадра. Оба буфера (бэк и фронтальный) создаются при вызове функции CreateDevice.

Вторичная внеэкранная поверхность (Secondary Offscreen Surface)

  • Представляет собой обычный массив в памяти, по своей структуре напоминающий растровое изображение (bitmap).
Как и у битмап-файла (.bmp), здесь есть заголовок (header) и данные, представляющие собой пиксели различного цвета. Внеэкранных поверхностей можно создать сколь угодно много. Обычно в процессе игры их создаётся несколько сотен. А всё из-за того, что вся графика в игре хранится в этих поверхностях, которые затем копируются на экран (т.н. bit-block transfer). Ещё этот процесс называют "блит" (англ. blit - BLock Image Transfer - поблочный перенос изображения). Так и говорят: "заблитить поверхности на экран".
В Windows GDI есть функция BitBlt, которая блитит растры в контекст устройства (device context). Контекст устройства во многом схож с поверхностью Direct3D, но гораздо сложнее в применении (Windows GDI - вообще штука непростая). Поверхность Direct3D представляет собой заметно обновлённую версию контекста устройства. Причём процесс её создания и применения также заметно упростился.

Создаём поверхность (Creating a Surface)

...сперва объявив переменную, которая указывает на поверхность в памяти:
LPDIRECT3DSURFACE9 surface = NULL;

Поверхность создана. Теперь с ней можно вытворять много всяких разных штук. Можно назначить блиттер, который будет рисовать в неё растры, либо просто "залить" поверхность определённым цветом. Для очистки поверхности (= заливки в определённый цвет) применяют функцию ColorFill:
Прототип функции ColorFill
HRESULT ColorFill( IDirect3DSurface9 *pSurface, CONST RECT *pRect, D3DCOLOR color );

Вот пример её применения, в котором предварительно созданная поверхность заливается в красный цвет:
d3ddev->ColorFill(surface, NULL, D3DCOLOR_XRGB(255,0,0));


Отрисовка поверхности (Блиттинг)

Можно блитить часть, либо всю поверхность из одной в другую (например бэкбуфер или сам экран). Как ни странно, функция блитинга в Direct3D называется StretchRect:
Прототип функции StretchRect
HRESULT StretchRect(
	IDirect3DSurface9 *pSourceSurface, // Поверхность-источник
	CONST RECT *pSourceRect, // Прямоугольник-источник
	IDirect3DSurface9 *pDestSurface, // Поверхность-получатель
	CONST RECT *pDestRect, // Прямоугольник-получатель
	D3DTEXTUREFILTERTYPE Filter );

Вот пример её применения:
d3ddev->StretchRect(surface, NULL, backbuffer, NULL, D3DTEXF_NONE);

Это самый простой вариант применения функции StretchRect. Здесь берутся две поверхности (одна из которых бэкбуфер) одного размера. Если поверхность-источник меньше поверхности-получателя, то она заблитируется в левый верхний угол поверхности-получателя. Но куда интереснее указать размеры прямоугольной области, подлежащей блитированию. Причём как у поверхности-источника, так и у поверхности-получателя. Прямоугольник-источник может включать в себя лишь небольшую часть поверхности-источника. Та же история с поверхностью-получателем. Вот пример:
rect.left = 100;
rect.top = 90;
rect.right = 200;
rect.bottom = 180;

d3ddev->StretchRect(surface, NULL, backbuffer, &rect, D3DTEXF_NONE);

Этот код копирует поверхность-источник в поверхность-получатель, растягивая первую до размеров прямоугольника со сторонами 100, 90, 200, 180 (т.е. размером 100х90 пикселей). Независимо от исходных размеров поверхности-источника, до тех пор, пока она не равна NULL, она будет подогнана под размеры прямоугольника-получателя.
Параметр backbuffer может быть объявлен как отдельная переменная, либо взят как глобальная переменная DirectX. Для получения указателя на бэкбуфер используют функцию GetBackBuffer:
Прототип функции GetBackBuffer
HRESULT GetBackBuffer( UINT iSwapChain,
	UINT BackBuffer,
	D3DBACKBUFFER_TYPE Type,
	IDirect3DSurface9 **ppBackBuffer );

В следующем примере мы создаём переменную backbuffer (это слово и будет являться указателем на бэкбуфер) и затем укажем Direct3D использовать его в качестве активного бэкбуфера при рендеринге:
LPDIRECT3DSURFACE9 backbuffer = NULL;

d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);


Пример приложения, блиттирующего поверхности

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

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

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

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

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта D3D9Surface01.
  • Во всплывающем меню Добавить->Создать элемент...
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	// Уменьшаем кол-во используемых компонентов в программе.

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

#include <windows.h>
#include "dxfunc.h"
#include <time.h>

// Здесь идут всякие объявления
BOOL 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;
}

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;
}

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);

 // При нажатии Esc на клавиатуре завершаем работу приложения
 if(KEY_DOWN(VK_ESCAPE))
 {
	 PostMessage(hWnd, WM_DESTROY, 0, 0);
 }
}

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

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

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

Добавляем в Проект заголовочный файл dxfunc.h

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

#include "dxfunc.h"

HRESULT d3d9Init(IDirect3D9 **d3d, IDirect3DDevice9 **d3ddev,
	HWND hWnd, DWORD iWidth, DWORD iHeight, BOOL bFullScreen)
{
	// Инициализируем Direct3D
	if ((*d3d = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
	{
		return E_FAIL;
	}
		
	// Заполняем основные (общие) параметры представления
    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;
  }
	
 if(FAILED((*d3d)->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, d3ddev)))
 {
	 return E_FAIL;
 }
}

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

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

Прежде чем начать модифицировать код, скомпилируем Проект D3D9Surface01. Для успешной компиляции изменим его настройки (=свойства).

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

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

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

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

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

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

На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Здесь всё делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект D3D9Surface01.
  • Убедись, что DirectX SDK 9 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "D3D9Surface01"", а строкой ниже жирным шрифтом название Проекта (тоже D3D9Surface01).
  • Жмём правой кнопкой мыши по названию Проекта D3D9Surface01. Во всплывающем меню выбираем пункт "Свойства".
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.
  • Жмём ОК, ОК.
  • Сохрани Решение (Файл->Сохранить все).

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

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

  • Нажми Esc на клавиатуре для выхода из программы.

Изменения в WinMain.cpp

На данном этапе исходный код приложения D3D9Surface01 одинаков с оным Проекта D3D9Init01. Но это ненадолго. Пробил час применить написанную выше теорию на практике. Ты увидишь, в каких именно участках кода создаются и вызываются поверхности. БОльшая часть кода будет добавлена в авторские функции GameInit и GameRun.

Добавляем объявления объектов интерфейсов поверхностей

  • Найди в WinMain.cpp следующий фрагмент:
Фргамент WinMain.cpp
...
// Объекты Direct3D (DirectX 9)
IDirect3D9 *d3d = NULL;
IDirect3DDevice9 *d3ddev = NULL;
...

  • Пониже строки LPDIRECT3DDEVICE9 d3ddev... добавь строки:
LPDIRECT3DSURFACE9 backbuffer = NULL;
LPDIRECT3DSURFACE9 surface = NULL;


Дополнения в функции GameInit. Объявляем переменную result

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

  • ДО оператора условного перехода if размести строку:
HRESULT result;

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

Дополнения в функции Gamelnit. Инициализируем поверхности

  • Найди в WinMain.cpp следующий фрагмент:
Фргамент WinMain.cpp
...
	// Создаём генератор случайных чисел на основе системного времени
	srand(time(NULL));
	
	// Раз достигли этой строки, то всё ОК.
	return TRUE;
...

  • Ниже вызова функции srand добавь следующий код:
// Создаём указатель на бэкбуфер
	d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
	
	// Создаём поверхность
	result = d3ddev->CreateOffscreenPlainSurface( 100, // Ширина поверхности
		100, // Высота поверхности
		D3DFMT_X8R8G8B8, // Формат цветности поверхности
		D3DPOOL_DEFAULT, // используемый пул памяти (memory pool)
		&surface, // Указатель на создаваемую поверхность NULL
		NULL // Зарезервирован (всегда NULL)
		);

	if(!result) return NULL;

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

Дополнения в функции GameRun. Выводим поверхности на экран

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

  • ДО проверки готовности объекта устройства Direct3D (строка с оператором if) добавь переменные:
RECT rect;
int r,g,b;

  • Найди в WinMain.cpp следующий фрагмент:
Фрагмент WinMain.cpp
...
 // Очищаем бэкбуфер в синий цвет.
 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0);
...

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

  • Чуть ниже найди фрагмент:
Фрагмент WinMain.cpp
...
 // Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
  // Что-то делаем.

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

  • Ниже (или вместо) комментария "Что-то делаем" набери:
// Заполняем поверхность произвольным цветом
	 r = rand() % 255;
	 g = rand() % 255;
	 b = rand() % 255;
	 d3ddev->ColorFill(surface, NULL, D3DCOLOR_XRGB(r,g,b));
	 
	 // Копируем поверхность в бэкбуфер
	 rect.left = rand() % iWidth/2;
	 rect.right = rect.left + rand() % iWidth/2;
	 rect.top = rand() % iHeight;
	 rect.bottom = rect.top + rand() % iHeight/2;
	 d3ddev->StretchRect(surface, NULL, backbuffer, &rect, D3DTEXF_NONE);

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

Дополнения в функции GameEnd. Грамотно удаляем поверхности при завершении работы приложения

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

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

  • ДО уничтожения объекта устройства d3ddev (строка с оператором if) добавь строки:
// Удаляем поверхность
surface->Release();


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

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

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

Растры (Bitmaps)

В продолжении этого урока мы загрузим в память файл растрового изображения (.bmp), отрисуем это самое изображение на DX-поверхности (surface) и выведем эту самую поверхность на экран (через бэкбуфер, конечно же). К сожалению, в DirectX отсутствуют функции загрузки растровых изображений. Поэтому мы напишем такой загрузчик сами (с применением Windows API).
В DirectX есть чудесная вспомогательная библиотека D3DX, которая, помимо всего прочего, предоставляет функцию для загрузки растра (bitmap) на DX-поверхность (surface) прямо из .bmp-файла! Само собой, перед применением необходимо подключить заголовок d3dx.h директивой #include, а также указать d3dx9.lib в настройках Проекта (Проект->Свойства->Свойства конфигурации->Компоновщик->Ввод). Но обо всём по порядку.

Функция D3DXLoadSurfaceFromFile

  • Расположена в библиотеке d3dx9.lib. Вот её прототип:
Прототип функции D3DXLoadSurfaceFromFile
HRESULT D3DXLoadSurfaceFromFile(
  LPDIRECT3DSURFACE9	pDestSurface,
  const PALETTEENTRY    *pDestPalette,
  const RECT            *pDestRect,
  LPCTSTR            pSrcFile,
  const RECT         *pSrcRect,
  DWORD              Filter,
  D3DCOLOR           ColorKey,
  D3DXIMAGE_INFO     *pSrcInfo
);

  • Загружает с диска файлы растровых изображений следующих форматов:
.bmp Windows Bitmap (standart).
.dds DirectDraw Surface (DirectX 7).
.dib Windows Device Independent Bitmap.
.jpg Joint Photographic Experts Group (JPEG).
.png Portable Network Graphics.
.tga Truevision Targa.


Пример приложения, загружающего файл растрового изображения средствами d3dx9.lib

Напишем программу, которая загружает с жёсткого диска файл растрового изображения (.bmp), наносит его на DX-поверхность, которую затем выводит на экран. За основу возьмём Проект D3D9Surface01, созданный выше. Там уже есть код инициализации DX-поверхностей. Но сперва подключим к Проекту библиотеку d3dx9.lib.

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

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

Указываем в dxfunc.h директиву #include "d3dx9.h"

Ранее мы условились хранить все директивы и объявления DirectX-стафа в заголовочном файле dxfunc.h. Это упростит их поиск. Да и вообще соответствует концепции "единой точки контакта", общепринятой в игрокодинге.
  • В dxfunc.h найди следующий фрагмент:
Фрагмент dxfunc.h
...
#include <d3d9.h>
...

  • Пониже строки #include "d3d9.h" пишем:
#include <d3dx9.h>

Имя обоих заголовков размещено в угловых скобках т.к. данные файлы надо искать в подкаталогах установленного DirectX SDK.
  • Сохрани Решение (Файл->Сохранить все).

Изменения в функции GameInit (WinMain.cpp). Размещаем на DX-поверхности растровое изображение из файла

  • В WinMain.cpp найди следующий фрагмент:
Фрагмент WinMain.cpp
...
	// Создаём поверхность
	result = d3ddev->CreateOffscreenPlainSurface( 100, // Ширина поверхности
		100, // Высота поверхности
		D3DFMT_X8R8G8B8, // Формат цветности поверхности
		D3DPOOL_DEFAULT, // используемый пул памяти (memory pool)
		&surface, // Указатель на создаваемую поверхность NULL
		NULL // Зарезервирован (всегда NULL)
		);
...

  • Измени значение первого параметра (ширина поверхности) на 640.
  • Измени значение второго параметра (высота поверхности) на 480.

  • Сразу после этого кода видим строки:
Фрагмент WinMain.cpp
...
if(!result) return NULL;
...

  • Замени этот код на следующий:
if(result != D3D_OK) return NULL;

  • Ниже этих строк добавь код:
// Загружаем "поверхность" из файла
	result = D3DXLoadSurfaceFromFile( surface, // Поверхность-получатель
		NULL, // Палитра-получатель
		NULL, // Прямоугольник-получатель
		"test.jpg", // Имя файла растрового изображения
		NULL, // Прямоугольник-источник
		D3DX_DEFAULT, // Метод фильтрации изображения
		0, // Флаг прозрачности (0 - значит без неё)
		NULL); // Инфо о файле растрового изображения (обычно всегда NULL)
	
	// Проверяем, что файл успешо загружен
	if(result != D3D_OK) return NULL;
	
	// Рисуем поверхность в бэкбуфер
	d3ddev->StretchRect(surface, NULL, backbuffer, NULL, D3DTEXF_NONE);

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

Изменения в функции GameRun (WinMain.cpp). Выводим DX-поверхность на экран

  • В WinMain.cpp найди следующий фрагмент:
Фрагмент WinMain.cpp
...
void GameRun(HWND hWnd)
{
	RECT rect;
	int r,g,b;
...

  • Удали строку RECT rect;
  • Удали строку int r,g,b;

  • В WinMain.cpp найди следующий фрагмент:
Фрагмент WinMain.cpp
...
// Начинаем рендеринг.
 if(SUCCEEDED(d3ddev->BeginScene()))
 {
	 // Заполняем поверхность произвольным цветом
	 r = rand() % 255;
	 g = rand() % 255;
	 b = rand() % 255;
	 d3ddev->ColorFill(surface, NULL, D3DCOLOR_XRGB(r,g,b));
	 
	 // Копируем поверхность в бэкбуфер
	 rect.left = rand() % iWidth/2;
	 rect.right = rect.left + rand() % iWidth/2;
	 rect.top = rand() % iHeight;
	 rect.bottom = rect.top + rand() % iHeight/2;
	 d3ddev->StretchRect(surface, NULL, backbuffer, &rect, D3DTEXF_NONE);

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

  • Замени его на следующий:
// Начинаем рендеринг
	if(SUCCEEDED(d3ddev->BeginScene()))
	{
		d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);  // Создаём указатель на бэкбуфер
		d3ddev->StretchRect(surface, NULL, backbuffer, NULL, D3DTEXF_NONE); // Рисуем поверхность в бэкбуфер
		
		// Завершаем рендеринг.
		d3ddev->EndScene();
	}

  • Сохрани Решение (Файл->Сохранить все).
Видим, что объём кода заметно уменьшился. Ведь больше не нужно создавать рандомные прямоугольники и раскрашивать их.

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

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

  • Перейди в каталог Проекта (в нашем случае C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\D3D9Surface01\Debug) и запусти скомпилированный исполняемый файл D3D9Surface01.exe .
Приложение сначала покажет сообщение с запросом на переход в полноэкранный режим. Затем окно зальёт фон чёрным цветом. Это нормально. Ведь открываемый файл test.jpg пока никто не готовил. Напомним, программа ищет его в том же каталоге, где расположен исполняемый файл приложения.
  • Нажми Esc для выхода из программы.

Готовим файл test.jpg

В WinMain.cpp (после всех внесённых изменений) есть такой код:
Фрагмент WinMain.cpp
...
	// Загружаем "поверхность" из файла
	result = D3DXLoadSurfaceFromFile( surface, // Поверхность-получатель
		NULL, // Палитра-получатель
		NULL, // Прямоугольник-получатель
		"test.jpg", // Имя файла растрового изображения
		NULL, // Прямоугольник-источник
		D3DX_DEFAULT, // Метод фильтрации изображения
		0, // Флаг прозрачности (0 - значит без неё)
		NULL); // Инфо о файле растрового изображения (обычно всегда NULL)
...

Здесь загружается изображение из файла test.jpg.
В этом фрагменте...
Фрагмент WinMain.cpp
...
	// Создаём поверхность
	result = d3ddev->CreateOffscreenPlainSurface( 640, // Ширина поверхности
		480, // Высота поверхности
		D3DFMT_X8R8G8B8, // Формат цветности поверхности
		D3DPOOL_DEFAULT, // используемый пул памяти (memory pool)
		&surface, // Указатель на создаваемую поверхность NULL
		NULL // Зарезервирован (всегда NULL)
		);
...

...указано, что размер создаваемой поверхности составляет 640х480. Поэтому...
  • Найди в интернете или создай в любом графическом редакторе файл формата .jpg размером 640х480.
  • Помести его в каталог с исполняемым файлом нашего приложения (в нашем случае путь до него такой: C:\Users\<Имя пользователя >\Documents\Visual Studio 2010\Projects\D3D9Surface01\Debug).
  • Переименуй .jpg-файл в test.jpg .
  • Снова запусти приложение D3D9Surface01.exe .
На экране появится окно с запросом на переход в полноэкранный режим. При выборе любого варианта в окне появится изображение из файла test.jpg.

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

Исходные коды примера (ZIP-архив с проектом для MSVC++2010) забираем здесь: https://disk.yandex.ru/d/td8ldudZDDHMrQ(external link)

Задание на дом

  • Найди в Интернете .jpg-файл с другим разрешением (больше или меньше).
  • Размести его в каталоге с приложением и переименуй в test.jpg (удали предыдущий .jpg-файл при необходимости).
  • Запусти приложение D3D9Surface01.exe . Посмотри результат.

Заключение

В этой статье мы изучили:
  • Поверхности DirectX и их применение в реальном коде Windows-приложения.
  • Функция StretchRect копирует поверхность в бэкбуфер.
  • Вспомогательная библиотека d3dx9.lib содержит функцию D3DXLoadSurfaceFromFile, позволяющую загружать в DX-поверхность файлы распространённых графических форматов (.jpg, .bmp, .tga);
DirectX позволяет эффективно работать с растровой графикой. Размер итогового приложения всего 18 Кб.

Источники


Последние изменения страницы Среда 08 / Июнь, 2022 07:47:12 MSK

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

No records to display

Search Wiki Page

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

Категории

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