Prise en charge de l’orientation de l’écran (DirectX et C++)

Applies to Windows only

De nombreux appareils Windows 8 prennent en charge plusieurs orientations d’écran. Vos applications du Windows Store en C++ avec DirectX peuvent également prendre en charge cette fonctionnalité lorsque vous gérez l’événement CoreWindow::SizeChanged. Nous allons examiner dans cette rubrique les meilleures pratiques pour la gestion de la rotation de l’écran dans votre application du Windows Store avec DirectX de manière à utiliser efficacement le matériel graphique de l’appareil Windows 8.

Remarque  Ces pratiques sont illustrées de manière détaillée dans le modèle de projet Direct2D App dans Microsoft Visual Studio et dans l’exemple de rotation de chaîne de permutation DXGI.

Avant de commencer, souvenez-vous que le matériel graphique génère toujours des données de pixels de la même manière, quelle que soit l’orientation de l’appareil. Les appareils Windows 8 peuvent déterminer leur orientation d’affichage actuelle (à l’aide d’un capteur ou d’une bascule logicielle) et autoriser les utilisateurs à modifier les paramètres d’affichage. Pour cette raison, Windows 8 gère lui-même la rotation des images afin de s’assurer qu’elles sont « dans le bon sens » en fonction de l’orientation de l’appareil. Par défaut, votre application reçoit une notification signalant que quelque chose a changé d’orientation, par exemple une taille de fenêtre. Lorsque cela se produit, Windows 8 fait pivoter immédiatement l’image pour l’affichage final. Pour trois des quatre orientations de l’écran spécifiques (discutés plus bas), Windows 8 utilise de la puissance de calcul et des ressources graphiques supplémentaires pour afficher l’image finale.

Pour une application du Windows Store utilisant DirectX, l’objet DisplayProperties fournit des données d’orientation de l’affichage de base qui peuvent être interrogées par votre application. L’orientation par défaut est paysage, où la largeur en pixels de l’affichage est supérieure à sa hauteur ; l’autre orientation est portrait, où l’affichage subit une rotation de 90 degrés dans l’un ou l’autre sens et la largeur devient inférieure à la hauteur.

Windows 8 définit quatre modes d’orientation d’affichage spécifique :

  • Paysage—il s’agit de l’orientation d’affichage par défaut de Windows 8 ; elle est considérée comme l’angle de base ou d’identité pour la rotation (0 degré).
  • Portrait—l’affichage a subi une rotation de 270 degrés dans le sens des aiguilles d’une montre (ou de 90 degrés dans le sens inverse).
  • Paysage (renversé)—l’affichage a subi une rotation de 180 degrés (retournement).
  • Portrait (renversé)—l’affichage a subi une rotation de 90 degrés dans le sens des aiguilles d’une montre (ou de 270 degrés dans le sens inverse).

Lorsque l’affichage bascule d’une orientation à une autre, Windows 8 effectue en interne une opération de rotation pour aligner l’image dessinée avec la nouvelle orientation et l’utilisateur voit une image à l’endroit à l’écran.

De plus, Windows 8 affiche des animations de transition automatique afin de créer une expérience utilisateur fluide lors du basculement d’une orientation à une autre. Le changement d’orientation de l’affichage est présenté à l’utilisateur sous la forme d’une animation de rotation et de zoom fixe de l’image d’écran affichée. Un délai est accordé à l’application par Windows 8 pour la disposition dans la nouvelle orientation. Toutefois, pour les applications du Windows Store avec DirectX, le délai requis pour la nouvelle disposition est en général inférieur au délai alloué, car d’autres infrastructures peuvent avoir besoin d’un plus grand pourcentage du temps alloué. Puisqu’il est probable que votre application n’utilise jamais tout le délai qui lui est allouée, vous pouvez signaler directement à Windows 8 qu’elle a terminé tout le travail nécessaire à la gestion de la nouvelle orientation de l’affichage et qu’elle a appelé IDXGISwapChain::Presentpour afficher la nouvelle image. Vous pouvez signaler que votre application a achevé la disposition à l’avance à l’aide de CoreWindowResizeManager::NotifyLayoutCompleted.

