Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Поймите роль инфраструктуры графики Microsoft DirectX (DXGI) в вашей игре для магазина приложений Windows на DirectX. DXGI — это набор API, используемых для настройки ресурсов графических и графических адаптеров низкого уровня и управления ими. Без этого вы не можете нарисовать графику игры в окно.
Подумайте об DXGI таким образом: чтобы напрямую получить доступ к GPU и управлять его ресурсами, необходимо описать его в приложении. Наиболее важным элементом информации, необходимой для GPU, является место для рисования пикселей, чтобы он смог отправить эти пиксели на экран. Обычно это называется "обратным буфером" — это место в памяти GPU, где можно рисовать пиксели, а затем они "переключаются" и отправляются на экран по сигналу обновления. DXGI позволяет получить это расположение и средства для использования этого буфера (называемого цепочкой сменяемых буферов , так как это цепочка сменяемых буферов, которая позволяет использовать несколько стратегий буферизации).
Для этого требуется доступ для записи в цепочку обмена и дескриптор окна, который будет отображать текущий задний буфер. Кроме того, необходимо подключить эти два элемента, чтобы убедиться, что операционная система обновит окно с содержимым заднего буфера при запросе на это.
Общий процесс рисования на экране выглядит следующим образом:
- Получите CoreWindow для вашего приложения.
- Получите интерфейс для устройства Direct3D и контекста.
- Создайте свапчейн для отображения отрисованного изображения в CoreWindow.
- Создайте целевой объект отрисовки для рисования и заполните его пикселями.
- Показать цепочку буферов!
Создание окна для приложения
Первое, что нужно сделать, — создать окно. Сначала создайте класс окна путем заполнения экземпляра WNDCLASS, а затем зарегистрируйте его с помощью RegisterClass. Класс окна содержит основные свойства окна, включая значок, который он использует, статическую функцию обработки сообщений (подробнее об этом позже) и уникальное имя класса окна.
if(m_hInstance == NULL)
m_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HICON hIcon = NULL;
WCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL, szExePath, MAX_PATH);
// If the icon is NULL, then use the first one found in the exe
if(hIcon == NULL)
hIcon = ExtractIcon(m_hInstance, szExePath, 0);
// Register the windows class
WNDCLASS wndClass;
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = MainClass::StaticWindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = m_hInstance;
wndClass.hIcon = hIcon;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = m_windowClassName.c_str();
if(!RegisterClass(&wndClass))
{
DWORD dwError = GetLastError();
if(dwError != ERROR_CLASS_ALREADY_EXISTS)
return HRESULT_FROM_WIN32(dwError);
}
Затем вы создадите окно. Кроме того, необходимо указать сведения о размере окна и имя только что созданного класса окна. При вызове CreateWindowвы получите непрозрачный указатель на окно с именем HWND; Вам потребуется сохранить указатель HWND и использовать его в любое время, когда вам нужно ссылаться на окно, включая уничтожение или воссоздание, и (особенно важно) при создании цепочки буферов DXGI, которую вы используете для рисования в окне.
m_rc;
int x = CW_USEDEFAULT;
int y = CW_USEDEFAULT;
// No menu in this example.
m_hMenu = NULL;
// This example uses a non-resizable 640 by 480 viewport for simplicity.
int nDefaultWidth = 640;
int nDefaultHeight = 480;
SetRect(&m_rc, 0, 0, nDefaultWidth, nDefaultHeight);
AdjustWindowRect(
&m_rc,
WS_OVERLAPPEDWINDOW,
(m_hMenu != NULL) ? true : false
);
// Create the window for our viewport.
m_hWnd = CreateWindow(
m_windowClassName.c_str(),
L"Cube11",
WS_OVERLAPPEDWINDOW,
x, y,
(m_rc.right-m_rc.left), (m_rc.bottom-m_rc.top),
0,
m_hMenu,
m_hInstance,
0
);
if(m_hWnd == NULL)
{
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
}
Модель настольного приложения Windows включает в себя механизм взаимодействия с циклом сообщений Windows. Вам потребуется построить основной цикл программы вокруг данного хука, написав функцию StaticWindowProc для обработки событий окон. Это должна быть статической функцией, так как Windows будет вызывать её вне контекста любого экземпляра класса. Ниже приведен очень простой пример функции обработки статических сообщений.
LRESULT CALLBACK MainClass::StaticWindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_CLOSE:
{
HMENU hMenu;
hMenu = GetMenu(hWnd);
if (hMenu != NULL)
{
DestroyMenu(hMenu);
}
DestroyWindow(hWnd);
UnregisterClass(
m_windowClassName.c_str(),
m_hInstance
);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
В этом простом примере проверяются только условия выхода программы: WM_CLOSE, отправляется при запросе на закрытие окна, и WM_DESTROY, передается после фактического удаления окна с экрана. Полное производственное приложение также должно обрабатывать другие события окна— полный список событий окна см. в разделе уведомления о окне.
Основной цикл программы должен обрабатывать сообщения Windows, позволяя Windows выполнять статический обработчик сообщений. Помогите программе работать эффективно, разделяя поведение: каждая итерация должна обрабатывать новые сообщения Windows, если они доступны, а если сообщений в очереди нет, она должна отображать новый кадр. Ниже приведен очень простой пример:
bool bGotMsg;
MSG msg;
msg.message = WM_NULL;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while (WM_QUIT != msg.message)
{
// Process window events.
// Use PeekMessage() so we can use idle time to render the scene.
bGotMsg = (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0);
if (bGotMsg)
{
// Translate and dispatch the message
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// Update the scene.
renderer->Update();
// Render frames during idle time (when no messages are waiting).
renderer->Render();
// Present the frame to the screen.
deviceResources->Present();
}
}
Получите интерфейс для устройства Direct3D и контекста
Первым шагом к использованию Direct3D является получение интерфейса для оборудования Direct3D (GPU), представленного как экземпляры ID3D11Device и ID3D11DeviceContext. Первый — это виртуальное представление ресурсов GPU, а последнее — не зависящая от устройства абстракция конвейера отрисовки и процесса. Вот простой способ подумать об этом: ID3D11Device содержит методы графики, которые вы вызываете редко, как правило, перед выполнением любой отрисовки, чтобы получить и настроить набор ресурсов, необходимых для начала рисования пикселей. ID3D11DeviceContext, с другой стороны, содержит методы, которые вы вызываете каждый кадр: загрузка буферов и представлений и других ресурсов, изменение состояния слияния вывода и растеризатора, управление шейдерами и рисование результатов передачи этих ресурсов через состояния и шейдеры.
Существует одна очень важная часть этого процесса: настройка уровня компонентов. Уровень возможностей сообщает DirectX о минимальном уровне оборудования, поддерживаемого вашим приложением: D3D_FEATURE_LEVEL_9_1 как самый низкий набор функций и D3D_FEATURE_LEVEL_11_1 как самый высокий на данный момент. Вы должны поддерживать 9_1 как минимум, если вы хотите достичь максимально возможной аудитории. Ознакомьтесь с уровнями функций Direct3D и, оцените минимальные и максимальные уровни функций, которые вы хотите, чтобы ваша игра поддерживала, и поймите последствия вашего выбора.
Получите ссылки (указатели) на устройство Direct3D и контекст устройства и сохраните их как переменные уровня класса в экземпляре DeviceResources (в виде смарт-указателей ComPtr). Используйте эти интерфейсы всякий раз, когда необходимо получить доступ к устройству Direct3D или его контексту.
D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_11_1
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
// This flag adds support for surfaces with a color-channel ordering different
// from the API default. It is required for compatibility with Direct2D.
UINT deviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(DEBUG) || defined(_DEBUG)
deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Create the Direct3D 11 API device object and a corresponding context.
Microsoft::WRL::ComPtr<ID3D11Device> device;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
hr = D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
deviceFlags, // Set debug and Direct2D compatibility flags.
levels, // List of feature levels this app can support.
ARRAYSIZE(levels), // Size of the list above.
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
&device, // Returns the Direct3D device created.
&m_featureLevel, // Returns feature level of device created.
&context // Returns the device immediate context.
);
if (FAILED(hr))
{
// Handle device interface creation failure if it occurs.
// For example, reduce the feature level requirement, or fail over
// to WARP rendering.
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
device.As(&m_pd3dDevice);
context.As(&m_pd3dDeviceContext);
Создайте цепочку обмена
Хорошо: у вас есть окно для рисования, и у вас есть интерфейс для отправки данных и предоставления команд GPU. Теперь давайте посмотрим, как объединить их вместе.
Во-первых, вы сообщаете DXGI, какие значения следует использовать для свойств цепочки обмена. Это можно сделать, используя структуру DXGI_SWAP_CHAIN_DESC. Шесть полей особенно важны для классических приложений:
- оконный режим: указывает, является ли цепочка обмена полноэкранной или обрезана по размеру окна. Установите значение TRUE, для того чтобы поместить цепочку обмена в созданное ранее окно.
- BufferUsage: задайте для этого значение DXGI_USAGE_RENDER_TARGET_OUTPUT. Это означает, что цепочка буферов будет областью рисования, что позволяет использовать ее в качестве целевого объекта отрисовки Direct3D.
- SwapEffect: задайте для этого значение DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL.
- Формат: формат DXGI_FORMAT_B8G8R8A8_UNORM задает 32-разрядный цвет: 8 бит для каждого из трех каналов цветов RGB и 8 бит для альфа-канала.
- BufferCount: установите значение 2 для традиционной двойной буферизации, чтобы избежать разрыва. Задайте для количества буферов значение 3, если графическое содержимое занимает несколько циклов обновления монитора для отрисовки одного кадра (например, при 60 Гц пороговое значение превышает 16 мс).
- SampleDesc: это поле управляет мультисемплингом. Установите для счетчика значение на 1 и для индикатора качества значение на 0 для цепочек обмена в режиме переключения. (Чтобы использовать многофакторную фильтрацию с цепочками буферов с помощью модели переключения, нарисуйте отдельный многофакторный целевой объект отрисовки, а затем устраните эту цель в цепочке буферов, прежде чем представить ее. Пример кода представлен в multisampling в приложениях Магазина Windows.)
После указания конфигурации для цепочки обмена необходимо использовать ту же фабрику DXGI, которая создала устройство Direct3D (и контекст устройства), чтобы создать цепочку обмена.
краткая форма:
Получите ссылку ID3D11Device, ранее созданную вами. Переадресуйте его в IDXGIDevice3 (если вы еще не сделали), а затем вызовите IDXGIDevice::GetAdapter для получения адаптера DXGI. Получите родительскую фабрику для этого адаптера, вызвав IDXGIAdapter::GetParent (IDXGIAdapter наследует от IDXGIObject) — теперь вы можете использовать эту фабрику для создания цепочки обмена путем вызова CreateSwapChainForHwnd, как показано в следующем примере кода.
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(DXGI_SWAP_CHAIN_DESC));
desc.Windowed = TRUE; // Sets the initial state of full-screen mode.
desc.BufferCount = 2;
desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1; //multisampling setting
desc.SampleDesc.Quality = 0; //vendor-specific flag
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.OutputWindow = hWnd;
// Create the DXGI device object to use in other factories, such as Direct2D.
Microsoft::WRL::ComPtr<IDXGIDevice3> dxgiDevice;
m_pd3dDevice.As(&dxgiDevice);
// Create swap chain.
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
Microsoft::WRL::ComPtr<IDXGIFactory> factory;
hr = dxgiDevice->GetAdapter(&adapter);
if (SUCCEEDED(hr))
{
adapter->GetParent(IID_PPV_ARGS(&factory));
hr = factory->CreateSwapChain(
m_pd3dDevice.Get(),
&desc,
&m_pDXGISwapChain
);
}
Если вы только начинаете, это, вероятно, лучше всего использовать конфигурацию, показанную здесь. Теперь на этом этапе, если вы уже знакомы с предыдущими версиями DirectX, вы можете спросить: "Почему мы не создали устройство и цепочку обмена одновременно, вместо того чтобы заново проходить через все эти классы?" Ответ заключается в эффективности: цепочки обмена являются ресурсами устройства Direct3D, а ресурсы устройства привязаны к конкретному устройству Direct3D, которое их создало. При создании нового устройства с новой цепочкой буферов необходимо повторно создать все ресурсы устройства с помощью нового устройства Direct3D. Таким образом, создав цепочку буферов с той же фабрикой (как показано выше), вы можете воссоздать цепочку буферов и продолжить использование ресурсов устройств Direct3D, которые вы уже загрузили!
Теперь у вас есть окно операционной системы, способ доступа к GPU и его ресурсам, а также цепочка обмена для отображения результатов отрисовки. Осталось только все это соединить!
Создание целевого объекта отрисовки для рисования
Конвейеру шейдеров необходим ресурс для отрисовки пикселей. Самый простой способ создания этого ресурса — определить ресурс ID3D11Texture2D в качестве обратного буфера для отрисовки пиксельным шейдером, а затем считывать эту текстуру в цепочку обмена.
Для этого создайте представление целевого объекта отрисовки. В Direct3D представление — это способ доступа к конкретному ресурсу. В этом случае представление позволяет шейдеру пикселей записывать данные в текстуру по мере завершения операций на пиксель.
Давайте рассмотрим код для этого. При установке DXGI_USAGE_RENDER_TARGET_OUTPUT в swap chain, базовый ресурс Direct3D разрешается использовать в качестве поверхности рисования. Таким образом, чтобы получить представление целевого объекта отрисовки, необходимо просто получить буфер обратно из цепочки буферов и создать целевое представление отрисовки, привязанное к ресурсу обратного буфера.
hr = m_pDXGISwapChain->GetBuffer(
0,
__uuidof(ID3D11Texture2D),
(void**) &m_pBackBuffer);
hr = m_pd3dDevice->CreateRenderTargetView(
m_pBackBuffer.Get(),
nullptr,
m_pRenderTarget.GetAddressOf()
);
m_pBackBuffer->GetDesc(&m_bbDesc);
Кроме того, создайте буфер элементов глубины. Глубинный буфер-шаблон — это особая форма ресурса ID3D11Texture2D, который обычно используется для определения приоритета отрисовки пикселей во время растеризации на основе расстояния объектов в сцене от камеры. Буфер глубины и трафарета также можно использовать для трафаретных эффектов, при которых определенные пиксели удаляются или игнорируются во время растеризации. Этот буфер должен иметь тот же размер, что и целевой объект отрисовки. Обратите внимание, что вы не можете считывать или выводить текстуру глубины-стенсила буфера кадра, так как она используется исключительно шейдерным конвейером до и во время окончательной растеризации.
Кроме того, создайте представление для буфера элементов глубины в виде ID3D11DepthStencilView. Представление сообщает конвейеру шейдера, как интерпретировать базовый ресурс ID3D11Texture2D - поэтому, если вы не предоставите это представление, пер-пиксельное тестирование глубины не выполняется, и объекты в вашей сцене могут выглядеть, по крайней мере, немного перевернутыми.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
DXGI_FORMAT_D24_UNORM_S8_UINT,
static_cast<UINT> (m_bbDesc.Width),
static_cast<UINT> (m_bbDesc.Height),
1, // This depth stencil view has only one texture.
1, // Use a single mipmap level.
D3D11_BIND_DEPTH_STENCIL
);
m_pd3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&m_pDepthStencil
);
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
m_pd3dDevice->CreateDepthStencilView(
m_pDepthStencil.Get(),
&depthStencilViewDesc,
&m_pDepthStencilView
);
Последним шагом является создание окна просмотра. Это определяет видимый прямоугольник заднего буфера, отображаемого на экране; Вы можете изменить часть буфера, отображаемую на экране, изменив параметры окна просмотра. Этот код предназначен для размера всего окна или разрешения экрана в случае цепочек обмена во весь экран. Для удовольствия измените указанные значения координат и просмотрите результаты.
ZeroMemory(&m_viewport, sizeof(D3D11_VIEWPORT));
m_viewport.Height = (float) m_bbDesc.Height;
m_viewport.Width = (float) m_bbDesc.Width;
m_viewport.MinDepth = 0;
m_viewport.MaxDepth = 1;
m_pd3dDeviceContext->RSSetViewports(
1,
&m_viewport
);
И вот как вы идете от ничего до рисования пикселей в окне! В начале работы рекомендуется ознакомиться с тем, как DirectX через DXGI управляет основными ресурсами, необходимыми для начала рисования пикселей.
Далее вы изучите структуру потока обработки графики; см. Понимание потока рендеринга в шаблоне приложения DirectX.
Связанные разделы