Freigeben über


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

Ihre Universelle Windows-Plattform-App (UWP) kann mehrere Bildschirmausrichtungen unterstützen, wenn Sie das DisplayInformation::OrientationChanged-Ereignis behandeln. Hier besprechen wir bewährte Methoden für die Behandlung der Bildschirmdrehung in Ihrer UWP DirectX-App, damit die Grafikhardware des Windows 10-Geräts effizient und effektiv verwendet wird.

Bevor Sie beginnen, denken Sie daran, dass Die Grafikhardware Pixeldaten unabhängig von der Ausrichtung des Geräts immer auf die gleiche Weise ausgibt. Windows 10-Geräte können ihre aktuelle Anzeigeausrichtung (mit einem bestimmten Sensor oder mit einem Softwareschalter) bestimmen und Benutzern das Ändern der Anzeigeeinstellungen ermöglichen. Aus diesem Grund übernimmt Windows 10 selbst die Drehung der Bilder, um sicherzustellen, dass sie auf der Grundlage der Ausrichtung des Geräts "aufrecht" sind. Standardmäßig empfängt Ihre App die Benachrichtigung, dass sich etwas in der Ausrichtung geändert hat, z. B. eine Fenstergröße. In diesem Fall dreht Windows 10 das Bild sofort für die endgültige Anzeige. Für drei der vier spezifischen Bildschirmausrichtungen (weiter unten erläutert) verwendet Windows 10 zusätzliche Grafikressourcen und Berechnungen, um das endgültige Bild anzuzeigen.

Für UWP-DirectX-Apps stellt das DisplayInformation-Objekt grundlegende Anzeigeausrichtungsdaten bereit, die Ihre App abfragen kann. Die Standardausrichtung ist querformatiert, wobei die Pixelbreite der Anzeige größer als die Höhe ist. Die alternative Ausrichtung ist hoch, wobei die Anzeige in beiden Richtungen um 90 Grad gedreht wird und die Breite kleiner als die Höhe wird.

Windows 10 definiert vier spezifische Anzeigeausrichtungsmodi:

  • Querformat – die Standardanzeigeausrichtung für Windows 10 und gilt als Basis- oder Identitätswinkel für Drehung (0 Grad).
  • Hochformat – die Anzeige wurde im Uhrzeigersinn um 90 Grad gedreht (oder gegen den Uhrzeigersinn 270 Grad).
  • Querformat, gekippt – die Anzeige wurde um 180 Grad gedreht (aufgedreht).
  • Hochformat, gekippt – die Anzeige wurde im Uhrzeigersinn um 270 Grad gedreht (oder gegen den Uhrzeigersinn 90 Grad).

Wenn die Anzeige von einer Ausrichtung in eine andere gedreht wird, führt Windows 10 intern einen Drehvorgang aus, um das gezeichnete Bild an der neuen Ausrichtung auszurichten, und der Benutzer sieht ein aufrechtes Bild auf dem Bildschirm.

Außerdem zeigt Windows 10 automatische Übergangsanimationen an, um eine reibungslose Benutzererfahrung zu schaffen, wenn sie von einer Ausrichtung zur anderen wechselt. Während sich die Anzeigeausrichtung ändert, sieht der Benutzer diese Schichten als feste Zoom- und Drehanimation des angezeigten Bildschirmbilds. Die Zeit wird der App von Windows 10 für das Layout in der neuen Ausrichtung zugewiesen.

Insgesamt ist dies der allgemeine Prozess für die Behandlung von Änderungen in der Bildschirmausrichtung:

  1. Verwenden Sie eine Kombination aus fenstergebundenen Werten und den Anzeigeausrichtungsdaten, um die Swapchain an der systemeigenen Anzeigeausrichtung des Geräts auszurichten.
  2. Benachrichtigen Sie Windows 10 über die Ausrichtung der Swapchain mithilfe von IDXGISwapChain1::SetRotation.
  3. Ändern Sie den Renderingcode, um Bilder zu generieren, die an der Benutzerausrichtung des Geräts ausgerichtet sind.

Ändern der Größe der Swapchain und Vordrehen des Inhalts