Globalement, voici le processus général de gestion des modifications de l’orientation de l’écran :

  1. Utilisez une combinaison des valeurs de limites de fenêtre et des données d’orientation de l’écran pour maintenir la chaîne d’échange alignée avec l’orientation d’affichage native de l’appareil.
  2. Informez Windows 8 de l’orientation de la chaîne d’échange à l’aide de IDXGISwapChain1::SetRotation.
  3. Modifiez le code de rendu pour générer des images alignées avec l’orientation de l’appareil choisie par l’utilisateur.
  4. Signalez à Windows 8 que votre application est prête à continuer dans la nouvelle orientation à l’aide de CoreWindowResizeManager::NotifyLayoutCompleted.

Pour voir le code complet de ce processus tel qu’appliqué au rendu Direct2D et Direct3D, téléchargez l’exemple de rotation de chaîne d’échange DXGI.

Deux méthodes sont à votre disposition pour gérer le changement d’orientation dans votre application du Windows Store avec DirectX :

Examinons maintenant ces deux méthodes.

Méthode 1 : redimensionnement de la chaîne d’échange

L’approche la plus simple pour la gestion de la rotation consiste à laisser Windows 8 s’en charger pour vous. Bien entendu, votre affichage n’ayant pas des proportions de 1:1, les résultats apparaîtront étirés ou compressés. La solution ? Redimensionner la chaîne d’échange avec les valeurs de hauteur et de largeur pour les limites de fenêtre qui ont permuté et présenter le contenu.

Pour effectuer un redimensionnement de base de l’affichage dans votre application du Windows Store avec DirectX, procédez comme suit :

  1. Gérez l’événement CoreWindow::SizeChanged.
  2. Redimensionnez la chaîne d’échange aux nouvelles dimensions de la fenêtre.
  3. Recréez toutes les ressources qui dépendent de la taille de la fenêtre, telles que vos cibles de rendu et autres mémoires tampons de données de pixels.

Examinons maintenant ces étapes un peu plus en détail.

La première étape consiste à enregistrer un gestionnaire pour l’événement CoreWindow::SizeChanged. Cet événement est déclenché sur l’objet CoreWindow de votre application chaque fois que la taille de l’écran change, par exemple lorsque l’écran subit une rotation. Si vous avez indiqué que votre application prenait en charge la rotation de l’écran dans le fichier package.appxmanifest de l’application, vous devez gérer l’événement SizeChanged. (Sinon, l’affichage de l’application est redimensionné automatiquement lorsque l’orientation change et le contenu de la chaîne d’échange est étiré ou compressé pour s’adapter à la nouvelle zone.)

Pour gérer l’événement SizeChanged, vous devez connecter votre gestionnaire pour CoreWindow::SizeChanged dans la méthode SetWindow requise, qui est l’une des méthodes de l’interface IFrameworkView que votre fournisseur d’affichage doit mettre en œuvre.

Dans cet exemple de code, le gestionnaire d’événements pour CoreWindow::SizeChanged est une méthode nommée OnWindowSizeChanged et elle est également définie sur l’objet fournisseur d’affichage. Lorsque l’événement CoreWindow::SizeChanged est déclenché, il appelle à son tour une méthode nommée UpdateForWindowSizeChange définie sur l’objet convertisseur.


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
}

Maintenant, redimensionnez la chaîne d’échange à partir de votre rappel. (Dans l’exemple de code, cette opération est effectuée dans DirectXBase::UpdateForWindowSizeChange.)



// 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...
    // ...

}


Dans cette méthode, vous reconfigurez ou redimensionnez également toute autre ressource DirectX qui dépend de la taille de la fenêtre, comme vos gabarits de profondeur ou autres mémoires tampons de données de pixels, ainsi que les fenêtres d’affichage Direct3D si vous avez une scène 3D à afficher.

