Unterstützen der Bildschirmausrichtung (DirectX und C++)

Applies to Windows only

Zahlreiche Windows 8-Geräte unterstützen mehrere Bildschirmausrichtungen. Die Windows Store-Apps mit DirectX mit C++ können dieses Feature beim Behandeln des CoreWindow::SizeChanged-Ereignisses ebenfalls unterstützen. An dieser Stelle erläutern wir die bewährten Methoden zum Umgang mit der Bildschirmausrichtung in Ihrer Windows Store-App mit DirectX, sodass die Grafikhardware der Windows 8-Gärte effizient und effektiv verwendet wird.

Hinweis  Diese Methoden werden in der Projektvorlage Direct2D-App von Microsoft Visual Studio und im DXGI-Swapketten-Drehungsbeispiel vollständig demonstriert.

Beachten Sie vor Beginn, dass Grafikhardware grundsätzlich auf irgendeine Art und Weise Pixeldaten ausgibt und dass die Ausrichtung des Geräts dabei keine Rolle spielt. Windows 8-Geräte können ihre aktuelle Anzeigeausrichtung selbst bestimmen (mithilfe eines Sensors oder einer Softwareumschaltung), und sie ermöglichen dem Benutzer das Ändern der Anzeigeeinstellungen. Aufgrund dessen übernimmt Windows 8 die Drehung von Bildern selbst, um sicherzustellen, dass sie basierend auf der Ausrichtung des Geräts "aufrecht" angezeigt werden. Standardmäßig empfangen Apps eine Benachrichtigung mit dem Hinweis, dass bezüglich der Ausrichtung eine Änderung vorgenommen wurde (beispielsweise eine Änderung der Fenstergröße). Wenn dieser Fall eintritt, dreht Windows 8 das Bild sofort für die endgültige Anzeige. Für drei der vier speziellen Bildschirmausrichtungen (diese werden zu einem späteren Zeitpunkt erläutert) verwendet Windows 8 zusätzliche Grafikressourcen und Berechnungen, um das endgültige Bild anzuzeigen.

Für Windows Store-Apps, für die DirectX verwendet wird, stellt das DisplayProperties-Objekt grundlegende Bildschirmausrichtungsdaten bereit, die von der App abgefragt werden können. Die Standardausrichtung lautet Querformat, wobei die Pixelbreite der Anzeige größer ist als die Höhe. Die alternative Ausrichtung lautet Hochformat, in der die Anzeige um 90 Grad in eine Richtung gedreht und die Breite kleiner als die Höhe wird.

Windows 8 definiert vier spezielle Bildschirmausrichtungsmodi:

  • Querformat – dies ist die Bildschirmstandardausrichtung für Windows 8, und sie wird als Basis- oder Identitätswinkel für die Drehung betrachtet (0 Grad).
  • Hochformat – die Anzeige wurde um 270 Grad im Uhrzeigersinn gedreht (oder um 90 entgegen dem Uhrzeigersinn).
  • Querformat, gedreht – die Anzeige wurde um 180 Grad gedreht (über Kopf gedreht).
  • Hochformat, gedreht – die Anzeige wurde um 90 Grad im Uhrzeigersinn gedreht (oder um 270 Grad entgegen dem Uhrzeigersinn).

Wenn die Anzeige von einer Ausrichtung in eine andere gedreht wird, führt Windows 8 intern einen Drehvorgang aus, um das dargestellte Bild an die neue Ausrichtung anzupassen, und dem Benutzer wird auf dem Bildschirm ein aufrechtes Bild angezeigt.

Zudem zeigt Windows 8 automatische Übergangsanimationen an, um beim Wechseln zwischen den Ausrichtungen sanfte Übergänge zu erzeugen. Einen Wechsel der Bildschirmausrichtung nimmt der Benutzer mit einer festen Vergrößerung und einer Drehungsanimation des auf dem Bildschirm angezeigten Bilds wahr. Der App wird von Windows 8 Zeit für das Layout in der neuen Ausrichtung zugewiesen. Bei Windows Store-Apps, für die DirectX verwendet wird, ist die für das neue Layout erforderliche Zeit jedoch normalerweise kürzer als die zulässige Zeit, da andere Frameworks möglicherweise mehr der zugewiesenen Zeit benötigen. Da die App höchstwahrscheinlich nie die volle Zeit verwenden wird, die ihr zugewiesen wurde, können Sie Windows 8 direkt darüber benachrichtigen, dass die App alle erforderlichen Arbeitsschritte zum Behandeln der neuen Bildschirmausrichtung abgeschlossen und dass die App IDXGISwapChain::Present zum Anzeigen des neuen Bilds aufgerufen hat. Den vorzeitigen Layoutabschluss durch die App können Sie mit CoreWindowResizeManager::NotifyLayoutCompleted angeben.

Im Großen und Ganzen lautet der allgemeine Prozess zum Behandeln von Änderungen bezüglich der Bildschirmausrichtung wie folgt:

  1. Verwenden Sie eine Kombination aus den Fensterabgrenzungswerten und den Bildschirmausrichtungsdaten, um die Ausrichtung der Swapkette an der systemeigenen Bildschirmausrichtung des Geräts beizubehalten.
  2. Benachrichtigen Sie Windows 8 mithilfe von IDXGISwapChain1::SetRotation über die Ausrichtung der Swapkette.
  3. Ändern Sie den Code zum Rendern, um Bilder zu generieren, die an die benutzerdefinierte Ausrichtung des Geräts angepasst sind.
  4. Benachrichtigen Sie Windows 8 mit CoreWindowResizeManager::NotifyLayoutCompleted darüber, dass die App bereit zum Fortsetzen des Vorgangs in der neuen Ausrichtung ist.