Führen Sie die folgenden Schritte aus, um eine grundlegende Anzeigegröße zu ändern und ihre Inhalte in Ihrer UWP DirectX-App vorab zu drehen:

  1. Behandeln Sie das DisplayInformation::OrientationChanged-Ereignis.
  2. Ändern Sie die Größe der Swapchain in die neuen Dimensionen des Fensters.
  3. Rufen Sie IDXGISwapChain1::SetRotation auf, um die Ausrichtung der Swapchain festzulegen.
  4. Erstellen Sie abhängige Fenstergrößenressourcen neu, z. B. Die Renderziele und andere Pixeldatenpuffer.

Sehen wir uns diese Schritte nun etwas ausführlicher an.

Der erste Schritt besteht darin, einen Handler für das DisplayInformation::OrientationChanged-Ereignis zu registrieren. Dieses Ereignis wird in Ihrer App jedes Mal ausgelöst, wenn sich die Bildschirmausrichtung ändert, z. B. wenn die Anzeige gedreht wird.

Zum Behandeln des DisplayInformation::OrientationChanged-Ereignisses verbinden Sie den Handler für DisplayInformation::OrientationChanged in der erforderlichen SetWindow-Methode, die eine der Methoden der IFrameworkView-Schnittstelle ist, die der Ansichtsanbieter implementieren muss.

In diesem Codebeispiel ist der Ereignishandler für DisplayInformation::OrientationChanged eine Methode namens "OnOrientationChanged". Wenn DisplayInformation::OrientationChanged ausgelöst wird, ruft sie wiederum eine Methode namens "SetCurrentOrientation" auf, die dann CreateWindowSizeDependentResources aufruft.

void App::SetWindow(CoreWindow^ window)
{
  // ... Other UI event handlers assigned here ...
  
    currentDisplayInformation->OrientationChanged +=
        ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnOrientationChanged);

  // ...
}
}
void App::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
{
    m_deviceResources->SetCurrentOrientation(sender->CurrentOrientation);
    m_main->CreateWindowSizeDependentResources();
}

// This method is called in the event handler for the OrientationChanged event.
void DX::DeviceResources::SetCurrentOrientation(DisplayOrientations currentOrientation)
{
    if (m_currentOrientation != currentOrientation)
    {
        m_currentOrientation = currentOrientation;
        CreateWindowSizeDependentResources();
    }
}

Als Nächstes ändern Sie die Größe der Swapchain für die neue Bildschirmausrichtung und bereiten sie vor, um den Inhalt der Grafikpipeline zu drehen, wenn das Rendering ausgeführt wird. In diesem Beispiel ist DirectXBase::CreateWindowSizeDependentResources eine Methode, die den Aufruf von IDXGISwapChain::ResizeBuffers behandelt, eine 3D- und eine 2D-Drehungsmatrix festlegt, SetRotation aufruft und Ihre Ressourcen neu erstellt.