Maintenant, la chaîne d’échange étant redimensionnée et présentée correctement, le contenu de l’écran de l’application est affiché correctement dans la nouvelle orientation après que Windows 8 a effectué l’animation de rotation système. Toutefois, Windows 8 effectue quand même sa propre copie des données de pixels de l’écran complet pour l’animation de rotation, en prenant un instantané de la mémoire tampon d’écran et en faisant pivoter l’image proprement dite avant d’afficher la chaîne d’échange présentée de votre application. Vous pouvez éliminer cette redondance et économiser au GPU une partie du travail (en particulier si la résolution native est élevée) en signalant à Windows 8 que vous avez déjà fait pivoter le pipeline de rendu. Nous examinerons cette optimisation potentielle dans la section suivante. Nous examinerons également la gestion des composants Direct2D et Direct3D de la chaîne d’échange, qui nécessitent l’application de techniques différentes lors de la composition de l’image pivotée.

Pour obtenir un code complet qui illustre ce processus, examinez le modèle Application Direct2D dans Visual Studio ou téléchargez l’exemple de rotation de chaîne de permutation DXGI.

Méthode 2 : prérotation du contenu de la chaîne d’échange

Direct3D 11 et DXGI fournissent une nouvelle API, IDXGISwapChain1::SetRotation, que vous pouvez utiliser dans votre application du Windows Store avec DirectX pour informer Windows 8 de l’orientation de l’image de la fenêtre dans la chaîne d’échange. Si l’orientation de l’appareil et celle de la chaîne d’échange correspondent, Windows 8 peut utiliser les images de la fenêtre directement sans effectuer aucune rotation, et vous évitez une opération de copie d’écran complet supplémentaire au niveau matériel, ce qui permet à l’application de récupérer les économies en matière de performances et de durée de vie de batterie. Toutefois, votre application doit effectuer un rendu des images dans la chaîne d’échange conformément à l’orientation pour que vous puissiez bénéficier de cette efficacité supplémentaire.

Comme mentionné précédemment, au minimum votre application du Windows Store avec DirectX peut simplement gérer un événement de rotation en redimensionnant les mémoires tampons de la chaîne d’échange aux nouvelles hauteur et largeur de l’écran puis en la présentant.

Ce processus remplit la fonction souhaitée mais il présente quelques inconvénients :

  • il effectue une opération de copie redondante de l’écran complet ;
  • il ne fait pas vraiment la distinction entre le type d’événement de changement de taille, qui aurait pu être déclenché par le basculement à l’état Fill ou Snapped d’une fenêtre d’application ou par un changement de la résolution de l’écran.

Mettons à jour ce processus afin d’éliminer ces faiblesses à l’aide des étapes suivantes :

  1. Gérez l’événement CoreWindow::SizeChanged comme précédemment.
  2. Utilisez DisplayProperties::CurrentOrientationpour confirmer que l’événement de changement de taille de fenêtre concerne spécifiquement une rotation.
  3. Ne redimensionnez pas la chaîne d’échange ! Au lieu de cela, utilisez les données d’orientation actuelles pour mettre à jour les transformations Direct2D et Direct3D détenues par l’application et effectuer une rotation du pipeline de rendu.
  4. Appelez IDXGISwapChain1::SetRotation pour définir l’orientation de la chaîne d’échange.
  5. Recréez les éventuelles ressources qui dépendent de la taille de la fenêtre, comme précédemment.

Voici comment redimensionner la chaîne d’échange pour la nouvelle orientation de l’écran et comment la préparer pour la rotation du contenu du pipeline graphique lorsque le rendu est effectué. Dans cet exemple, DirectXBase::CreateWindowSizeDependentResources est une méthode que vous mettez en œuvre sur votre objet convertisseur.

Prenez le temps de bien examiner ce code.



// 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);

}


Après avoir enregistré les valeurs actuelles de hauteur et de largeur de la fenêtre pour le prochain appel de cette méthode, convertissez les valeurs DIP (Device Independent Pixel) pour les limites d’affichage en pixels. Dans l’exemple, vous appelez ConvertDipsToPixels, qui est une fonction simple qui exécute le code suivant :

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

Vous ajoutez 0,5f pour garantir un arrondi à la valeur entière la plus proche.

Notez en passant que les coordonnées CoreWindow sont toujours définies en DIP. Pour Windows 8 et les versions antérieures de Windows, un DIP est défini comme 1/96ème de pouce et aligné à la définition de vers le haut du système d’exploitation. Lorsque l’orientation de l’affichage bascule en mode portrait, l’application inverse la hauteur et la largeur de l’objet CoreWindow et la taille (limites) de la cible de rendu doit changer en conséquence. Les coordonnées Direct3D étant toujours en pixels physiques, vous devez effectuer une conversion des valeurs DIP de CoreWindow en valeurs de pixels entières avant de passer ces valeurs à Direct3D pour configurer la chaîne d’échange.