Wenn Sie den vollständigen Code für diesen Prozess anzeigen möchten, der auf das Rendern von Direct2D und Direct3D angewendet wird, laden Sie das DXGI-Swapketten-Drehungsbeispiel herunter.

Für die Behandlung der Ausrichtungsänderung gibt es in der Windows Store-App, für die DirectX verwendet wird, zwei Methoden:

Diese Methoden sehen wir uns einmal genauer an.

Methode 1: Ändern der Swapkettengröße

Der einfachste Ansatz zum Behandeln der Drehung besteht in der Ausführung der Aktion durch Windows 8. Da das Seitenverhältnis der Anzeige nicht 1:1 beträgt, sehen die Ergebnisse natürlich gestreckt oder komprimiert aus. Wie lässt sich dieses Problem lösen? Ändern Sie die Swapkettengröße mithilfe der Höhen- und Breitenwerte für die ausgetauschten Fensterbegrenzungen, und stellen Sie die Inhalte dar.

Wenn Sie eine einfache Änderung der Anzeigegröße in der Windows Store-App ausführen möchten, führen Sie die folgenden Schritte aus:

  1. Behandeln Sie das CoreWindow::SizeChanged-Ereignis.
  2. Ändern Sie die Swapkettengröße in die neuen Fensterabmessungen.
  3. Erstellen Sie die von der Fenstergröße abhängigen Ressourcen neu, beispielsweise die Renderziele und andere Pixeldatenpuffer.

Jetzt wollen wir uns die Schritte etwas genauer ansehen.

Im ersten Schritt registrieren Sie einen Handler für das CoreWindow::SizeChanged-Ereignis. Dieses Ereignis wird immer dann im CoreWindow der App ausgelöst, wenn sich die Bildschirmgröße ändert (beispielsweise beim Drehen der Anzeige). Wenn Sie in der App-Datei package.appxmanifest angegeben haben, dass die App die Bildschirmdrehung unterstützt, müssen Sie das SizeChanged-Ereignis behandeln. (Anderenfalls wird bei einer Änderung der Ausrichtung die Größer der App-Anzeige automatisch geändert, und die Swapketteninhalte werden gestreckt oder komprimiert, damit sie in den neuen Bereich passen.)

Zum Behandeln des SizeChanged-Ereignisses verbinden Sie den Handler für CoreWindow::SizeChanged in der erforderlichen SetWindow-Methode. Dabei handelt es sich um eine der Methoden der IFrameworkView-Schnittstelle, die vom Ansichtsanbieter implementiert werden muss.

In diesem Codebeispiel ist der Ereignishandler für CoreWindow::SizeChanged eine Methode mit der Bezeichnung OnWindowSizeChanged, und er wird auch für das Ansichtsanbieterobjekt definiert. Beim Auslösen von CoreWindow::SizeChanged wird wiederum eine Methode mit der Bezeichnung UpdateForWindowSizeChange aufgerufen, die für das Rendererobjekt definiert wurde.


void MyDirectXApp::SetWindow(
    _In_ CoreWindow^ window
    )
{
  // ... Other UI event handlers assigned here ...

  window->SizeChanged += ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(
                                 this,
                                 &MyDirectXApp::OnWindowSizeChanged
                                 );
  // ...

}


void MyDirectXApp::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
{
  m_renderer->UpdateForWindowSizeChange(); // m_renderer is an object that inherits from DirectXBase
}

Ändern Sie nun die Swapkettengröße aus dem Rückruf. (In dem Codebeispiel wird dieser Vorgang in DirectXBase::UpdateForWindowSizeChange ausgeführt.)



// This routine is called in the event handler for the view SizeChanged event.
void DirectXBase::UpdateForWindowSizeChange()
{
  if (m_window->Bounds.Width  != m_windowBounds.Width ||
      m_window->Bounds.Height != m_windowBounds.Height ||
      m_orientation != DisplayProperties::CurrentOrientation)
  {
    m_d2dContext->SetTarget(nullptr);
    m_d2dTargetBitmap = nullptr;
    m_d3dRenderTargetView = nullptr;
    m_d3dDepthStencilView = nullptr;
    m_windowSizeChangeInProgress = true;
    CreateWindowSizeDependentResources();
  }
}