void DX::DeviceResources::CreateWindowSizeDependentResources() 
{
    // Clear the previous window size specific context.
    ID3D11RenderTargetView* nullViews[] = {nullptr};
    m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
    m_d3dRenderTargetView = nullptr;
    m_d2dContext->SetTarget(nullptr);
    m_d2dTargetBitmap = nullptr;
    m_d3dDepthStencilView = nullptr;
    m_d3dContext->Flush();

    // Calculate the necessary render target size in pixels.
    m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi);
    m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi);
    
    // Prevent zero size DirectX content from being created.
    m_outputSize.Width = max(m_outputSize.Width, 1);
    m_outputSize.Height = max(m_outputSize.Height, 1);

    // The width and height of the swap chain must be based on the window's
    // natively-oriented width and height. If the window is not in the native
    // orientation, the dimensions must be reversed.
    DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();

    bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
    m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
    m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;

    if (m_swapChain != nullptr)
    {
        // If the swap chain already exists, resize it.
        HRESULT hr = m_swapChain->ResizeBuffers(
            2, // Double-buffered swap chain.
            lround(m_d3dRenderTargetSize.Width),
            lround(m_d3dRenderTargetSize.Height),
            DXGI_FORMAT_B8G8R8A8_UNORM,
            0
            );

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

            // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method 
            // and correctly set up the new device.
            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 = lround(m_d3dRenderTargetSize.Width); // Match the size of the window.
        swapChainDesc.Height = lround(m_d3dRenderTargetSize.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.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All UWP apps must use this SwapEffect.
        swapChainDesc.Flags = 0;    
        swapChainDesc.Scaling = DXGI_SCALING_NONE;
        swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

        // This sequence obtains the DXGI factory that was used to create the Direct3D device above.
        ComPtr<IDXGIDevice3> 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))
            );

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

        // Ensure that DXGI does not 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 2D and
    // 3D matrix transformations for rendering to the rotated swap chain.
    // Note the rotation angle for the 2D and 3D transforms are different.
    // This is due to the difference in coordinate spaces.  Additionally,
    // the 3D matrix is specified explicitly to avoid rounding errors.

    switch (displayRotation)
    {
    case DXGI_MODE_ROTATION_IDENTITY:
        m_orientationTransform2D = Matrix3x2F::Identity();
        m_orientationTransform3D = ScreenRotation::Rotation0;
        break;

    case DXGI_MODE_ROTATION_ROTATE90:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(90.0f) *
            Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
        m_orientationTransform3D = ScreenRotation::Rotation270;
        break;

    case DXGI_MODE_ROTATION_ROTATE180:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(180.0f) *
            Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
        m_orientationTransform3D = ScreenRotation::Rotation180;
        break;

    case DXGI_MODE_ROTATION_ROTATE270:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(270.0f) *
            Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
        m_orientationTransform3D = ScreenRotation::Rotation90;
        break;

    default:
        throw ref new FailureException();
    }


    //SDM: only instance of SetRotation
    DX::ThrowIfFailed(
        m_swapChain->SetRotation(displayRotation)
        );

    // Create a 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 3D rendering if needed.
    CD3D11_TEXTURE2D_DESC depthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT, 
        lround(m_d3dRenderTargetSize.Width),
        lround(m_d3dRenderTargetSize.Height),
        1, // This depth stencil view has only one texture.
        1, // Use a single mipmap level.
        D3D11_BIND_DEPTH_STENCIL
        );

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

    CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
    DX::ThrowIfFailed(
        m_d3dDevice->CreateDepthStencilView(
            depthStencil.Get(),
            &depthStencilViewDesc,
            &m_d3dDepthStencilView
            )
        );
    
    // Set the 3D rendering viewport to target the entire window.
    m_screenViewport = CD3D11_VIEWPORT(
        0.0f,
        0.0f,
        m_d3dRenderTargetSize.Width,
        m_d3dRenderTargetSize.Height
        );

    m_d3dContext->RSSetViewports(1, &m_screenViewport);

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

    ComPtr<IDXGISurface2> 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 UWP apps.
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

}

Nachdem Sie die aktuellen Werte für Höhe und Breite des Fensters für das nächste Mal gespeichert haben, wenn diese Methode aufgerufen wird, konvertieren Sie die Geräteunabhängigen Pixelwerte (DIP) für die Anzeigegrenzen in Pixel. Im Beispiel rufen Sie ConvertDipsToPixels auf. Dabei handelt es sich um eine einfache Funktion, die diesen Code ausführt:

floor((dips * dpi / 96.0f) + 0.5f);

Sie fügen den Wert 0,5f hinzu, um sicherzustellen, dass der nächstliegende ganzzahlige Wert gerundet wird.

Nebenbei werden CoreWindow-Koordinaten immer in DIPs definiert. Für Windows 10 und frühere Versionen von Windows wird ein DIP als 1/96. Zoll definiert und an die Definition des Betriebssystems angepasst. Wenn die Anzeigeausrichtung in den Hochformatmodus gedreht wird, kippt die App die Breite und Höhe des CoreWindow, und die Renderzielgröße (Begrenzungen) muss sich entsprechend ändern. Da sich die Koordinaten von Direct3D immer in physischen Pixeln befinden, müssen Sie die DIP-Werte von CoreWindow in ganzzahlige Pixelwerte konvertieren, bevor Sie diese Werte an Direct3D übergeben, um die Swapchain einzurichten.