Du point de vue du traitement, vous effectuez un peu plus de travail que si vous redimensionniez simplement la chaîne d’échange : en fait, vous faites pivoter les composants Direct2D et Direct3D de votre image avant de les composer pour la présentation et vous signalez à la chaîne d’échange que vous avez rendu les résultats dans une nouvelle orientation. Voici quelques détails supplémentaires sur ce processus, comme illustré dans l’exemple de code pour DirectXBase::CreateWindowSizeDependentResources :

  • Déterminez la nouvelle orientation de l’affichage. Si l’affichage a basculé de paysage à portrait, ou inversement, permutez les valeurs de hauteur et de largeur—converties de valeurs DIP en pixels, bien entendu—pour les limites d’affichage.

  • Ensuite, vérifiez si la chaîne d’échange a été créée. Si ce n’est pas le cas, créez-la en appelant IDXGIFactory2::CreateSwapChainForCoreWindow. Autrement, redimensionnez les mémoires tampons de la chaîne d’échange existante aux nouvelles dimensions d’affichage en appelant IDXGISwapchain:ResizeBuffers. Bien qu’il ne soit pas obligatoire de redimensionner la chaîne d’échange pour l’événement de rotation—après tout, vous générez le contenu déjà pivoté par votre pipeline de rendu—d’autres événements de changement de taille, tels que des événements d’alignement et de remplissage, exigent un redimensionnement.

  • Après cela, définissez la transformation matricielle 2D ou 3D appropriée à appliquer aux pixels ou vertex (respectivement) dans le pipeline graphique lors de leur rendu dans la chaîne d’échange. Nous avons quatre matrices de rotation possibles :

    • Paysage (DXGI_MODE_ROTATION_IDENTITY)
    • Portrait (DXGI_MODE_ROTATION_ROTATE270)
    • Paysage (renversé) (DXGI_MODE_ROTATION_ROTATE180)
    • Portrait (renversé) (DXGI_MODE_ROTATION_ROTATE90)

    La matrice correcte est sélectionnée en fonction des données fournies par Windows 8 (telles que les résultats de DisplayProperties::OrientationChanged ou SimpleOrientationSensor::OrientationChanged) pour déterminer l’orientation de l’affichage et elle sera multipliée par les coordonnées de chaque pixel (Direct2D) ou vertex (Direct3D) dans la scène, ce qui entraîne leur rotation en vue de leur alignement avec l’orientation de l’écran. (Notez que dans Direct2D l’origine de l’écran est définie comme le coin supérieur gauche, alors que dans Direct3D elle est définie comme le centre logique de la fenêtre.)

Remarque  Pour plus d’informations sur les transformations 2D utilisées pour la rotation et sur la façon de les définir, voir Définition de matrices pour la rotation de l’écran (2D). Pour plus d’informations sur les transformations 3D utilisées pour la rotation, voir Définition de matrices pour la rotation de l’écran (3D).

Nous en arrivons maintenant au point important : appelez IDXGISwapChain1::SetRotation et fournissez-lui votre matrice de rotation mise à jour, comme ceci :

m_swapChain->SetRotation(rotation);

Vous stockez également la matrice de rotation sélectionnée là où votre méthode de rendu peut l’obtenir lorsqu’elle calcule la nouvelle projection. Vous utiliserez cette matrice lors du rendu de votre projection 3D finale ou de la composition de votre disposition 2D finale. (Elle n’est pas appliquée automatiquement pour vous.)

Après cela, créez une nouvelle cible de rendu pour l’affichage 3D pivoté, ainsi qu’une nouvelle mémoire tampon de gabarit de profondeur pour l’affichage. Définissez la fenêtre d’affichage de rendu 3D pour la scène pivotée en appelant ID3D11DeviceContext:RSSetViewports.

