Prise en charge de l’orientation de l’écran (DirectX et C++)
Votre application plateforme Windows universelle (UWP) peut prendre en charge plusieurs orientations d’écran lorsque vous gérez l’événement DisplayInformation ::OrientationChanged. Ici, nous allons aborder les meilleures pratiques de gestion de la rotation de l’écran dans votre application DirectX UWP, afin que le matériel graphique de l’appareil Windows 10 soit utilisé efficacement et efficacement.
Avant de commencer, n’oubliez pas que le matériel graphique génère toujours des données de pixels de la même façon, quelle que soit l’orientation de l’appareil. Les appareils Windows 10 peuvent déterminer leur orientation actuelle de l’affichage (avec un certain type de capteur ou avec une bascule logicielle) et permettre aux utilisateurs de modifier les paramètres d’affichage. En raison de cela, Windows 10 gère la rotation des images pour s’assurer qu’elles sont « verticales » en fonction de l’orientation de l’appareil. Par défaut, votre application reçoit la notification indiquant que quelque chose a changé en orientation, par exemple une taille de fenêtre. Lorsque cela se produit, Windows 10 fait pivoter immédiatement l’image pour l’affichage final. Pour trois des quatre orientations d’écran spécifiques (décrites plus loin), Windows 10 utilise des ressources graphiques et des calculs supplémentaires pour afficher l’image finale.
Pour les applications UWP DirectX, l’objet DisplayInformation fournit des données d’orientation d’affichage de base que votre application peut interroger. L’orientation par défaut est paysage, où la largeur du pixel de l’affichage est supérieure à la hauteur ; l’orientation alternative est portrait, où l’affichage est pivoté de 90 degrés dans les deux sens et la largeur devient inférieure à la hauteur.
Windows 10 définit quatre modes d’orientation d’affichage spécifiques :
- Paysage : l’orientation d’affichage par défaut pour Windows 10 et est considérée comme l’angle de base ou d’identité pour la rotation (0 degrés).
- Portrait : l’affichage a été pivoté dans le sens des aiguilles d’une montre de 90 degrés (ou à 270 degrés au niveau des aiguilles d’une montre).
- Paysage, retourné : l’affichage a été pivoté de 180 degrés (tourné à l’envers).
- Portrait, retourné : l’affichage a été pivoté à 270 degrés (ou à 90 degrés au niveau de l’horloge).
Lorsque l’affichage pivote d’une orientation à une autre, Windows 10 effectue en interne une opération de rotation pour aligner l’image dessinée avec la nouvelle orientation et l’utilisateur voit une image verticale sur l’écran.
En outre, Windows 10 affiche des animations de transition automatique pour créer une expérience utilisateur fluide lors du passage d’une orientation à une autre. À mesure que l’orientation de l’affichage change, l’utilisateur voit ces décalages sous la forme d’une animation de zoom et de rotation fixes de l’image d’écran affichée. Le temps est alloué par Windows 10 à l’application pour la disposition dans la nouvelle orientation.
Dans l’ensemble, il s’agit du processus général de gestion des modifications dans l’orientation de l’écran :
- Utilisez une combinaison des valeurs liées à la fenêtre et des données d’orientation d’affichage pour que la chaîne d’échange reste alignée sur l’orientation d’affichage native de l’appareil.
- Informez Windows 10 de l’orientation de la chaîne d’échange à l’aide de IDXGISwapChain1 ::SetRotation.
- Modifiez le code de rendu pour générer des images alignées sur l’orientation utilisateur de l’appareil.
Redimensionnement de la chaîne d’échange et préversion de son contenu
Pour effectuer un redimensionnement d’affichage de base et faire pré pivoter son contenu dans votre application UWP DirectX, implémentez les étapes suivantes :
- Gérez l’événement DisplayInformation ::OrientationChanged .
- Redimensionnez la chaîne d’échange vers les nouvelles dimensions de la fenêtre.
- Appelez IDXGISwapChain1 ::SetRotation pour définir l’orientation de la chaîne d’échange.
- Recréez toutes les ressources dépendantes de la taille de fenêtre, telles que vos cibles de rendu et d’autres mémoires tampons de données de pixels.
Examinons maintenant ces étapes plus en détail.
Votre première étape consiste à inscrire un gestionnaire pour l’événement DisplayInformation ::OrientationChanged . Cet événement est déclenché dans votre application chaque fois que l’orientation de l’écran change, par exemple lorsque l’affichage est pivoté.
Pour gérer l’événement DisplayInformation ::OrientationChanged, vous connectez votre gestionnaire pour DisplayInformation ::OrientationChanged dans la méthode SetWindow requise, qui est l’une des méthodes de l’interface IFrameworkView que votre fournisseur d’affichage doit implémenter.
Dans cet exemple de code, le gestionnaire d’événements pour DisplayInformation ::OrientationChanged est une méthode appelée OnOrientationChanged. Lorsque DisplayInformation ::OrientationChanged est déclenché, il appelle à son tour une méthode appelée SetCurrentOrientation , qui appelle ensuite CreateWindowSizeDependentResources.
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();
}
}
Ensuite, vous redimensionnez la chaîne d’échange pour la nouvelle orientation de l’écran et préparez-la pour faire pivoter le contenu du pipeline graphique lorsque le rendu est effectué. Dans cet exemple, DirectXBase ::CreateWindowSizeDependentResources est une méthode qui gère l’appel d’IDXGISwapChain ::ResizeBuffers, la définition d’une matrice de rotation 3D et 2D, l’appel de SetRotation et la recréation de vos ressources.
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);
}
Après avoir enregistré les valeurs actuelles de hauteur et de largeur de la fenêtre pour la prochaine fois que cette méthode est appelée, convertissez les valeurs de pixels indépendants de l’appareil (DIP) pour les limites d’affichage en pixels. Dans l’exemple, vous appelez ConvertDipsToPixels, qui est une fonction simple qui exécute ce code :
floor((dips * dpi / 96.0f) + 0.5f);
Vous ajoutez la valeur 0.5f pour garantir l’arrondi à la valeur entière la plus proche.
De côté, les coordonnées CoreWindow sont toujours définies dans les ADRESSES IP. Pour Windows 10 et versions antérieures de Windows, un DIP est défini comme 1/96e de pouce et aligné sur la définition du système d’exploitation vers le haut. Lorsque l’orientation de l’affichage pivote en mode portrait, l’application retourne la largeur et la hauteur de CoreWindow, et la taille de la cible de rendu (limites) doit changer en conséquence. Étant donné que les coordonnées de Direct3D sont toujours en pixels physiques, vous devez convertir les valeurs DIP de CoreWindow en valeurs de pixels entiers avant de passer ces valeurs à Direct3D pour configurer la chaîne d’échange.
Au niveau du processus, vous effectuez un peu plus de travail que vous le feriez si vous avez simplement redimensionné la chaîne d’échange : vous faites pivoter réellement les composants Direct2D et Direct3D de votre image avant de les compositer pour la présentation, et vous indiquez à la chaîne d’échange que vous avez rendu les résultats dans une nouvelle orientation. Voici un peu plus de détails sur ce processus, comme illustré dans l’exemple de code pour DX ::D eviceResources ::CreateWindowSizeDependentResources :
Déterminez la nouvelle orientation de l’affichage. Si l’affichage a basculé d’un paysage à un portrait, ou inversement, échangez les valeurs de hauteur et de largeur , passant des valeurs DIP aux pixels, bien sûr, pour les limites d’affichage.
Ensuite, vérifiez si la chaîne d’échange a été créée. S’il n’a pas été créé, créez-le en appelant IDXGIFactory2 ::CreateSwapChainForCoreWindow. Sinon, redimensionnez les mémoires tampons de la chaîne d’échange existante vers les nouvelles dimensions d’affichage en appelant IDXGISwapchain :ResizeBuffers. Bien que vous n’ayez pas besoin de redimensionner la chaîne d’échange pour l’événement de rotation , vous placez le contenu déjà pivoté par votre pipeline de rendu, après tout, il existe d’autres événements de modification de taille, tels que les événements d’alignement et de remplissage, qui nécessitent un redimensionnement.
Ensuite, définissez la transformation de matrice 2D ou 3D appropriée à appliquer aux pixels ou aux sommets (respectivement) dans le pipeline graphique lors de leur rendu dans la chaîne d’échange. Nous avons 4 matrices de rotation possibles :
- paysage (DXGI_MODE_ROTATION_IDENTITY)
- portrait (DXGI_MODE_ROTATION_ROTATE270)
- paysage, retourné (DXGI_MODE_ROTATION_ROTATE180)
- portrait, retourné (DXGI_MODE_ROTATION_ROTATE90)
La matrice correcte est sélectionnée en fonction des données fournies par Windows 10 (par exemple, les résultats de DisplayInformation ::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, en les faisant pivoter efficacement pour s’aligner sur l’orientation de l’écran. (Notez que dans Direct2D, l’origine de l’écran est définie en tant qu’angle supérieur gauche, tandis que dans Direct3D, l’origine est définie comme centre logique de la fenêtre.)
Remarque Pour plus d’informations sur les transformations 2D utilisées pour la rotation et la façon de les définir, consultez Définition de matrices pour la rotation de l’écran (2D). Pour plus d’informations sur les transformations 3D utilisées pour la rotation, consultez Définition de matrices pour la rotation de l’écran (3D).
Voici le bit important : appelez IDXGISwapChain1 ::SetRotation et fournissez-le avec votre matrice de rotation mise à jour, comme suit :
m_swapChain->SetRotation(rotation);
Vous stockez également la matrice de rotation sélectionnée où votre méthode de rendu peut l’obtenir quand elle calcule la nouvelle projection. Vous utiliserez cette matrice lorsque vous affichez votre projection 3D finale ou composite votre disposition finale 2D. (Il ne l’applique pas automatiquement pour vous.)
Ensuite, créez une cible de rendu pour la vue 3D pivotée, ainsi qu’une nouvelle mémoire tampon de gabarit de profondeur pour la vue. Définissez la fenêtre d’affichage de rendu 3D pour la scène pivotée en appelant ID3D11DeviceContext :RSSetViewports.
Enfin, 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 l’ID2D1DeviceContext ::CreateBitmapFromDxgiSurface et compositez 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 indiqué 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 10 fournit une fenêtre courte mais notable de temps pour n’importe quelle application, quel que soit le modèle ou la langue de l’application, pour effectuer la rotation de l’image. Toutefois, les chances sont que lorsque votre application effectue le calcul de rotation à l’aide de l’une des techniques décrites ici, elle sera effectuée bien avant la fermeture de cette fenêtre de temps. Vous souhaitez récupérer ce temps et terminer l’animation de rotation, non ? C’est là que CoreWindowResizeManager entre en jeu.
Voici comment utiliser CoreWindowResizeManager : lorsqu’un événement DisplayInformation ::OrientationChanged est déclenché, appelez CoreWindowResizeManager ::GetForCurrentView dans le gestionnaire de l’événement pour obtenir une instance de CoreWindowResizeManager et, lorsque la disposition de la nouvelle orientation est terminée et présentée, appelez NotifyLayoutCompleted pour informer Windows qu’il peut terminer l’animation de rotation et afficher l’écran de l’application.
Voici à quoi ressemble le code de votre gestionnaire d’événements pour DisplayInformation ::OrientationChanged :
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 10 affiche une animation indépendante de votre application en tant que commentaires à l’utilisateur. Il existe trois parties de cette animation qui se produisent dans l’ordre suivant :
- Windows 10 réduit l’image d’origine.
- Windows 10 contient l’image pour le temps nécessaire à la reconstruction de la nouvelle disposition. Il s’agit de la fenêtre de temps que vous souhaitez réduire, car votre application n’en a probablement pas besoin.
- Lorsque la fenêtre de disposition expire ou lorsqu’une notification de saisie semi-automatique de disposition est reçue, Windows fait pivoter l’image, puis effectue un zoom croisé en fondu vers une nouvelle orientation.
Comme suggéré dans la troisième puce, lorsqu’une application appelle NotifyLayoutCompleted, Windows 10 arrête la fenêtre de délai d’expiration, termine l’animation de rotation et retourne le contrôle à votre application, qui dessine désormais dans la nouvelle orientation d’affichage. L’effet global est que votre application se sent maintenant un peu plus fluide et réactive, et fonctionne un peu plus efficacement !
Annexe A : Application de matrices pour la rotation de l’écran (2D)
Dans l’exemple de code de redimensionnement de la chaîne d’échange et en pré-rotation de son contenu (et dans l’exemple de rotation de la chaîne d’échange DXGI), vous avez peut-être remarqué que nous avions des matrices de rotation distinctes pour la sortie Direct2D et la sortie Direct3D. Examinons 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 :
Un, ils utilisent différents modèles de coordonnées cartesiens. Direct2D utilise la règle de droite, où la coordonnée y augmente en valeur positive se déplaçant vers le haut de l’origine. Toutefois, Direct3D utilise la règle de gauche, où la coordonnée y augmente en valeur positive vers l’intérieur de l’origine. Le résultat est l’origine des coordonnées de l’é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, consultez les systèmes de coordonnées 3D.)
Deux, les matrices de rotation 3D doivent être spécifiées explicitement pour éviter les erreurs d’arrondi.
La chaîne d’échange part du principe que l’origine se trouve en bas à gauche. Vous devez donc effectuer une rotation pour aligner le système de coordonnées Direct2D droitier avec celui de gauche utilisé par la chaîne d’échange. Plus précisément, vous repositionnez l’image sous la nouvelle orientation gaucher en multipliant la matrice de rotation par une matrice de traduction pour l’origine du système de coordonnées pivoté, puis transformez 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 cette transformation de manière cohérente 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 directement associées à la chaîne d’échange, n’appliquez pas cette transformation d’espace de coordonnées.
Votre code pour sélectionner la matrice correcte dans les quatre rotations possibles peut ressembler à ceci (tenez compte de la traduction vers la nouvelle origine du système de coordonnées) :
// 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();
}
Une fois la matrice de rotation et l’origine correctes pour l’image 2DD, définissez-la avec un appel à ID2D1DeviceContext ::SetTransform entre vos appels à ID2D1DeviceContext ::BeginDraw et ID2D1DeviceContext ::EndDraw.
Avertissement Direct2D n’a pas de pile de transformation. Si votre application utilise également l’ID2D1DeviceContext ::SetTransform dans le cadre de son code de dessin, cette matrice doit être post-multipliée par toute autre transformation que vous avez appliquée.
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();
La prochaine fois que vous présentez la chaîne d’échange, votre image 2D sera pivotée pour correspondre à la nouvelle orientation de l’affichage.
Annexe B : Application de matrices pour la rotation de l’écran (3D)
Dans l’exemple de code de redimensionnement de la chaîne d’échange et de la préversion de son contenu (et dans l’exemple de rotation de la chaîne d’échange DXGI), nous avons défini une matrice de transformation spécifique pour chaque orientation d’écran possible. À présent, examinons les matrices pour faire pivoter des scènes 3D. Comme précédemment, vous créez un ensemble de matrices pour chacune des 4 orientations possibles. Pour éviter les erreurs d’arrondi et ainsi les artefacts visuels mineurs, déclarez les matrices explicitement dans votre code.
Vous configurez ces matrices de rotation 3D comme suit. Les matrices présentées dans l’exemple de code suivant sont des matrices de rotation standard pour les rotations 0, 90, 180 et 270 degrés des sommets qui définissent des points dans l’espace de scène 3D de la caméra. Chaque valeur de coordonnée de vertex [x, y, z] dans la scène est multipliée par cette matrice de rotation lorsque la projection 2D de la scène est calculée.
// 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
);
}
Vous définissez le type de rotation sur la chaîne d’échange avec un appel à IDXGISwapChain1 ::SetRotation, comme suit :
m_swapChain->SetRotation(rotation);
À présent, dans votre méthode de rendu, implémentez du code similaire à ceci :
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);
À présent, 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) avec la matrice de projection actuelle et affecte les résultats de cette opération en tant que nouvelle matrice de projection pour votre renderer. Présentez la chaîne d’échange pour voir la scène dans l’orientation d’affichage mise à jour.