Prozessweise erledigen Sie etwas mehr Arbeit als wenn Sie einfach die Größe der Swapchain ändern würden: Sie drehen die Direct2D- und Direct3D-Komponenten Ihres Bilds tatsächlich, bevor Sie sie für die Präsentation zusammengesetzt haben, und Sie teilen der Swapchain mit, dass Sie die Ergebnisse in einer neuen Ausrichtung gerendert haben. Hier ist ein wenig ausführlicher zu diesem Prozess, wie im Codebeispiel für DX::D eviceResources::CreateWindowSizeDependentResources gezeigt:

  • Bestimmen Sie die neue Ausrichtung der Anzeige. Wenn die Anzeige von Querformat in Hochformat gekippt wurde oder umgekehrt, tauschen Sie die Werte für Höhe und Breite aus , die von DIP-Werten in Pixel geändert wurden, natürlich für die Anzeigegrenzen.

  • Überprüfen Sie dann, ob die Swapchain erstellt wurde. Wenn sie nicht erstellt wurde, erstellen Sie sie durch Aufrufen von IDXGIFactory2::CreateSwapChainForCoreWindow. Ändern Sie andernfalls die Größe der Puffer der vorhandenen Swapchain in die neuen Anzeigedimensionen, indem Sie IDXGISwapchain:ResizeBuffers aufrufen. Obwohl Sie die Größe der Swapchain für das Drehungsereignis nicht ändern müssen, geben Sie den Inhalt aus, der bereits von der Renderingpipeline gedreht wurde, denn es gibt andere Größenänderungsereignisse, z. B. Andock- und Füllereignisse, für die eine Größenänderung erforderlich ist.

  • Legen Sie anschließend die entsprechende 2D- oder 3D-Matrixtransformation fest, die auf die Pixel oder die Scheitelpunkte (bzw. die Scheitelpunkte) in der Grafikpipeline angewendet werden soll, wenn sie in der Swapchain gerendert werden. Wir haben vier mögliche Drehungsmatrizen:

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

    Die richtige Matrix wird basierend auf den von Windows 10 bereitgestellten Daten (z. B. die Ergebnisse von DisplayInformation::OrientationChanged) zum Bestimmen der Anzeigeausrichtung ausgewählt, und sie wird mit den Koordinaten der einzelnen Pixel (Direct2D) oder Scheitelpunkt (Direct3D) in der Szene multipliziert und effektiv gedreht, um sie an der Ausrichtung des Bildschirms auszurichten. (Beachten Sie, dass in Direct2D der Bildschirmursprung als obere linke Ecke definiert ist, während in Direct3D der Ursprung als logische Mitte des Fensters definiert ist.)

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

 

Hier ist nun das wichtige Bit: Rufen Sie IDXGISwapChain1::SetRotation auf, und stellen Sie sie wie folgt mit Ihrer aktualisierten Drehungsmatrix bereit:

m_swapChain->SetRotation(rotation);

Sie speichern auch die ausgewählte Drehungsmatrix, in der die Rendermethode beim Berechnen der neuen Projektion abgerufen werden kann. Sie verwenden diese Matrix, wenn Sie die endgültige 3D-Projektion rendern oder ihr endgültiges 2D-Layout zusammengesetzt haben. (Sie wird nicht automatisch angewendet.)

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

Wenn Sie 2D-Bilder zum Drehen oder Anordnen haben, erstellen Sie ein 2D-Renderziel als beschreibbare Bitmap für die geänderte Swapchain mithilfe von ID2D1DeviceContext::CreateBitmapFromDxgiSurface und zusammengesetzt Das neue Layout für die aktualisierte Ausrichtung. Legen Sie alle Eigenschaften fest, die Sie für das Renderziel benötigen, z. B. den Antialiasingmodus (wie im Codebeispiel dargestellt).

Präsentieren Sie nun die Swapchain.

Verringern der Drehungsverzögerung mithilfe von CoreWindowResizeManager

Standardmäßig bietet Windows 10 ein kurzes, aber spürbares Zeitfenster für jede App, unabhängig vom App-Modell oder der Sprache, um die Drehung des Bilds abzuschließen. Wenn Ihre App jedoch die Drehungsberechnung mit einer der hier beschriebenen Techniken durchführt, wird sie gut ausgeführt, bevor dieses Zeitfenster geschlossen wurde. Sie möchten diese Zeit zurückholen und die Drehungsanimation abschließen, richtig? Hier kommt CoreWindowResizeManager.

Hier erfahren Sie, wie Sie CoreWindowResizeManager verwenden: Wenn ein DisplayInformation::OrientationChanged-Ereignis ausgelöst wird, rufen Sie CoreWindowResizeManager::GetForCurrentView innerhalb des Handlers für das Ereignis auf, um eine Instanz von CoreWindowResizeManager abzurufen, und rufen Sie nach Abschluss des Layouts für die neue Ausrichtung das NotifyLayoutCompleted auf. um Windows mitzuteilen, dass die Drehungsanimation abgeschlossen und der App-Bildschirm angezeigt werden kann.