Pour finir, si vous avez des images 2D à faire pivoter ou à disposer, créez une cible de rendu 2D en tant que bitmap accessible en écriture pour la chaîne d’échange redimensionnée à l’aide de ID2D1DeviceContext::CreateBitmapFromDxgiSurface et composez votre nouvelle disposition pour l’orientation mise à jour. Définissez les propriétés dont vous avez besoin sur la cible de rendu, telles que le mode anticrénelage (comme illustré dans l’exemple de code).

Maintenant, présentez la chaîne d’échange.

Réduire le délai de rotation à l’aide de CoreWindowResizeManager

Par défaut, Windows 8 fournit à toute application, quel que soit le langage ou le modèle, un laps de temps bref mais remarquable pour effectuer la rotation de l’image. Il est toutefois probable que si votre application effectue le calcul de rotation à l’aide de l’une des techniques décrites ici, cette rotation sera terminée bien avant la fin de ce laps de temps. Vous souhaiteriez récupérer ce temps et terminer l’animation de rotation, n’est-ce pas ? C’est ici que CoreWindowResizeManager entre en jeu.

Voici comment utiliser CoreWindowResizeManager : quand un événement CoreWindow::SizeChanged est déclenché, appelez CoreWindowResizeManager::GetForCurrentView dans le gestionnaire d’événements pour obtenir une instance de CoreWindowResizeManager et, une fois la disposition de la nouvelle orientation terminée et présentée, appelez la méthode NotifyLayoutCompleted pour signaler à Windows qu’il peut terminer l’animation de rotation et afficher l’écran d’application.

Voici ce à quoi pourrait ressembler le code dans votre gestionnaire d’événements pour CoreWindow::SizeChanged :


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

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

resizeManager->NotifyLayoutCompleted();

Lorsqu’un utilisateur fait pivoter l’orientation de l’affichage, Windows 8 affiche une animation indépendante de votre application. Cette animation se décompose en trois parties, dans l’ordre suivant :

  • Windows 8 réduit l’image d’origine.
  • Windows 8 maintient l’image pendant toute la durée nécessaire à la recréation de la nouvelle disposition. Il s’agit du délai que vous souhaiterez probablement réduire, car votre application n’a pas besoin de tout ce temps.
  • Lorsque la fenêtre de disposition expire, ou quand une notification d’achèvement de disposition est reçue, Windows fait pivoter l’image puis effectue des zooms en fondu enchaîné vers la nouvelle orientation.

Comme il est suggéré dans la troisième puce, lorsqu’une application appelle NotifyLayoutCompleted, Windows 8 arrête la fenêtre de délai, effectue l’animation de rotation et redonne le contrôle à votre application qui dessine maintenant dans la nouvelle orientation d’affichage. L’effet global est que votre application semble maintenant un peu plus fluide et réceptive et qu’elle fonctionne un peu plus efficacement !

Annexe A : application de matrices pour la rotation de l’écran (2D)

Dans l’exemple fourni dans Optimisation du processus de rotation (et dans l’ exemple de rotation de chaîne d’échange DXGI), vous aurez peut-être remarqué que nous avions des matrices de rotation distinctes pour la sortie Direct2D et la sortie Direct3D. Examinons tout d’abord les matrices 2D.

Il existe deux raisons pour lesquelles nous ne pouvons pas appliquer les mêmes matrices de rotation au contenu Direct2D et Direct3D :

  • Premièrement, ils utilisent différents modèles de coordonnées cartésiennes. Direct2D utilise la règle de priorité à droite, selon laquelle la coordonnée « y » augmente en valeur positive lorsqu’on se déplace vers le haut par rapport à l’origine. En revanche, Direct3D utilise la règle de priorité à gauche, selon laquelle la coordonnée « y » augmente en valeur positive lorsqu’on se déplace vers la droite par rapport à l’origine. Le résultat est que l’origine des coordonnées d’écran se trouve en haut à gauche pour Direct2D, tandis que l’origine de l’écran (le plan de projection) se trouve en bas à gauche pour Direct3D. (Pour plus d’informations, voir Systèmes de coordonnées 3D.)

    Système de coordonnées Direct3D.Système de coordonnées Direct2D.
  • Deuxièmement, les matrices de rotation 3D doivent être spécifiées de manière explicite afin d’éviter toute erreur d’arrondi.

