Arbeiten mit DirectX-Geräteressourcen
Erfahren Sie mehr über die Rolle des Microsoft DirectX Graphic Infrastructure (DXGI) in Ihrem Windows Store DirectX-Spiel. DXGI ist eine Reihe von APIs, die zum Konfigurieren und Verwalten von Grafik- und Grafikadapterressourcen auf niedriger Ebene verwendet werden. Ohne sie hätten Sie keine Möglichkeit, die Grafiken Ihres Spiels in ein Fenster zu zeichnen.
Stellen Sie sich DXGI auf diese Weise vor: Um direkt auf die GPU zuzugreifen und ihre Ressourcen zu verwalten, müssen Sie eine Möglichkeit haben, diese in Ihrer App zu beschreiben. Die wichtigste Information, die Sie über die GPU benötigen, ist der Ort zum Zeichnen von Pixeln, damit diese Pixel an den Bildschirm gesendet werden können. In der Regel wird dies als "Hintergrundpuffer" bezeichnet– eine Position im GPU-Speicher, an der Sie die Pixel zeichnen und dann "flippen" oder "tauschen" und auf einem Aktualisierungssignal an den Bildschirm senden können. MIT DXGI können Sie diese Position und die Mittel zur Verwendung dieses Puffers abrufen (dies wird als Swapkette bezeichnet, da es sich um eine Kette aus austauschbarer Puffer handelt, die mehrere Pufferungsstrategien ermöglicht).
Dazu benötigen Sie Zugriff, um in die Swapkette zu schreiben, und ein Handle für das Fenster, das den aktuellen Hintergrundpuffer für die Swapkette anzeigt. Sie müssen auch die beiden Verbinden, um sicherzustellen, dass das Betriebssystem das Fenster mit dem Inhalt des Backpuffers aktualisiert, wenn Sie dies anfordern.
Der gesamte Prozess zum Zeichnen auf dem Bildschirm sieht wie folgt aus:
- Abrufen eines CoreWindow-Werts für Ihre App
- Abrufen einer Schnittstelle für das Direct3D-Gerät und den Kontext
- Erstellen Sie die Swapkette, um das gerenderte Bild im CoreWindowanzuzeigen.
- Erstellen Sie ein Renderziel zum Zeichnen, und füllen Sie es mit Pixeln auf.
- Präsentieren Sie die Swapkette!
Erstellen eines Fensters für Ihre App
Zunächst müssen wir ein Fenster erstellen. Erstellen Sie zunächst eine Fensterklasse, indem Sie eine Instanz von WNDCLASSauffüllen und sie dann mit RegisterClassregistrieren. Die Fensterklasse enthält wichtige Eigenschaften des Fensters, einschließlich des verwendeten Symbols, der statischen Nachrichtenverarbeitungsfunktion (mehr dazu weiter unten) und eines eindeutigen Namens für die Fensterklasse.
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);
}
Als Nächstes erstellen Sie das Fenster. Außerdem müssen Sie Größeninformationen für das Fenster und den Namen der soeben erstellten Fensterklasse angeben. Wenn Sie CreateWindowaufrufen, erhalten Sie einen nicht transparenten Zeiger auf das Fenster, das als HWND bezeichnet wird. Sie müssen den HWND-Zeiger beibehalten und ihn immer dann verwenden, wenn Sie auf das Fenster verweisen müssen, einschließlich zerstören oder neu erstellen, und (besonders wichtig), wenn Sie die DXGI-Swapkette erstellen, die Sie zum Zeichnen im Fenster verwenden.
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);
}
Das Windows Desktop-App-Modell enthält einen Hook in die Windows Nachrichtenschleife. Sie müssen ihre Hauptprogrammschleife von diesem Hook abschwenken, indem Sie eine "StaticWindowProc"-Funktion schreiben, um Windowingereignisse zu verarbeiten. Dies muss eine statische Funktion sein, da Windows sie außerhalb des Kontexts einer Klasseninstanz aufruft. Hier ist ein sehr einfaches Beispiel für eine statische Nachrichtenverarbeitungsfunktion.
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);
}
In diesem einfachen Beispiel wird nur auf Beendigungsbedingungen des Programms überprüft: WM _ CLOSE, gesendet, wenn das Fenster geschlossen werden soll, und WM _ DESTROY, das gesendet wird, nachdem das Fenster tatsächlich vom Bildschirm entfernt wurde. Eine vollständige Produktions-App muss auch andere Windowingereignisse verarbeiten. Eine vollständige Liste der Fensterereignisse finden Sie unter Fensterbenachrichtigungen.
Die Hauptprogrammschleife selbst muss Windows Nachrichten bestätigen, indem Windows die Möglichkeit zum Ausführen der statischen Nachrichtenprozage zugelassen wird. Unterstützen Sie das Programm bei der effizienten Ausführung, indem Sie das Verhalten forken: Jede Iteration sollte neue Windows Nachrichten verarbeiten, wenn sie verfügbar sind, und wenn sich keine Nachrichten in der Warteschlange befinden, sollte ein neuer Frame gerendert werden. Hier ist ein sehr einfaches Beispiel:
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();
}
}
Abrufen einer Schnittstelle für das Direct3D-Gerät und den Kontext
Der erste Schritt bei der Verwendung von Direct3D besteht darin, eine Schnittstelle für die Direct3D-Hardware (DIE GPU) zu erhalten, die als Instanzen von ID3D11Device und ID3D11DeviceContextdargestellt wird. Erstere ist eine virtuelle Darstellung der GPU-Ressourcen, und letztere ist eine geräteunabhängige Abstraktion der Renderingpipeline und des Renderingprozesses. Dies ist eine einfache Möglichkeit, sich dies zu vorstellen: ID3D11Device enthält die Grafikmethoden, die Sie selten aufrufen, in der Regel vor dem Rendern, um die Ressourcen zu erhalten und zu konfigurieren, die Sie zum Zeichnen von Pixeln benötigen. ID3D11DeviceContext enthält dagegen die Methoden, die Sie jeden Frame aufrufen: Laden in Puffern und Ansichten und anderen Ressourcen, Ändern des Ausgabezusammenführungs- und Rasterizerzustands, Verwalten von Shadern und Zeichnen der Ergebnisse der Übergabe dieser Ressourcen durch die Zustände und Shader.
Es gibt einen sehr wichtigen Teil dieses Prozesses: das Festlegen der Featureebene. Die Featureebene teilt DirectX die Mindesthardwareebene mit, die Ihre App unterstützt, wobei D3D _ FEATURE _ LEVEL _ 9 _ 1 als niedrigster Featuresatz und D3D _ FEATURE LEVEL _ _ _ 11 1 die aktuelle höchste Ist-Stufe ist. Sie sollten 9 _ 1 als Mindestwert unterstützen, wenn Sie die größtmögliche Zielgruppe erreichen möchten. Nehmen Sie sich einige Zeit, um sich über Direct3D-Featureebenen zu informieren und die minimalen und maximalen Featureebenen zu bewerten, die Ihr Spiel unterstützen soll, und um die Auswirkungen Ihrer Wahl zu verstehen.
Rufen Sie Verweise (Zeiger) sowohl auf den Direct3D-Geräte- als auch auf den Gerätekontext ab, und speichern Sie sie als Variablen auf Klassenebene auf der DeviceResources-Instanz (als intelligente ComPtr-Zeiger). Verwenden Sie diese Verweise immer dann, wenn Sie auf das Direct3D-Gerät oder den Gerätekontext zugreifen müssen.
D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_9_1,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_11_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);
Erstellen der Swapkette
Ok: Sie haben ein Fenster zum Zeichnen, und Sie verfügen über eine Schnittstelle zum Senden von Daten und Zum Senden von Befehlen an die GPU. Nun sehen wir uns an, wie sie zusammengeführt werden.
Zunächst teilen Sie DXGI mit, welche Werte für die Eigenschaften der Swapkette verwendet werden sollen. Verwenden Sie dazu eine DXGI _ SWAP CHAIN _ _ DESC-Struktur. Sechs Felder sind besonders wichtig für Desktop-Apps:
- Windowed: Gibt an, ob die Auslagerungskette im Vollbildmodus oder im Fenster abgeschnitten ist. Legen Sie diese Einstellung auf TRUE fest, um die Swapkette in das zuvor erstellte Fenster zu setzen.
- BufferUsage: Legen Sie diese Einstellung auf DXGI _ USAGE RENDER TARGET OUTPUT _ _ _ fest. Dies gibt an, dass die Swapkette eine Zeichnungsoberfläche ist, sodass Sie sie als Direct3D-Renderziel verwenden können.
- SwapEffect: Legen Sie diese Einstellung auf DXGI _ SWAP EFFECT FLIP SEQUENTIAL _ _ _ fest.
- Format: Das _ UNORM-Format DXGI FORMAT _ B8G8R8A8 _ gibt eine 32-Bit-Farbe an: 8 Bits für jeden der drei RGB-Farbkanäle und 8 Bits für den Alphakanal.
- BufferCount: Legen Sie diese Einstellung auf 2 fest, um ein herkömmliches Verhalten mit doppelter Pufferung zu vermeiden. Legen Sie die Pufferanzahl auf 3 fest, wenn der Grafikinhalt mehr als einen Überwachungsaktualisierungszyklus benötigt, um einen einzelnen Frame zu rendern (bei 60 Hz beträgt der Schwellenwert beispielsweise mehr als 16 ms).
- SampleDesc: Dieses Feld steuert multisampling. Legen Sie Count (Anzahl) auf 1 und Quality (Qualität) für Flip-Model-Swapketten auf 0 (0) fest. (Um Multisampling mit Flip-Model-Swapketten zu verwenden, zeichnen Sie auf einem separaten Multisampling-Renderziel, und lösen Sie dieses Ziel dann in die Swapkette auf, bevor Sie es präsentieren. Beispielcode wird in Multisampling in Windows Store Appsbereitgestellt.)
Nachdem Sie eine Konfiguration für die Swapkette angegeben haben, müssen Sie dieselbe DXGI-Factory verwenden, die das Direct3D-Gerät (und den Gerätekontext) erstellt hat, um die Austauschkette zu erstellen.
**Kurzform: **
Abrufen des id3D11Device-Verweises, den Sie zuvor erstellt haben. Übertragen Sie ihn in IDXGIDevice3 (sofern noch nicht erfolgt), und rufen Sie dann IDXGIDevice::GetAdapter auf, um den DXGI-Adapter abzurufen. Rufen Sie die übergeordnete Factory für diesen Adapter ab, indem Sie IDXGIFactory2::GetParent aufrufen (IDXGIFactory2 erbt von IDXGIObject). Nun können Sie diese Factory verwenden, um die Swapkette durch Aufrufen von CreateSwapChainForHwndzu erstellen, wie im folgenden Codebeispiel zu sehen.
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
);
}
Wenn Sie gerade erst beginnen, ist es wahrscheinlich am besten, die hier gezeigte Konfiguration zu verwenden. Wenn Sie nun bereits mit früheren Versionen von DirectX vertraut sind, fragen Sie sich vielleicht: "Warum haben wir das Gerät und die Austauschkette nicht gleichzeitig erstellt, anstatt alle diese Klassen zu durchlaufen?" Die Antwort ist Effizienz: Austauschketten sind Direct3D-Geräteressourcen, und Geräteressourcen sind an das spezielle Direct3D-Gerät gebunden, das sie erstellt hat. Wenn Sie ein neues Gerät mit einer neuen Austauschkette erstellen, müssen Sie alle Geräteressourcen mit dem neuen Direct3D-Gerät neu erstellen. Wenn Sie also die Swapkette mit derselben Factory erstellen (wie oben gezeigt), können Sie die Swapkette neu erstellen und die Direct3D-Geräteressourcen weiter verwenden, die Sie bereits geladen haben!
Nun haben Sie ein Fenster vom Betriebssystem, eine Möglichkeit, auf die GPU und ihre Ressourcen zuzugreifen, und eine Swapkette zum Anzeigen der Renderingergebnisse. Alles, was noch übrig ist, besteht darin, das Ganze miteinander zu verbinden!
Erstellen eines Renderziels zum Zeichnen
Die Shaderpipeline benötigt eine Ressource, in die Pixel gezeichnet werden. Die einfachste Möglichkeit zum Erstellen dieser Ressource besteht darin, eine ID3D11Texture2D-Ressource als Hintergrundpuffer für den Pixel-Shader zu definieren, in den der Pixelshader gezeichnet werden soll, und diese Textur dann in die Swapkette zu lesen.
Hierzu erstellen Sie eine Renderzielansicht. In Direct3D ist eine Ansicht eine Möglichkeit, auf eine bestimmte Ressource zuzugreifen. In diesem Fall ermöglicht die Ansicht dem Pixel-Shader, in die Textur zu schreiben, während er seine Pixelvorgänge abschließt.
Sehen wir uns dazu den Code an. Wenn Sie DXGI _ USAGE RENDER TARGET OUTPUT für die _ _ _ Swapkette festlegen, kann die zugrunde liegende Direct3D-Ressource als Zeichenoberfläche verwendet werden. Um also die Renderzielansicht zu erhalten, müssen wir nur den Hintergrundpuffer aus der Swapkette abrufen und eine Renderzielansicht erstellen, die an die Backpufferressource gebunden ist.
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);
Erstellen Sie außerdem einen Tiefenschablonenpuffer. Ein Tiefenschablonenpuffer ist nur eine bestimmte Form der ID3D11Texture2D-Ressource, die in der Regel verwendet wird, um basierend auf dem Abstand der Objekte in der Szene von der Kamera zu bestimmen, welche Pixel während der Rasterung die Zeichnen-Priorität haben. Ein Tiefenschablonenpuffer kann auch für Schabloneneffekte verwendet werden, bei denen bestimmte Pixel während der Rasterung verworfen oder ignoriert werden. Dieser Puffer muss die gleiche Größe wie das Renderziel haben. Beachten Sie, dass Sie die Tiefenschablonentextur des Framepuffers nicht lesen oder in diese rendern können, da sie vor und während der endgültigen Rasterung ausschließlich von der Shaderpipeline verwendet wird.
Erstellen Sie außerdem eine Ansicht für den Tiefenschablonenpuffer als ID3D11DepthStencilView. Die Ansicht teilt der Shaderpipeline mit, wie die zugrunde liegende ID3D11Texture2D-Ressource interpretiert werden soll. Wenn Sie diese Ansicht also nicht bereitstellen, werden keine Tiefentests pro Pixel durchgeführt, und die Objekte in Ihrer Szene scheinen zumindest ein wenig nach außen zu wirken!
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
);
Der letzte Schritt besteht darin, einen Viewport zu erstellen. Dadurch wird das sichtbare Rechteck des auf dem Bildschirm angezeigten Hintergrundpuffers definiert. Sie können den Teil des Puffers ändern, der auf dem Bildschirm angezeigt wird, indem Sie die Parameter des Viewports ändern. Dieser Code ist für die gesamte Fenstergröße oder die Bildschirmauflösung im Fall von Vollbild-Auslagerungsketten verfügbar. Ändern Sie aus Spaß die angegebenen Koordinatenwerte, und beobachten Sie die Ergebnisse.
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
);
Und so wechseln Sie von nichts zum Zeichnen von Pixeln in einem Fenster! Wenn Sie beginnen, ist es eine gute Idee, sich damit vertraut zu machen, wie DirectX über DXGI die Kernressourcen verwaltet, die Sie zum Zeichnen von Pixeln benötigen.
Als Nächstes sehen Sie sich die Struktur der Grafikpipeline an. Weitere Informationen finden Sie unter Grundlegendes zur Renderingpipeline der DirectX-App-Vorlage.
Zugehörige Themen
-
Als Nächstes