// Allocate all memory resources that change on a window SizeChanged event.
void DirectXBase::CreateWindowSizeDependentResources()
{
    // Store the window bounds so the next time we get a SizeChanged event we can
    // avoid rebuilding everything if the size is identical.
    m_windowBounds = m_window->Bounds;

    if (m_swapChain != nullptr)
    {
        // If the swap chain already exists, resize it.
        HRESULT hr = m_swapChain->ResizeBuffers(
            2,
            static_cast<UINT>(m_renderTargetSize.Width),
            static_cast<UINT>(m_renderTargetSize.Height),
            DXGI_FORMAT_B8G8R8A8_UNORM,
            0
            );

        if (hr == DXGI_ERROR_DEVICE_REMOVED)
        {
            // If the device was removed for any reason, a new device and swapchain will need to be created.
            HandleDeviceLost();

            // Everything is set up now. Don't continue to execute this method.
            return;
        }
        else
        {
            DX::ThrowIfFailed(hr);
        }
    }
    else
    {
        // ...
        // Otherwise, create a new one using the same adapter as the existing Direct3D device.
        // ...       
    }

    // Create a Direct3D render target view of the swap chain back buffer.
    ComPtr<ID3D11Texture2D> backBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
        );

    DX::ThrowIfFailed(
        m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_d3dRenderTargetView
            )
        );

    // ...
    // Set up other window size dependent resources like Direct2D contexts, depth stencils, 
    // and 3D viewports here...
    // ...

}


Bei dieser Methode konfigurieren Sie ebenfalls die von der Fenstergröße abhängigen DirectX-Ressourcen neu bzw. Sie ändern die Größe der von der Fenstergröße abhängigen DirectX-Ressourcen. Dazu zählen beispielsweise Tiefenschablonen und die Direct3D-Viewports, falls eine 3-D-Szene angezeigt werden soll.

Da die Größe der Swapkette nun effektiv geändert wurde und sie effizient dargestellt wird, werden die Bildschirminhalte der App in der neuen Ausrichtung ordnungsgemäß angezeigt, nachdem Windows 8 die Systemdrehungsanimation abgeschlossen hat. Windows 8 führt jedoch dennoch einen eigenen Pixeldaten-Kopiervorgang des gesamten Bildschirms für die Drehungsanimation aus. Dabei wird eine Momentaufnahme des Bildschirmpuffers erstellt, und das Bild selbst wird gedreht, bevor die dargestellte Swapkette der App angezeigt wird. Sie können diese Redundanz verhindern und der GPU Arbeit ersparen (insbesondere dann, wenn die systemeigene Auflösung hoch ist), indem Sie Windows 8 darüber informieren, dass Sie die Pipeline zum Rendern bereits gedreht haben. Diese potenzielle Optimierung werden wir im nächsten Abschnitt untersuchen. Dabei betrachten wird auch die Behandlung der Direct2D- und Direct3D-Komponenten der Swapkette, für die beim Zusammenstellen des gedrehten Bilds jeweils eine andere Technik erforderlich ist.

Den vollständigen Code zur Veranschaulichung dieses Prozesses finden Sie in der Vorlage Direct2D-Anwendung von Visual Studio, oder laden Sie das DXGI-Swapketten-Drehungsbeispiel herunter.

Methode 2: Vorabdrehung der Inhalte der Swapkette

Direct3D 11 und DXGI stellen die neue API IDXGISwapChain1::SetRotation bereit, mit deren Hilfe Sie in der Windows Store-App, für die DirectX verwendet wird, Windows 8 über die Ausrichtung der Fensterbilddaten in der Swapkette informieren können. Falls die Ausrichtung des Geräts und der Swapkette übereinstimmen, können die Fensterbilder von Windows 8 direkt ohne Drehung verwendet werden. Zudem vermeiden Sie dadurch einen zusätzlichen Kopiervorgang des gesamten Bildschirms auf Hardwareebene, wodurch Sie wiederum Leistung und Batterienutzungsdauer für die App einsparen. Die App muss die Bilder jedoch in der Swapkette rendern, um die Ausrichtung zuzuordnen. Demzufolge können Sie diese zusätzliche Effizienz ausnutzen.

Wie bereits zuvor erläutert, kann die Windows Store-App, für die DirectX verwendet wird, ein Drehungsereignis zumindest auf einfache Art und Weise behandeln, indem die Größe der Swapkettenpuffer auf die neue Höhe und Breite des Bildschirms angepasst und die Bildschirminhalte anschließend dargestellt werden.

Die Aufgabe wird zwar während des Prozesses erledigt, diese Methode hat jedoch einige Nachteile:

  • Es wird ein redundanter Kopiervorgang des gesamten Bildschirms ausgeführt.
  • Es wird tatsächlich nicht zwischen dem Typ des Größenänderungsereignisses unterschieden, das durch den Wechsel eines App-Fensters in einen angedockten oder gefüllten Zustand oder durch eine Änderung der Bildschirmauflösung ausgelöst werden könnte.

Daher aktualisieren wir den Prozess, um diese Ineffizienz durch Ausführen der folgenden aktualisierten Schritte zu beseitigen:

  1. Behandeln Sie das CoreWindow::SizeChanged-Ereignis wie zuvor.
  2. Bestätigen Sie mithilfe von DisplayProperties::CurrentOrientation, dass das Ereignis zum Ändern der Fenstergröße spezielle für eine Drehung eintritt.
  3. Ändern Sie keinesfalls die Größe der Swapkette! Verwenden Sie stattdessen die aktuellen Ausrichtungsdaten, um die in der App enthaltenen Direct2D- und Direct3D-Transformationen zu aktualisieren und die Pipeline zum Rendern zu Drehen.
  4. Rufen Sie IDXGISwapChain1::SetRotation auf, um die Ausrichtung der Swapkette festzulegen.
  5. Erstellen Sie wie zuvor alle von der Fenstergröße abhängigen Ressourcen neu.

