Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Poznaj rolę infrastruktury graficznej DirectX firmy Microsoft (DXGI) w grze DirectX dla Sklepu Windows. DXGI to zestaw interfejsów API używanych do konfigurowania i zarządzania zasobami graficznymi i adapterami graficznymi niskiego poziomu. Bez tego, nie miałbyś sposobu, aby narysować grafikę gry do okna.
Pomyśl o DXGI w ten sposób: aby bezpośrednio uzyskać dostęp do procesora GPU i zarządzać jego zasobami, musisz mieć sposób na opisanie go w aplikacji. Najważniejszym elementem informacji potrzebnym na temat procesora GPU jest miejsce do rysowania pikseli, dzięki czemu może wysyłać te piksele do ekranu. Zazwyczaj jest to nazywane "buforem wstecznym" — lokalizacją w pamięci procesora GPU, w której można narysować piksele, a następnie mieć ją "przerzuconą" lub "zamieniona" i wysłaną do ekranu na sygnał odświeżania. DXGI umożliwia uzyskanie tej lokalizacji i środków do użycia tego buforu (nazywanego łańcuchem wymiany , ponieważ jest to łańcuch z możliwością wymiany, co pozwala na zastosowanie wielu strategii buforowania).
Aby to zrobić, musisz mieć dostęp do zapisu w łańcuchu swap i uchwyt do okna, które będzie wyświetlać bieżący bufor tylni dla łańcucha swap. Należy również połączyć te dwa elementy, aby upewnić się, że system operacyjny odświeży okno z zawartością buforu wstecznego, gdy zażądasz tego.
Ogólny proces rysowania na ekranie wygląda następująco:
- Pobierz CoreWindow dla twojej aplikacji.
- Pobierz interfejs dla urządzenia i kontekstu Direct3D.
- Utwórz łańcuch wymiany, aby wyświetlić renderowany obraz w CoreWindow.
- Utwórz obiekt docelowy renderowania do rysowania i wypełnij go pikselami.
- Przedstaw łańcuch wymiany!
Tworzenie okna dla aplikacji
Pierwszą rzeczą, którą musimy zrobić, jest utworzenie okna. Najpierw utwórz klasę okien, wypełniając wystąpienie WNDCLASS, a następnie zarejestruj ją przy użyciu RegisterClass. Klasa okna zawiera podstawowe właściwości okna, w tym ikonę, która jest używana, funkcję statycznego przetwarzania komunikatów (więcej na ten temat później) oraz unikatową nazwę klasy okna.
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);
}
Następnie utworzysz okno. Musimy również podać informacje o rozmiarze okna i nazwie właśnie utworzonej klasy okna. Podczas wywoływania metody CreateWindowotrzymasz nieprzezroczysty wskaźnik do okna o nazwie HWND; należy zachować wskaźnik HWND i używać go za każdym razem, gdy musisz odwołać się do okna, w tym zniszczyć lub odtworzyć je, a (szczególnie ważne) podczas tworzenia łańcucha swapowania DXGI używanego do rysowania w oknie.
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);
}
Model aplikacji desktopowej systemu Windows zawiera zaczep w pętli komunikatów systemu Windows. Należy oprzeć główną pętlę programu na tym haku, pisząc funkcję "StaticWindowProc" do przetwarzania zdarzeń związanych z oknami. Musi to być funkcja statyczna, ponieważ system Windows wywoła ją poza kontekstem dowolnego wystąpienia klasy. Oto bardzo prosty przykład funkcji przetwarzania komunikatów statycznych.
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);
}
W tym prostym przykładzie sprawdzane są tylko warunki zakończenia programu: WM_CLOSE, wysyłane, gdy zostanie złożona prośba o zamknięcie okna, i WM_DESTROY, które jest wysyłane po faktycznym usunięciu okna z ekranu. Pełna, produkcyjna aplikacja musi również obsługiwać inne zdarzenia okien — aby uzyskać pełną listę zdarzeń okien, zobacz Powiadomienia okien.
Sama pętla głównego programu musi potwierdzić komunikaty systemu Windows, umożliwiając systemowi Windows możliwość uruchomienia statycznego komunikatu proc. Pomóż programowi działać wydajnie, rozgałęziając jego działanie: każda iteracja powinna wybrać przetwarzanie nowych komunikatów systemu Windows, jeśli są dostępne. Jeśli w kolejce nie ma żadnych komunikatów, powinna renderować nową klatkę. Oto bardzo prosty przykład:
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();
}
}
Uzyskaj interfejs dla urządzenia i kontekstu Direct3D
Pierwszym krokiem do korzystania z Direct3D jest uzyskanie interfejsu dla sprzętu Direct3D (GPU), przedstawionego jako wystąpienia ID3D11Device i ID3D11DeviceContext. Pierwszy to wirtualna reprezentacja zasobów procesora GPU, a druga to niezależna od urządzenia abstrakcja potoku renderowania i procesu. Oto prosty sposób myślenia o tym: ID3D11Device zawiera metody graficzne, które są wywoływane rzadko, zwykle przed każdym renderowaniem, w celu uzyskania i skonfigurowania zestawu zasobów potrzebnych do rozpoczęcia rysowania pikseli. ID3D11DeviceContext, z drugiej strony, zawiera metody wywoływane w każdej klatce: ładowanie buforów i widoków oraz innych zasobów, zmienianie stanu scalacza danych wyjściowych i rasteryzatora, zarządzanie shaderami i rysowanie wyników przepływu tych zasobów przez stany i shadery.
Jest jedna bardzo ważna część tego procesu: ustawienie poziomu funkcji. Poziom funkcji informuje DirectX o minimalnym poziomie sprzętu obsługiwanego przez aplikację, a D3D_FEATURE_LEVEL_9_1 jako najniższy zestaw funkcji i D3D_FEATURE_LEVEL_11_1 jako bieżący najwyższy. Jeśli chcesz dotrzeć do najszerszej liczby odbiorców, należy obsługiwać wartość 9_1 jako minimalną. Pośmiń trochę czasu, aby zapoznać się z poziomami funkcji Direct3D i ocenić dla siebie minimalne i maksymalne poziomy funkcji, które mają być obsługiwane przez twoją grę i zrozumieć implikacje wyboru.
Uzyskaj odwołania (wskaźniki) do urządzenia Direct3D oraz kontekstu urządzenia i zapisz je jako zmienne na poziomie klasy w instancji DeviceResources (jako inteligentne wskaźniki ComPtr). Używaj tych odwołań zawsze, gdy musisz uzyskać dostęp do urządzenia Direct3D lub kontekstu urządzenia.
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);
Utwórz łańcuch wymiany
W porządku: masz okno do rysowania i masz interfejs do wysyłania danych i wydawania poleceń do procesora GPU. Teraz zobaczmy, jak je połączyć.
Najpierw należy poinformować DXGI, jakie wartości mają być używane dla właściwości łańcucha wymiany. Zrób to przy użyciu struktury DXGI_SWAP_CHAIN_DESC. Sześć pól jest szczególnie ważnych w przypadku aplikacji klasycznych:
- Okna: Wskazuje, czy łańcuch wymiany jest pełnoekranowy, czy przycięty do okna. Ustaw tę wartość na TRUE, aby wstawić łańcuch wymiany w utworzonym wcześniej oknie.
- BufferUsage: ustaw tę wartość na DXGI_USAGE_RENDER_TARGET_OUTPUT. Oznacza to, że swap chain będzie powierzchnią do rysowania, umożliwiając jego użycie jako obiektu docelowego renderowania Direct3D.
- SwapEffect: ustaw tę wartość na DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL.
- Format: format DXGI_FORMAT_B8G8R8A8_UNORM określa kolor 32-bitowy: 8 bitów dla każdego z trzech kanałów kolorów RGB i 8 bitów dla kanału alfa.
- BufferCount: ustaw tę wartość na 2 dla tradycyjnego zachowania buforowanego dwukrotnie, aby uniknąć przerywania obrazu. Ustaw liczbę buforów na 3, jeśli zawartość grafiki wymaga więcej niż jednego cyklu odświeżania monitora do wyrenderowania pojedynczej klatki (na przykład przy 60 Hz próg wynosi więcej niż 16 ms).
- SampleDesc: to pole steruje multisamplingiem. Ustaw liczbę na 1 i jakość na 0 dla łańcuchów zamiany modelu flip. (Aby użyć wieloprzykładowości z łańcuchami zamiany modelu flip-model, narysuj na oddzielnym obiekcie docelowym renderowania wieloprzykładowego, a następnie przekształć ten obiekt do łańcucha zamian bezpośrednio przed jego prezentacją. Przykładowy kod jest dostarczany w Wieloprzykładowość w aplikacjach Sklepu Windows.)
Po określeniu konfiguracji łańcucha wymiany należy użyć tej samej fabryki DXGI, która stworzyła urządzenie Direct3D (oraz kontekst urządzenia), by utworzyć łańcuch wymiany.
Krótka forma:
Pobierz wcześniej utworzone odwołanie ID3D11Device. Upcast it to IDXGIDevice3 (jeśli jeszcze tego nie zrobiono), a następnie wywołaj IDXGIDevice::GetAdapter, aby uzyskać adapter DXGI. Pobierz fabrykę nadrzędną dla tego adaptera, wywołując IDXGIAdapter::GetParent (IDXGIAdapter dziedziczy po IDXGIObject) — teraz możesz użyć tej fabryki do utworzenia swap chain, wywołując CreateSwapChainForHwnd, jak pokazano w poniższym przykładzie kodu.
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
);
}
Jeśli dopiero zaczynasz, prawdopodobnie najlepiej użyć konfiguracji pokazanej tutaj. Teraz, jeśli znasz już poprzednie wersje DirectX, możesz zapytać: "Dlaczego nie utworzyliśmy urządzenia i łańcucha wymiany w tym samym czasie, zamiast przechodzić przez wszystkie te klasy?" Odpowiedź to kwestia wydajności: łańcuchy wymiany to zasoby urządzeń Direct3D, a zasoby urządzeń są powiązane z konkretnym urządzeniem Direct3D, które je utworzyło. Jeśli tworzysz nowe urządzenie z nowym łańcuchem zamiany, musisz ponownie utworzyć wszystkie zasoby urządzenia przy użyciu nowego urządzenia Direct3D. Dlatego tworząc łańcuch wymiany z tą samą fabryką (jak pokazano powyżej), możesz ponownie utworzyć łańcuch wymiany i kontynuować korzystanie z zasobów urządzeń Direct3D, które zostały już załadowane!
Teraz masz okno z systemu operacyjnego, sposób dostępu do GPU i jego zasobów oraz łańcuch wymiany do wyświetlania wyników renderowania. Wszystko, co zostało, to tylko połączyć wszystko w całość!
Tworzenie obiektu docelowego renderowania dla rysunku
Potok przetwarzania cieniowania potrzebuje zasobu do rysowania pikseli. Najprostszym sposobem utworzenia tego zasobu jest zdefiniowanie zasobu ID3D11Texture2D jako bufora wstecznego, do którego cieniowanie pikseli może rysować, a następnie wczytanie tej tekstury do łańcucha wymiany.
W tym celu należy utworzyć widok render-target. W trybie Direct3D widok to sposób uzyskiwania dostępu do określonego zasobu. W takim przypadku widok umożliwia shaderowi pikseli zapisywanie w teksturze, gdy kończy on swoje operacje na pikslach.
Przyjrzyjmy się temu kodowi. Ustawienie DXGI_USAGE_RENDER_TARGET_OUTPUT w łańcuchu wymiany umożliwia wykorzystanie bazowego zasobu Direct3D jako powierzchni rysunkowej. Aby uzyskać widok celu renderowania, wystarczy pobrać bufor zwrotny z łańcucha wymian i utworzyć widok celu renderowania związany z zasobem buforu zwrotnego.
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);
Utwórz również bufor głębokości-wzornika . Bufor danych głębokości i wzornika jest tylko określoną formą zasobu ID3D11Texture2D, który jest zwykle używany do określania, które piksele mają priorytet rysowania podczas rasteryzacji na podstawie odległości obiektów w scenie z kamery. Bufor głębi i szablonu może być również używany do efektów szablonu, gdzie określone piksele są odrzucane lub ignorowane podczas rasteryzacji. Ten bufor musi mieć taki sam rozmiar jak obiekt docelowy renderowania. Należy pamiętać, że nie można odczytać ani renderować do tekstury głębi buforu ramki, ponieważ jest on używany wyłącznie przez potok cieniowania przed i podczas ostatecznej rasteryzacji.
Utwórz również widok buforu wzornika głębokości jako ID3D11DepthStencilView. Widok informuje potok cieniowania, jak interpretować podstawowy zasób ID3D11Texture2D - więc jeśli nie podasz tego widoku, nie zostaną wykonane żadne testy głębokości na piksel, a obiekty w scenie mogą wydawać się co najmniej na odwrót!
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
);
Ostatnim krokiem jest utworzenie viewportu. To definiuje widoczny prostokąt bufora wstecznego wyświetlanego na ekranie; można zmienić część bufora, która jest wyświetlana na ekranie, poprzez zmianę parametrów widoku. Ten kod dotyczy całkowitego rozmiaru okna lub rozdzielczości ekranu w przypadku łańcuchów wymiany w trybie pełnoekranowym. Dla zabawy zmień podane wartości współrzędnych i obserwuj wyniki.
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
);
I tak idziesz od niczego do rysowania pikseli w oknie! Gdy zaczynasz, dobrym pomysłem jest zapoznanie się z sposobem, w jaki directX za pośrednictwem DXGI zarządza podstawowymi zasobami, których potrzebujesz, aby rozpocząć rysowanie pikseli.
Następnie przyjrzysz się strukturze potoku grafiki; zobacz Omówienie potoku renderowania szablonu aplikacji DirectX.
Tematy pokrewne