So sieht der Code im Ereignishandler für DisplayInformation::OrientationChanged möglicherweise wie folgt aus:

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

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

resizeManager->NotifyLayoutCompleted();

Wenn ein Benutzer die Ausrichtung des Bildschirms dreht, zeigt Windows 10 eine Animation unabhängig von Ihrer App als Feedback an den Benutzer an. Es gibt drei Teile dieser Animation, die in der folgenden Reihenfolge ausgeführt werden:

  • Windows 10 verkleinert das ursprüngliche Image.
  • Windows 10 enthält das Image für die Zeit, die zum Neuerstellen des neuen Layouts benötigt wird. Dies ist das Zeitfenster, das Sie reduzieren möchten, da Ihre App wahrscheinlich nicht alle benötigt.
  • Wenn das Layoutfenster abläuft oder eine Benachrichtigung über den Abschluss des Layouts empfangen wird, dreht Windows das Bild und zoomt dann querblenden auf eine neue Ausrichtung.

Wie im dritten Aufzählungszeichen vorgeschlagen, beendet Windows 10 beim Aufrufen von NotifyLayoutCompleted das Timeoutfenster, schließt die Drehungsanimation ab und gibt die Steuerung an Ihre App zurück, die nun in der neuen Anzeigeausrichtung gezeichnet wird. Der Gesamteffekt ist, dass Ihre App jetzt etwas flüssiger und reaktionsfähiger ist und etwas effizienter funktioniert!

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

Im Beispielcode zum Ändern der Größe der Swapchain und vor dem Drehen des Inhalts (und im DXGI-Swapchaindrehungsbeispiel) haben Sie möglicherweise bemerkt, dass wir separate Drehungsmatrizen für die Direct2D-Ausgabe und die Direct3D-Ausgabe hatten. Sehen wir uns zuerst die 2D-Matrizen an.

Es gibt zwei Gründe, aus denen wir die gleichen Drehungsmatrizen nicht auf Direct2D- und Direct3D-Inhalte anwenden können:

  • Eine, sie verwenden verschiedene kartesische Koordinatenmodelle. Direct2D verwendet die rechtshändige Regel, wobei sich die y-Koordinate vom Ursprung nach oben bewegt. Direct3D verwendet jedoch die linkshändige Regel, wobei die y-Koordinate vom Ursprung nach rechts in positivem Wert steigt. Das Ergebnis ist der Ursprung für die Bildschirmkoordinaten in der oberen linken Ecke von Direct2D, während sich der Ursprung für den Bildschirm (die Projektionsebene) unten links für Direct3D befindet. (Weitere Informationen finden Sie unter 3D-Koordinatensystemen .)

    direct3d-Koordinatensystem.Direct2d-Koordinatensystem.

  • Zwei, die 3D-Drehungsmatrizen müssen explizit angegeben werden, um Rundungsfehler zu vermeiden.

Die Swapchain geht davon aus, dass sich der Ursprung unten links befindet, daher müssen Sie eine Drehung ausführen, um das rechtshändige Direct2D-Koordinatensystem mit der von der Swapchain verwendeten linkshändigen Koordinatensystem auszurichten. Insbesondere positionieren Sie das Bild unter der neuen linkshändigen Ausrichtung, indem Sie die Drehungsmatrix mit einer Übersetzungsmatrix für den Ursprung des gedrehten Koordinatensystems multiplizieren und das Bild aus dem Koordinatenbereich von CoreWindow in den Koordinatenbereich der Swapchain transformieren. Ihre App muss diese Transformation auch konsistent anwenden, wenn das Direct2D-Renderziel mit der Swapchain verbunden ist. Wenn Ihre App jedoch auf Zwischenflächen zeichnet, die nicht direkt mit der Swapchain verknüpft sind, wenden Sie diese Transformation des Koordinatenraums nicht an.

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

   
// Set the proper orientation for the swap chain, and generate 2D and
// 3D matrix transformations for rendering to the rotated swap chain.
// Note the rotation angle for the 2D and 3D transforms are different.
// This is due to the difference in coordinate spaces.  Additionally,
// the 3D matrix is specified explicitly to avoid rounding errors.