Es folgt die Langform zum Ändern der Swapkettengröße für die neue Bildschirmausrichtung und zu deren Vorbereitung auf die Drehung der Inhalte der Grafikpipeline, wenn das Rendern erfolgt. In diesem Beispiel ist DirectXBase::CreateWindowSizeDependentResources eine Methode, die Sie für Ihr Rendererobjekt implementieren.

Nehmen Sie sich einen Moment Zeit, um sich den Code anzusehen. Anschließend werden wir ihn nach dem Sprung besprechen.



// Allocate all memory resources that change on a window SizeChanged event.
void DirectXBase::CreateWindowSizeDependentResources()
{
    // Store the window bounds so the next time we get a SizeChanged event we can
    // avoid rebuilding everything if the size is identical.
    m_windowBounds = m_window->Bounds;

    // Calculate the necessary swap chain and render target size in pixels.
    auto windowWidth = ConvertDipsToPixels(m_windowBounds.Width);
    auto windowHeight = ConvertDipsToPixels(m_windowBounds.Height);

    // Swap width and height based on orientation.
    m_orientation = DisplayProperties::CurrentOrientation;
    bool swapDimensions = (
        m_orientation == DisplayOrientations::Portrait ||
        m_orientation == DisplayOrientations::PortraitFlipped
        );
    m_renderTargetSize.Width = swapDimensions ? windowHeight : windowWidth;
    m_renderTargetSize.Height = swapDimensions ? windowWidth : windowHeight;

    if (m_swapChain != nullptr)
    {
        // If the swap chain already exists, resize it.
        HRESULT hr = m_swapChain->ResizeBuffers(
            2,
            static_cast<UINT>(m_renderTargetSize.Width),
            static_cast<UINT>(m_renderTargetSize.Height),
            DXGI_FORMAT_B8G8R8A8_UNORM,
            0
            );

        if (hr == DXGI_ERROR_DEVICE_REMOVED)
        {
            // If the device was removed for any reason, a new device and swapchain will need to be created.
            HandleDeviceLost();

            // Everything is set up now. Don't continue to execute this method.
            return;
        }
        else
        {
            DX::ThrowIfFailed(hr);
        }
    }
    else
    {
        // Otherwise, create a new one using the same adapter as the existing Direct3D device.
        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
        swapChainDesc.Width = static_cast<UINT>(m_renderTargetSize.Width); // Match the size of the window.
        swapChainDesc.Height = static_cast<UINT>(m_renderTargetSize.Height);
        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;           // This is the most common swap chain format.
        swapChainDesc.Stereo = false;
        swapChainDesc.SampleDesc.Count = 1;                          // Don't use multi-sampling.
        swapChainDesc.SampleDesc.Quality = 0;
        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 2;                               // Use double-buffering to minimize latency.
        swapChainDesc.Scaling = DXGI_SCALING_NONE;
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
        swapChainDesc.Flags = 0;

        ComPtr<IDXGIDevice1> dxgiDevice;
        DX::ThrowIfFailed(
            m_d3dDevice.As(&dxgiDevice)
            );

        ComPtr<IDXGIAdapter> dxgiAdapter;
        DX::ThrowIfFailed(
            dxgiDevice->GetAdapter(&dxgiAdapter)
            );

        ComPtr<IDXGIFactory2> dxgiFactory;
        DX::ThrowIfFailed(
            dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
            );

        CoreWindow^ window = m_window.Get();
        DX::ThrowIfFailed(
            dxgiFactory->CreateSwapChainForCoreWindow(
                m_d3dDevice.Get(),
                reinterpret_cast<IUnknown*>(window),
                &swapChainDesc,
                nullptr,
                &m_swapChain
                )
            );

        // Ensure that DXGI doesn't queue more than one frame at a time. This both reduces latency and
        // ensures that the application will only render after each VSync, minimizing power consumption.
        DX::ThrowIfFailed(
            dxgiDevice->SetMaximumFrameLatency(1)
            );
    }

    // Set the proper orientation for the swap chain, and generate 2-D and
    // 3-D matrix transformations for rendering to the rotated swap chain.
    // Note the rotation angle for the 2-D and 3-D transforms are different.
    // This is due to the difference in coordinate spaces.  Additionally,
    // the 3-D matrix is specified explicitly to avoid rounding errors.
    DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
    switch (m_orientation)
    {
        case DisplayOrientations::Landscape:
            rotation = DXGI_MODE_ROTATION_IDENTITY;
            m_rotationTransform2D = Matrix3x2F::Identity();
            m_rotationTransform3D = identity();
            break;
        case DisplayOrientations::Portrait:
            rotation = DXGI_MODE_ROTATION_ROTATE270;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(270.0f) *
                Matrix3x2F::Translation(0.0f, m_windowBounds.Width);
            m_rotationTransform3D = float4x4( // 90-degree Z-rotation
                0.0f, -1.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f
                );
            break;
        case DisplayOrientations::LandscapeFlipped:
            rotation = DXGI_MODE_ROTATION_ROTATE180;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(180.0f) *
                Matrix3x2F::Translation(m_windowBounds.Width, m_windowBounds.Height);
            m_rotationTransform3D = float4x4( // 180-degree Z-rotation
                -1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, -1.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f
                );
            break;
        case DisplayOrientations::PortraitFlipped:
            rotation = DXGI_MODE_ROTATION_ROTATE90;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(90.0f) *
                Matrix3x2F::Translation(m_windowBounds.Height, 0.0f);
            m_rotationTransform3D = float4x4( // 270-degree Z-rotation
                0.0f, 1.0f, 0.0f, 0.0f,
                -1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f
                );
            break;
        default:
            throw ref new Platform::FailureException();
            break;
    }

    DX::ThrowIfFailed(
        m_swapChain->SetRotation(rotation)
        );

    // Create a Direct3D render target view of the swap chain back buffer.
    ComPtr<ID3D11Texture2D> backBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
        );

    DX::ThrowIfFailed(
        m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_d3dRenderTargetView
            )
        );

    // Create a depth stencil view for use with 3-D rendering if needed.
    CD3D11_TEXTURE2D_DESC depthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT,
        static_cast<UINT>(m_renderTargetSize.Width),
        static_cast<UINT>(m_renderTargetSize.Height),
        1,
        1,
        D3D11_BIND_DEPTH_STENCIL
        );

    ComPtr<ID3D11Texture2D> depthStencil;
    DX::ThrowIfFailed(
        m_d3dDevice->CreateTexture2D(
            &depthStencilDesc,
            nullptr,
            &depthStencil
            )
        );

    auto viewDesc = CD3D11_DEPTH_STENCIL_VIEW_DESC(D3D11_DSV_DIMENSION_TEXTURE2D);
    DX::ThrowIfFailed(
        m_d3dDevice->CreateDepthStencilView(
            depthStencil.Get(),
            &viewDesc,
            &m_d3dDepthStencilView
            )
        );

    // Set the 3-D rendering viewport to target the entire window.
    CD3D11_VIEWPORT viewport(
        0.0f,
        0.0f,
        m_renderTargetSize.Width,
        m_renderTargetSize.Height
        );

    m_d3dContext->RSSetViewports(1, &viewport);

    // Create a Direct2D target bitmap associated with the
    // swap chain back buffer and set it as the current target.
    D2D1_BITMAP_PROPERTIES1 bitmapProperties =
        BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            m_dpi,
            m_dpi
            );

    ComPtr<IDXGISurface> dxgiBackBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
        );

    DX::ThrowIfFailed(
        m_d2dContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.Get(),
            &bitmapProperties,
            &m_d2dTargetBitmap
            )
        );

    m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());

    // Grayscale text anti-aliasing is recommended for all Windows Store apps.
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

}