La chaîne d’échange suppose que l’origine se trouve en bas à gauche. Vous devez par conséquent effectuer une rotation pour aligner le système de coordonnées Direct2D à priorité à droite avec le système à priorité à gauche utilisé par la chaîne d’échange. Plus spécifiquement, vous devez repositionner l’image sous la nouvelle orientation à priorité à gauche en multipliant la matrice de rotation par une matrice de traduction pour l’origine du système de coordonnées qui a pivoté, puis transformer l’image de l’espace de coordonnées de CoreWindow vers l’espace de coordonnées de la chaîne d’échange. Votre application doit également appliquer de façon cohérente cette transformation lorsque la cible de rendu Direct2D est connectée à la chaîne d’échange. Toutefois, si votre application dessine sur des surfaces intermédiaires qui ne sont pas associées directement à la chaîne d’échange, n’appliquez pas cette transformation d’espace de coordonnées.

Votre code de sélection de la matrice correcte parmi les quatre rotations possibles pourrait ressembler à ceci (notez la transformation vers la nouvelle origine de système de coordonnées) :


	// 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;
    }

Une fois que vous avez la matrice de rotation et l’origine correctes pour l’image 2D, définissez-la avec un appel à ID2D1DeviceContext::SetTransform entre vos appels à ID2D1DeviceContext::BeginDraw et ID2D1DeviceContext::EndDraw.

Avertissement   Direct2D n’a pas de pile de transformations. Si votre application utilise également ID2D1DeviceContext::SetTransform dans le cadre de son code de dessin, cette matrice doit être post-multipliée en toute autre transformation que vous avez appliquée.


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();

Dans l’exemple précédent, vous centrez également l’image en multipliant la nouvelle matrice de rotation par une autre matrice de traduction qui spécifie le coin supérieur gauche de l’écran, qui doit toujours être au-dessus et à gauche du centre de l’écran après que la rotation a eu lieu. (Il s’agit d’une étape facultative.) Dessinez l’image pivotée dans le contexte de l’appareil au moyen d’un appel à ID2D1DeviceContext::DrawBitmap.

La prochaine fois que vous présenterez la chaîne d’échange, votre image 2D subira une rotation afin de correspondre à la nouvelle orientation de l’affichage.

Annexe B : application de matrices pour la rotation de l’écran (3D)

Dans l’exemple fourni dans Optimisation du processus de rotation (et dans l’exemple de rotation de chaîne d’échange DXGI), nous avons défini une matrice de transformation spécifique pour chaque orientation d’écran possible. Examinons maintenant les matrices pour la rotation de scènes en 3D. Comme précédemment, vous créez un ensemble de matrices pour chacune des quatre orientations possibles. Pour éviter toute erreur d’arrondi et par conséquent les artefacts visuels mineurs, déclarez les matrices de manière explicite dans votre code.

La configuration de ces matrices de rotation 3D s’effectue comme suit. Les matrices illustrées dans l’exemple de code suivant sont des matrices de rotation standard pour des rotations de 0, 90, 180 et 270 degrés des vertex qui définissent des points dans l’espace de scène 3D de la caméra. La valeur de coordonnées [x, y, z] de chaque vertex dans la scène est multipliée par cette matrice de rotation lorsque la projection 2D de la scène est calculée.


	// 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();
	}

Vous pouvez définir le type de rotation sur la chaîne d’échange à l’aide d’un appel à IDXGISwapChain1::SetRotation, comme ceci :

m_swapChain->SetRotation(rotation);

Maintenant, dans votre méthode de rendu, mettez en œuvre du code semblable au suivant :


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);

Désormais, lorsque vous appelez votre méthode de rendu, elle multiplie la matrice de rotation actuelle (telle que spécifiée par la variable de classe m_orientationTransform3D) par la matrice de projection actuelle et assigne le résultat de cette opération comme nouvelle matrice de projection pour votre convertisseur. Présentez la chaîne d’échange pour voir la scène dans l’orientation d’affichage mise à jour !

Rubriques associées

Exemple de rotation de chaîne d’échange DXGI
Recommandations en matière de rotation
Réponse à l’interaction utilisateur (DirectX et C++)

 

 

Afficher:
© 2014 Microsoft