switch (displayRotation)
{
case DXGI_MODE_ROTATION_IDENTITY:
    m_orientationTransform2D = Matrix3x2F::Identity();
    m_orientationTransform3D = ScreenRotation::Rotation0;
    break;

case DXGI_MODE_ROTATION_ROTATE90:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(90.0f) *
        Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
    m_orientationTransform3D = ScreenRotation::Rotation270;
    break;

case DXGI_MODE_ROTATION_ROTATE180:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(180.0f) *
        Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
    m_orientationTransform3D = ScreenRotation::Rotation180;
    break;

case DXGI_MODE_ROTATION_ROTATE270:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(270.0f) *
        Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
    m_orientationTransform3D = ScreenRotation::Rotation90;
    break;

default:
    throw ref new FailureException();
}
    

Nachdem Sie die richtige Drehungsmatrix und den richtigen Ursprung für das 2D-Bild festgelegt haben, legen Sie sie mit einem Aufruf von ID2D1DeviceContext::SetTransform zwischen Ihren Aufrufen an ID2D1DeviceContext::BeginDraw und ID2D1DeviceContext::EndDraw fest.

Die Warnung direct2D verfügt nicht über einen Transformationsstapel. Wenn Ihre App auch die ID2D1DeviceContext::SetTransform als Teil des Zeichnungscodes verwendet, muss diese Matrix auf alle anderen Transformationen, die Sie angewendet haben, nach multipliziert werden.

 

    ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
    Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();

    context->SaveDrawingState(m_stateBlock.Get());
    context->BeginDraw();

    // Position on the bottom right corner.
    D2D1::Matrix3x2F screenTranslation = D2D1::Matrix3x2F::Translation(
        logicalSize.Width - m_textMetrics.layoutWidth,
        logicalSize.Height - m_textMetrics.height
        );

    context->SetTransform(screenTranslation * m_deviceResources->GetOrientationTransform2D());

    DX::ThrowIfFailed(
        m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)
        );

    context->DrawTextLayout(
        D2D1::Point2F(0.f, 0.f),
        m_textLayout.Get(),
        m_whiteBrush.Get()
        );

    // Ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = context->EndDraw();

Wenn Sie die Swapchain das nächste Mal präsentieren, wird das 2D-Bild gedreht, um der neuen Anzeigeausrichtung zu entsprechen.

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

Im Beispielcode beim Ändern der Größe der Swapchain und vor dem Drehen des Inhalts (und im DXGI-Swapchaindrehungsbeispiel) haben wir für jede mögliche Bildschirmausrichtung eine bestimmte Transformationsmatrix definiert. Sehen wir uns nun die Matrizen zum Drehen von 3D-Szenen an. Wie zuvor erstellen Sie für jede der vier möglichen Ausrichtungen eine Reihe von Matrizen. Um Rundungsfehler und damit kleinere visuelle Artefakte zu verhindern, deklarieren Sie die Matrizen explizit in Ihrem Code.

Sie richten diese 3D-Drehungsmatrizen wie folgt ein. Die im folgenden Codebeispiel gezeigten Matrizen sind Standarddrehungsmatrizen für 0, 90, 180 und 270 Grad Drehungen der Scheitelpunkte, die Punkte im 3D-Szenenbereich der Kamera definieren. Der Koordinatenwert [x, y, z] in der Szene wird durch diese Drehungsmatrix multipliziert, wenn die 2D-Projektion der Szene berechnet wird.

   
// 0-degree Z-rotation
static const XMFLOAT4X4 Rotation0( 
    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
    );

// 90-degree Z-rotation
static const XMFLOAT4X4 Rotation90(
    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
    );

// 180-degree Z-rotation
static const XMFLOAT4X4 Rotation180(
    -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
    );

// 270-degree Z-rotation
static const XMFLOAT4X4 Rotation270( 
    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
    );            
    }

Sie legen den Drehungstyp für die Swapchain mit einem Aufruf von IDXGISwapChain1::SetRotation wie folgt fest:

m_swapChain->SetRotation(rotation);

Implementieren Sie nun in Ihrer Rendermethode code ähnlich wie folgt:

struct ConstantBuffer // This struct is provided for illustration.
{
    // Other constant buffer matrices and data are 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, multipliziert sie die aktuelle Drehungsmatrix (wie durch die Klassenvariable m_orientationTransform3D angegeben) mit der aktuellen Projektionsmatrix und weist die Ergebnisse dieses Vorgangs als neue Projektionsmatrix für den Renderer zu. Präsentieren Sie die Swapchain, um die Szene in der aktualisierten Anzeigeausrichtung anzuzeigen.