Nachdem Sie die aktuellen Höhen- und Breitenwerte des Fensters für den nächsten Aufruf dieser Methode gespeichert haben, konvertieren Sie die DIP-Werte (Device Independent Pixel) für die Bildschirmbegrenzungen in Pixel. In dem Beispiel rufen Sie ConvertDipsToPixels auf. Dabei handelt es sich um eine einfache Funktion, die den folgenden Code ausführt:

floor((dips * DisplayProperties::LogicalDpi / 96.0f) + 0.5f);

0.5f wird hinzugefügt, um die Rundung auf den nächsten ganzzahligen Wert sicherzustellen.

Nebenbei bemerkt, CoreWindow-Koordinaten werden immer in DIPs definiert. In Windows 8 und früheren Versionen von Windows ist ein DIP als 1/96 Zoll definiert und wird an die Betriebssystemdefinition für oben angepasst. Beim Drehen der Bildschirmausrichtung in das Hochformat vertauscht die App die Breite und Höhe von CoreWindow, und die Renderzielgröße (Begrenzungen) müssen entsprechend geändert werden. Da die Direct3D-Koordinaten immer in physikalischen Pixeln angegeben sind, müssen Sie die DIP-Werte von CoreWindow in ganzzahlige Pixelwerte konvertieren, bevor Sie diese Werte zum Einrichten der Swapkette an Direct3D übergeben.

