Eine einfache Aufgabe, die man gerne vergisst, wenn man mit Direct3D arbeitet. Für gewöhnlich arbeitet man mit dem Adapter (=Grafikkarte), welcher als Standard am Rechner eingestellt ist. Dies ist nämlich der Fall, wenn bei allen Initialisierungs-Methoden nicht explizit angegeben wird, welcher Adapter verwendet werden soll bzw. der Standard-Adapter mit Index 0. Aber was ist, wenn mehrere Grafikkarten im Rechner stecken, über die mehrere Monitore (>2) angebunden sind? Was ist, wenn der Software-Renderer (WARP) verwendet werden soll? In diesem Beitrag beschreibe ich ein Beispielprogramm, welches per DXGI (DirectX Graphics Infrastructure) alle verfügbaren Adapter ermittelt und in einer WPF-Oberfläche einige Informationen dazu anzeigt.
Was ist DXGI?
Zunächst ein paar allgemeine Worte zu DXGI: Es handelt sich um eine API, die vom Ansatz her als Bindeglied zwischen verschiedenen Grafik-APIs wirkt (z. B. Direct3D11, Direct3D10, Direct2D, …). Über DXGI ist es etwa möglich, Texturen oder andere Ressourcen gemeinsam über verschiedene APIs oder Device-Objekte hinweg zu verwenden. Auch Thread- oder Prozess-Grenzen können per DXGI überwunden werden. Für dieses Beispiel wird aber etwas anderes genutzt: Über DXGI können allgemeine Infos zur verfügbaren Hardware abgegriffen werden.
Wie ermittelt man alle verfügbaren Adapter?
Die Ermittlung aller Adapter ist grundsätzlich relativ einfach. Über DXGI ist es möglich, einfach über alle Adapter zu schleifen. Aber Achtung: Auf manchen Rechnern taucht so der Software Renderer (WARP) nicht auf. Aus diesem Grund ist es eine bessere Methode, den WARP-Adapter explizit über die Direct3D11-Schnittstelle anzulegen und sich daraus die Adapter-Informationen zu ziehen. Nachfolgend ein Code-Beispiel.
/// <summary> /// Creates a list containing all available devices. /// </summary> public static IEnumerable<EngineDevice> CreateDevices(bool debugEnabled) { using(DXGI.Factory1 dxgiFactory = new DXGI.Factory1()) { int adapterCount = dxgiFactory.GetAdapterCount1(); for(int loop=0 ; loop<adapterCount; loop++) { DXGI.Adapter1 actAdapter = dxgiFactory.GetAdapter1(loop); DXGI.AdapterDescription1 actAdapterDescription = actAdapter.Description1; // Check for software device (don't add software devices here, this is done one step later) bool isSoftware = (actAdapterDescription.Description == "Microsoft Basic Render Driver") || ((!string.IsNullOrEmpty(actAdapterDescription.Description)) && actAdapterDescription.Description.Contains("Software")); if (!isSoftware) { // Create the device object yield return new EngineDevice(actAdapter, isSoftware, debugEnabled); } } // Try to append warp adapter (=> Software renderer) using (D3D11.Device warpDevice = new D3D11.Device(D3D.DriverType.Warp)) using (DXGI.Device1 dxgiDevice = warpDevice.QueryInterface<DXGI.Device1>()) { DXGI.Adapter1 warpAdapter = dxgiDevice.GetParent<DXGI.Adapter1>(); if (warpAdapter != null) { yield return new EngineDevice(warpAdapter, true, debugEnabled); } } } }
Die Klasse EngineDevice ist eine Hilfsklasse von mir, welche sich schließlich alle Informationen aus dem Adapter-Objekt zieht und zusätzlich die Device-Instanzen für die verschiedenen DirectX-Schnittstellen anlegt. Aus dem Adapter-Objekt bekommt man nun Informationen wie Hersteller-ID, Name des Adapters, Speicherausstattung usw. heraus.
Wie ermittelt man alle verfügbaren Outputs?
Weiterhin eine sinnvolle Information ist auch, welche Outputs (=Monitore) an welchen Adaptern angeschlossen sind. Diese Info bekommt man, wenn man wie folgt an einem Adapter über alle Outputs schleift.
// Create all output informations DXGI.Output[] outputs = m_adapter.Outputs; m_outputs = new List<EngineOutputInfo>(outputs.Length); for (int loop = 0; loop < outputs.Length; loop++) { m_outputs.Add(new EngineOutputInfo(loop, outputs[loop])); }
Von einem Output bekommt man z. B. den Namen, die verwendete Auflösung, ob auf ihm ein Desktop angezeigt wird, …. Nachfolgend ein paar Eigenschaften, die ich im Beispielprogramm auslese.
/// <summary> /// Gets the name of the output device. /// </summary> [Category(TRANSLATABLE_GROUP_COMMON_OUTPUT_INFO)] public string DeviceName { get { return m_outputDescription.DeviceName; } } [Category(TRANSLATABLE_GROUP_COMMON_OUTPUT_INFO)] public bool IsAttachedToDesktop { get { return m_outputDescription.IsAttachedToDesktop; } } [Category(TRANSLATABLE_GROUP_COMMON_OUTPUT_INFO)] public string DesktopResolution { get { return m_outputDescription.DesktopBounds.Width + "x" + m_outputDescription.DesktopBounds.Height.ToString(); } } [Category(TRANSLATABLE_GROUP_COMMON_OUTPUT_INFO)] public string DesktopLocation { get { return "X = " + m_outputDescription.DesktopBounds.X + ", Y = " + m_outputDescription.DesktopBounds.Y; } } [Category(TRANSLATABLE_GROUP_COMMON_OUTPUT_INFO)] public string Rotation { get { return m_outputDescription.Rotation.ToString(); } }
Das war es eigentlich schon, mehr steckt nicht dahinter. Auf Grundlage dieser Informationen kann man nun besser steuern, auf welchem Adapter das Rendering läuft. Sinnvoll kann auch sein, den zu verwendenden Adapter durch den Benutzer auswählen zu lassen.
Downloads
- Beispiel-Quellcode
https://www.rolandk.de/files/Testing.GraphicsAdapterOverview.zip
Ebenfalls interessant
- Projekt SeeingSharp 2 (OpenSource 3D-Engine für C# / .Net)
https://github.com/RolandKoenig/SeeingSharp2