Für die einzelnen Prozesse ist die Arbeit ein wenig umfangreicher als bei einer einfachen Änderung der Swapkettengröße: Sie drehen die Direct2D- und Direct3D-Komponenten Ihres Bilds, bevor Sie sie für die Darstellung zusammenstellen, und Sie informieren die Swapkette darüber, dass Sie die Ergebnisse in einer neuen Ausrichtung gerendert haben. Nachstehend finden Sie etwas mehr Details zu diesem Prozess, wie im Codebeispiel für DirectXBase::CreateWindowSizeDependentResources veranschaulicht wird:

  • Bestimmen Sie die neue Ausrichtung der Anzeige. Wenn die Anzeige vom Querformat in das Hochformat oder umgekehrt geändert wurde, tauschen Sie die Höhen- und Breitenwerte – die natürlich von DIP-Werten in Pixel geändert wurden – für die Bildschirmbegrenzungen aus.

  • Überprüfen Sie dann, ob die Swapkette erstellt wurde. Wenn dies nicht der Fall ist, erstellen Sie sie durch Aufrufen von IDXGIFactory2::CreateSwapChainForCoreWindow. Ändern Sie anderenfalls die Puffergröße der vorhandenen Swapkette in die neuen Anzeigeabmessungen, indem Sie IDXGISwapchain:ResizeBuffers aufrufen. Obwohl Sie die Größe der Swapkette für das Drehungsereignis nicht ändern müssen – schließlich geben Sie Inhalte aus, die bereits von der Pipeline zum Rendern gedreht wurden – existieren auch andere Größenänderungsereignisse, beispielsweise Andock- und Füllungsereignisse, für die Größenänderungen erforderlich sind.

  • Legen Sie danach die entsprechende 2-D- oder 3-D-Matrixtransformation fest, die beim Rendern in der Swapkette auf die Pixel bzw. Vertizes in der Grafikpipeline angewendet werden soll. Es existieren vier mögliche Drehungsmatrizen:

    • Querformat (DXGI_MODE_ROTATION_IDENTITY)
    • Hochformat (DXGI_MODE_ROTATION_ROTATE270)
    • Querformat, gedreht (DXGI_MODE_ROTATION_ROTATE180)
    • Hochformat, gedreht (DXGI_MODE_ROTATION_ROTATE90)

    Die Auswahl der richtigen Matrix erfolgt basierend auf den von Windows 8 bereitgestellten Daten (beispielsweise basierend auf den Ergebnissen von DisplayProperties::OrientationChanged oder SimpleOrientationSensor::OrientationChanged) zum Bestimmen der Bildschirmausrichtung, und sie wird mit den Koordinaten der einzelnen Pixel (Direct2D) oder Vertizes (Direct3D) in der Szene multipliziert, wodurch sie zur Anpassung an die Bildschirmausrichtung effektiv gedreht werden. (Beachten Sie, dass der Bildschirmursprung in Direct2D als obere linke Ecke und in Direct3D als logische Mitte des Fensters definiert ist.)

Hinweis  Weitere Informationen zu den für die Drehung verwendeten 2-D-Transformationen und zu deren Definition finden Sie unter Definieren von Matrizen für die Bildschirmdrehung (2-D). Weitere Informationen zu den für die Drehung verwendeten 3-D-Transformationen finden Sie unter Definieren von Matrizen für die Bildschirmdrehung (3-D).

Nun zum wichtigen Teil: rufen Sie IDXGISwapChain1::SetRotation auf, und stellen Sie dafür die aktualisierte Rotationsmatrix bereit – Beispiel:

m_swapChain->SetRotation(rotation);

Die ausgewählte Rotationsmatrix wird auch unter dem Verzeichnis gespeichert, in dem sie von der Rendermethode beim Berechnen der neuen Projektion abgerufen werden kann. Sie verwenden diese Matrix beim Rendern der endgültigen 3-D-Projektion oder beim Zusammenstellen des endgültigen 2-D-Layouts. (Sie wird nicht automatisch angewendet.)

Erstellen Sie nun ein neues Renderziel für die gedrehte 3-D-Ansicht sowie einen neuen Tiefenschablonenpuffer für die Ansicht. Legen Sie den Viewport zum 3-D-Rendern für die gedrehte Szene fest, indem Sie ID3D11DeviceContext:RSSetViewports aufrufen.

Wenn Sie 2-D-Bilder drehen oder ein Layout für 2-D-Bilder erzeugen müssen, erstellen Sie zuletzt mithilfe von ID2D1DeviceContext::CreateBitmapFromDxgiSurface ein 2-D-Renderziel als beschreibbare Bitmap-Datei für die Swapkette mit geänderter Größe, und stellen Sie das neue Layout für die aktualisierte Ausrichtung zusammen. Legen Sie die erforderlichen Eigenschaften für das Renderziel fest, beispielsweise den Antialiasing-Modus (siehe Codebeispiel).

Stellen Sie nun die Swapkette dar.

Reduzieren der Rotationsverzögerung mit CoreWindowResizeManager

Standardmäßig wird in Windows 8 für beliebige Apps ein kurzes, aber wahrnehmbares Zeitfenster für die Drehung des Bilds bereitgestellt. Dies ist unabhängig vom Modell oder der Sprache der App. Wenn die App die Drehungsberechnung mithilfe einer der hier beschriebenen Techniken ausführt, ist dieser Vorgang möglicherweise bereits abgeschlossen, bevor dieses Zeitfenster abgelaufen ist. Es wäre doch wünschenswert, diese Zeit zurückzugewinnen und die Drehungsanimation abzuschließen, oder? An dieser Stelle kommt CoreWindowResizeManager ins Spiel.

So verwenden Sie CoreWindowResizeManager: wenn ein CoreWindow::SizeChanged-Ereignis ausgelöst wird, rufen Sie CoreWindowResizeManager::GetForCurrentView innerhalb des Handlers für das Ereignis auf, um eine Instanz von CoreWindowResizeManager zu erhalten. Wenn das Layout für die neue Ausrichtung abgeschlossen ist und dargestellt wird, rufen Sie NotifyLayoutCompleted auf, um Windows darüber zu informieren, dass die Drehungsanimation abgeschlossen und der App-Bildschirm angezeigt werden kann.

Der Code im Ereignishandler für CoreWindow::SizeChanged kann wie folgt aussehen:


CoreWindowResizeManager^ resizeManager = Windows::UI::Core::CoreWindowResizeManager::GetForCurrentView();

// ... build the layout for the new display orientation ...

resizeManager->NotifyLayoutCompleted();

Wenn ein Benutzer die Bildschirmausrichtung dreht, zeigt Windows 8 unabhängig von der App eine Animation als Feedback für den Benutzer an. Diese Animation besteht aus drei Teilen, die in der folgenden Reihenfolge ablaufen:

  • Windows 8 verkleinert das ursprüngliche Bild.
  • Windows 8 speichert das Bild während der Neuerstellung des neuen Layouts. Dies entspricht dem Zeitfenster, das Sie verkürzen möchten, weil die App wahrscheinlich nicht das gesamte Zeitfenster benötigt.
  • Bei Ablauf des Zeitfensters für das Layout oder beim Empfang einer Benachrichtigung über den Layoutabschluss dreht Windows das Bild, und es erfolgt eine Vergrößerung mit Überblendung in die neue Ausrichtung.

Gemäß Punkt drei beendet Windows 8 beim Aufrufen von NotifyLayoutCompleted durch eine App das Zeitüberschreitungsfenster. Zudem wird die Drehungsanimation abgeschlossen und die Kontrolle wieder an die App übergeben, die die Darstellung nun in der neuen Bildschirmausrichtung ausführt. In der Gesamtheit fühlt sich die App nun ein wenig flüssiger und schneller an, und sie arbeitet etwas effizienter!

Anhang A: Anwenden von Matrizen für die Bildschirmdrehung (2-D)

In dem Beispiel unter Optimieren des Drehungsprozesses (und im DXGI-Swapketten-Drehungsbeispiel) haben Sie möglicherweise bemerkt, dass wir für die Direct2D- und die Direct3D-Ausgabe separate Drehungsmatrizen verwendet haben. Sehen wir uns zunächst die 2-D-Matrizen an.

Es gibt zwei Ursachen dafür, warum wir für Direct2D- und Direct3D-Inhalte nicht dieselben Drehungsmatrizen anwenden können:

  • Erstens: Sie verwenden unterschiedliche kartesische Koordinatenmodelle. Direct2D verwendet die rechtshändige Regel, wobei der positive Wert der Y-Koordinate vom Ausgangspunkt nach oben hin ansteigt. Für Direct3D wird jedoch die linkshändige Regel verwendet, wobei der positive Wert der Y-Koordinate vom Ausgangspunkt nach rechts hin ansteigt. Demzufolge befindet sich der Ausgangspunkt für die Bildschirmkoordinaten bei Direct2D oben links, während der Ausgangspunkt für den Bildschirm (Projektionsebene) für Direct3D unten links liegt. (Weitere Informationen finden Sie unter 3-D-Koordinatensysteme.)

    Direct3D-Koordinatensystem.Direct2D-Koordinatensystem.
  • Zweitens: Die 3-D-Drehungsmatrizen müssen explizit angegeben werden, um Rundungsfehler zu vermeiden.

Die Swapkette geht davon aus, dass der Ausgangspunkt unten links liegt. Daher müssen Sie eine Drehung ausführen, um das rechtshändige Direct2D-Koordinatensystem an das von der Swapkette verwendete linkshändige Koordinatensystem anzupassen. Insbesondere ordnen Sie das Bild unter der neuen linkshändigen Ausrichtung neu an, indem Sie die Drehungsmatrix mit einer Übersetzungsmatrix für den Ausgangspunkt des gedrehten Koordinatensystems multiplizieren. Zudem transformieren Sie das Bild aus dem Koordinatenbereich von CoreWindow in den Koordinatenbereich der Swapkette. Die App muss diese Transformation auch konsistent anwenden, wenn das Direct2D-Renderziel mit der Swapkette verbunden ist. Wenn die App jedoch in Zwischenflächen zeichnet, die der Swapkette nicht direkt zugeordnet snd, wenden Sie diese Transformation des Koordinatenbereichs nicht an.

Der Code zum Auswählen der richtigen Matrix aus den vier möglichen Drehungen kann beispielsweise wie folgt lauten (beachten Sie die Übersetzung in den neuen Koordinatensystemursprung):


	// Set the proper orientation for the swap chain, and generate 2-D and
 // 3-D matrix transformations for rendering to the rotated swap chain.

    DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
    switch (m_orientation)
    {
        case DisplayOrientations::Landscape:
            rotation = DXGI_MODE_ROTATION_IDENTITY;
            m_rotationTransform2D = Matrix3x2F::Identity();
            break;
        case DisplayOrientations::Portrait:
            rotation = DXGI_MODE_ROTATION_ROTATE270;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(270.0f) *
                Matrix3x2F::Translation(0.0f, m_windowBounds.Width);
            break;
        case DisplayOrientations::LandscapeFlipped:
            rotation = DXGI_MODE_ROTATION_ROTATE180;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(180.0f) *
                Matrix3x2F::Translation(m_windowBounds.Width, m_windowBounds.Height);
            break;
        case DisplayOrientations::PortraitFlipped:
            rotation = DXGI_MODE_ROTATION_ROTATE90;
            m_rotationTransform2D =
                Matrix3x2F::Rotation(90.0f) *
                Matrix3x2F::Translation(m_windowBounds.Height, 0.0f);
             break;
        default:
            throw ref new Platform::FailureException();
            break;
    }

Nachdem Sie die richtige Drehungsmatrix und den Ursprung für das 2-D-Bild ausgewählt haben, legen Sie sie bzw. ihn durch Aufrufen von ID2D1DeviceContext::SetTransform zwischen den Aufrufen von ID2D1DeviceContext::BeginDraw und ID2D1DeviceContext::EndDraw fest.

Warnung   Direct2D hat keinen Transformationsstapel. Wenn die App zudem ID2D1DeviceContext::SetTransform als Teil des zugehörigen Zeichnungscodes verwendet, muss diese Matrix im Nachhinein für alle angewendeten Transformationen multipliziert werden.


m_d2dContext->BeginDraw();

// draw reference bitmap at the center of the screen
m_d2dContext->SetTransform(
        Matrix3x2F::Translation(
            m_windowBounds.Width / 2.0f - bitmapWidth / 2.0f,
            m_windowBounds.Height / 2.0f - bitmapHeight / 2.0f
        ) *
     m_rotationTransform2D // apply 2D prerotation transform
);
m_d2dContext->DrawBitmap(m_referenceBitmap.Get());

m_d2dcontext->EndDraw();

Im vorherigen Beispiel wird das Bild zudem durch Multiplizieren der neuen Drehungsmatrix mit einer weiteren Übersetzungsmatrix zentriert. In dieser Übersetzungsmatrix wird die obere linke Ecke des Bilds angegeben, die sich nach erfolgter Drehung immer links oberhalb der Bildschirmmitte befinden muss. (Dies ist ein optionaler Schritt.) Zeichnen Sie das gedrehte Bild im Gerätekontext, indem Sie ID2D1DeviceContext::DrawBitmap aufrufen.

Bei der nächsten Darstellung der Swapkette wird das 2-D-Bild gedreht, sodass es mit der neuen Bildschirmausrichtung übereinstimmt.

Anhang B: Anwenden von Matrizen für die Bildschirmdrehung (3-D)

In dem Beispiel unter Optimieren des Drehungsprozesses (und im DXGI-Swapketten-Drehungsbeispiel) haben wir für alle möglichen Bildschirmausrichtungen eine spezielle Transformationsmatrix definiert. Nun sehen wir uns die Matrizen zum Drehen von 3-D-Szenen an. Genau wie zuvor erstellen Sie für jede der vier möglichen Ausrichtungen eine Matrizengruppe. Zum Vermeiden von Rundungsfehlern und somit von kleineren visuellen Artefakten deklarieren Sie die Matrizen explizit in Ihrem Code.

Diese 3-D-Rundungsmatrizen werden wie folgt eingerichtet. Bei den Matrizen im folgenden Codebeispiel handelt es sich um Standarddrehungsmatrizen für Drehungen der Vertizes um 0, 90, 180 und 270 Grad, die im 3-D-Szenenraum der Kamera zum Definieren von Punkten dienen. Beim Berechnen der 2-D-Projektion für die Szene werden die Koordinatenwerte [X, Y, Z] aller Vertizes in der Szene mit dieser Drehungsmatrix multipliziert.


	// Set the proper orientation for the swap chain, and generate the
	// 3-D matrix transformation for rendering to the rotated swap chain.
	DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
	switch (m_orientation)
	{
		case DisplayOrientations::Landscape:
			rotation = DXGI_MODE_ROTATION_IDENTITY;
			m_orientationTransform3D = XMFLOAT4X4( // 0-degree Z-rotation
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::Portrait:
			rotation = DXGI_MODE_ROTATION_ROTATE270;
			m_orientationTransform3D = XMFLOAT4X4( // 90-degree Z-rotation
				0.0f, 1.0f, 0.0f, 0.0f,
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::LandscapeFlipped:
			rotation = DXGI_MODE_ROTATION_ROTATE180;
			m_orientationTransform3D = XMFLOAT4X4( // 180-degree Z-rotation
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, -1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		case DisplayOrientations::PortraitFlipped:
			rotation = DXGI_MODE_ROTATION_ROTATE90;
			m_orientationTransform3D = XMFLOAT4X4( // 270-degree Z-rotation
				0.0f, -1.0f, 0.0f, 0.0f,
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;

		default:
			throw ref new Platform::FailureException();
	}

Der Drehungstyp wird wie folgt über einen Aufruf von IDXGISwapChain1::SetRotation für die Swapkette festgelegt:

m_swapChain->SetRotation(rotation);

Implementieren Sie nun in der Rendermethode einen Code, der dem folgenden Code ähnelt:


struct ConstantBuffer // this struct provided for illustration
{
    // other constant buffer matrices and data defined here

    float4x4 projection; // current matrix for projection
} ;
ConstantBuffer  m_constantBufferData;          // constant buffer resource data

// ...

// rotate the projection matrix as it will be used to render to the rotated swap chain
m_constantBufferData.projection = mul(m_constantBufferData.projection, m_rotationTransform3D);

Wenn Sie nun die Rendermethode aufrufen, wird die aktuelle Drehungsmatrix (gemäß Angabe durch die Klassenvariable m_orientationTransform3D) mit der aktuellen Projektionsmatrix multipliziert, und die Ergebnisse dieses Vorgangs werden als neue Projektionsmatrix für den Renderer zugeordnet. Stellen Sie die Swapkette dar, um die Szene in der aktualisierten Bildschirmausrichtung anzuzeigen!

Verwandte Themen

DXGI-Swapketten-Drehungsbeispiel
Richtlinien für Drehung
Reagieren auf Benutzerinteraktionen (DirectX und C++)

 

 

Anzeigen:
© 2014 Microsoft