Supporto all'orientamento dello schermo (DirectX e C++)
L'app UWP (Universal Windows Platform) può supportare più orientamenti dello schermo quando viene gestito l'evento DisplayInformation::OrientationChanged. In questa sede, discuteremo delle procedure consigliate per la gestione della rotazione dello schermo nell'app DirectX UWP, in modo che l'hardware grafico del dispositivo Windows 10 venga utilizzato in modo efficiente ed efficace.
Prima di iniziare, ricordare che l'hardware grafico restituisce sempre i dati dei pixel nello stesso modo, indipendentemente dall'orientamento del dispositivo. I dispositivi Windows 10 possono determinare l'orientamento corrente dello schermo (con una sorta di sensore o con un interruttore software) e consentire agli utenti di modificare le impostazioni di visualizzazione. Per questo motivo, Windows 10 gestisce autonomamente la rotazione delle immagini per accertarsi che siano "verticali" in base all'orientamento del dispositivo. Per impostazione predefinita, l'app riceve la notifica che qualcosa è cambiato nell'orientamento, ad esempio una dimensione della finestra. In questo caso, Windows 10 ruota immediatamente l'immagine per la visualizzazione finale. Per tre dei quattro orientamenti specifici dello schermo (descritti più avanti), Windows 10 utilizza risorse grafiche e calcoli aggiuntivi per visualizzare l'immagine finale.
Per le app DirectX UWP, l'oggetto DisplayInformation fornisce dati di base di orientamento dello schermo su cui l'app può eseguire query. L'orientamento predefinito è orizzontale, in cui la larghezza in pixel dello schermo è maggiore rispetto all'altezza. L'orientamento alternativo è verticale, in cui lo schermo viene ruotato di 90 gradi in una delle due direzioni e la larghezza diventa minore rispetto all'altezza.
Windows 10 definisce quattro modalità di orientamento dello schermo specifiche:
- Orizzontale: l'orientamento di visualizzazione predefinito per Windows 10 e viene considerato l'angolo di base o di identità per la rotazione (0 gradi).
- Verticale: lo schermo è stato ruotato in senso orario di 90 gradi (o antiorario di 270 gradi).
- Orizzontale, capovolto: lo schermo è stato ruotato di 180 gradi (sottosopra).
- Verticale, capovolto: lo schermo è stato ruotato in senso orario di 270 gradi (o in senso antiorario di 90 gradi).
Quando lo schermo ruota da un orientamento a un altro, Windows 10 esegue internamente un'operazione di rotazione per allineare l'immagine disegnata con il nuovo orientamento e l'utente vede un'immagine verticale sullo schermo.
Inoltre, Windows 10 visualizza animazioni di transizione automatiche per creare un'esperienza utente fluida quando si passa da un orientamento all'altro. Quando l'orientamento dello schermo cambia, l'utente vede questi spostamenti come un'animazione di zoom e rotazione fissa dell'immagine dello schermo visualizzata. L'orario viene allocato da Windows 10 nell'app per il layout nel nuovo orientamento.
Nel complesso, questo è il processo generale per la gestione delle modifiche nell'orientamento dello schermo:
- Utilizzare una combinazione dei valori dei limiti della finestra e dei dati di orientamento dello schermo per mantenere la catena di scambio allineata all'orientamento di visualizzazione nativo del dispositivo.
- Notificare a Windows 10 l'orientamento della catena di scambio tramite IDXGISwapChain1::SetRotation.
- Modificare il codice di rendering per generare immagini allineate all'orientamento dell'utente del dispositivo.
Ridimensionamento della catena di scambio e pre-rotazione del suo contenuto
Per eseguire un ridimensionamento dello schermo di base e la pre-rotazione del suo contenuto nell'app DirectX UWP, implementare questi passaggi:
- Gestire l'evento DisplayInformation::OrientationChanged.
- Ridimensionare la catena di scambio alle nuove dimensioni della finestra.
- Chiamare IDXGISwapChain1::SetRotation per impostare l'orientamento della catena di scambio.
- Ricreare qualsiasi risorsa dipendente delle dimensioni della finestra, ad esempio i target di rendering e altri buffer di dati pixel.
Esaminiamo ora questi passaggi in modo più dettagliato.
Il primo passaggio consiste nel registrare un gestore per l'evento DisplayInformation::OrientationChanged. Questo evento viene generato nell'app ogni volta che cambia l'orientamento dello schermo, ad esempio quando la visualizzazione viene ruotata.
Per gestire l'evento DisplayInformation::OrientationChanged, connettere il gestore per DisplayInformation::OrientationChanged nel metodo SetWindow richiesto, che è uno dei metodi dell'interfaccia IFrameworkView che il provider di visualizzazioni deve implementare.
In questo esempio di codice, il gestore eventi per DisplayInformation::OrientationChanged è un metodo denominato OnOrientationChanged. Quando DisplayInformation::OrientationChanged viene generato, chiama a sua volta un metodo denominato SetCurrentOrientation che chiama quindi 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();
}
}
Ridimensionare quindi la catena di scambio per il nuovo orientamento dello schermo e prepararla per ruotare il contenuto della pipeline grafica quando viene eseguito il rendering. In questo esempio DirectXBase::CreateWindowSizeDependentResources è un metodo che gestisce la chiamata a IDXGISwapChain::ResizeBuffers, impostando una matrice di rotazione 3D e 2D, chiamando SetRotation e ricreando le risorse.
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);
}
Dopo aver salvato i valori di altezza e larghezza correnti della finestra per la successiva chiamata a questo metodo, convertire i valori DIP (Device Independent Pixel) per i limiti di visualizzazione in pixel. Nell'esempio si chiama ConvertDipsToPixels, una funzione semplice che esegue questo codice:
floor((dips * dpi / 96.0f) + 0.5f);
Aggiungere 0,5f per garantire l'arrotondamento al valore intero più vicino.
D'altra parte, le coordinate CoreWindow sono sempre definite in DIP. Per Windows 10 e versioni precedenti di Windows, un DIP viene definito come 1/96 di pollice e allineato alla definizione di up del sistema operativo. Quando l'orientamento dello schermo passa alla modalità verticale, l'app capovolge la larghezza e l'altezza di CoreWindow e le dimensioni del target di rendering (limiti) devono cambiare di conseguenza. Poiché le coordinate di Direct3D sono sempre in pixel fisici, è necessario convertire da valori DIP di CoreWindow a valori di pixel interi prima di passare questi valori a Direct3D per impostare la catena di scambio.
A livello di processo, si sta eseguendo un po' di lavoro in più di quanto si farebbe se si ridimensionasse semplicemente la catena di scambio: si stanno effettivamente ruotando i componenti Direct2D e Direct3D dell'immagine prima di comporli per la presentazione, e si sta dicendo alla catena di scambio che è stato eseguito il rendering dei risultati in un nuovo orientamento. Ecco qualche ulteriore dettaglio su questo processo, come illustrato nell'esempio di codice per DX::DeviceResources::CreateWindowSizeDependentResources:
Determinare il nuovo orientamento della visualizzazione. Se lo schermo è stato capovolto da orizzontale a verticale o viceversa, scambiare i valori di altezza e larghezza, ovviamente da valori DIP a pixel, per i limiti di visualizzazione.
Verificare quindi se la catena di scambio sia stata creata. Se non è stata creata, crearla chiamando IDXGIFactory2::CreateSwapChainForCoreWindow. In caso contrario, ridimensionare i buffer della catena di scambio esistente in base alle nuove dimensioni di visualizzazione chiamando IDXGISwapchain:ResizeBuffers. Anche se non è necessario ridimensionare la catena di scambio per l'evento di rotazione, (dopo tutto, si sta emettendo il contenuto già ruotato dalla pipeline di rendering) sono presenti altri eventi di modifica delle dimensioni, quali eventi di allineamento e riempimento, che richiedono il ridimensionamento.
Successivamente, impostare la trasformazione della matrice 2D o 3D appropriata da applicare rispettivamente ai pixel o ai vertici nella pipeline grafica durante il rendering nella catena di scambio. Sono disponibili 4 possibili matrici di rotazione:
- orizzontale (DXGI_MODE_ROTATION_IDENTITY)
- verticale (DXGI_MODE_ROTATION_ROTATE270)
- orizzontale, capovolto (DXGI_MODE_ROTATION_ROTATE180)
- verticale, capovolto (DXGI_MODE_ROTATION_ROTATE90)
La matrice corretta viene selezionata in base ai dati forniti da Windows 10 (ad esempio i risultati di DisplayInformation::OrientationChanged) per determinare l'orientamento dello schermo, e verrà moltiplicata per le coordinate di ciascun pixel (Direct2D) o vertice (Direct3D) nella scena, ruotandoli in modo efficace per allinearsi all'orientamento dello schermo. Notare che in Direct2D l'origine dello schermo viene definita come angolo superiore sinistro, mentre in Direct3D l'origine è definita come centro logico della finestra.
Nota Per maggiori informazioni sulle trasformazioni 2D utilizzate per la rotazione e su come definirle, vedere Definizione di matrici per la rotazione dello schermo (2D). Per altre info sulle trasformazioni 3D utilizzate per la rotazione, vedere Definizione di matrici per la rotazione dello schermo (3D).
Ed ora, ecco il bit importante: chiamare IDXGISwapChain1::SetRotation e fornirgli la matrice di rotazione aggiornata, come illustrato di seguito:
m_swapChain->SetRotation(rotation);
Archiviare anche la matrice di rotazione selezionata dove il metodo di rendering può ottenerla quando calcola la nuova proiezione. Questa matrice verrà utilizzata quando si esegue il rendering della proiezione 3D finale o si compone il layout 2D finale (non viene applicata automaticamente).
Successivamente, creare un nuovo target di rendering per la visualizzazione 3D ruotata, nonché un nuovo buffer depth stencil per la visualizzazione. Impostare la Viewport di rendering 3D per la scena ruotata chiamando ID3D11DeviceContext:RSSetViewports.
Infine, se sono presenti immagini 2D da ruotare o disporre, creare un target di rendering 2D come bitmap scrivibile per la catena di scambio ridimensionata utilizzando ID2D1DeviceContext::CreateBitmapFromDxgiSurface e comporre il nuovo layout per l'orientamento aggiornato. Impostare tutte le proprietà necessarie nel target di rendering, ad esempio la modalità anti-aliasing, come illustrato nell'esempio di codice.
Presentare ora la catena di scambio.
Ridurre il ritardo di rotazione tramite CoreWindowResizeManager
Per impostazione predefinita, Windows 10 offre una breve ma evidente finestra temporale per qualsiasi app, indipendentemente dal modello o dalla lingua dell'app, per completare la rotazione dell'immagine. Tuttavia, è probabile che quando l'app esegue il calcolo della rotazione utilizzando una delle tecniche descritte qui, ciò avvenga molto prima che questa finestra temporale si sia chiusa. Sarebbe bello recuperare quel tempo e completare l'animazione di rotazione, giusto? Ecco dove entra in gioco CoreWindowResizeManager.
Ecco come utilizzare CoreWindowResizeManager: quando viene generato un evento DisplayInformation::OrientationChanged, chiamare CoreWindowResizeManager::GetForCurrentView all'interno del gestore per ottenere un'istanza di CoreWindowResizeManager e, quando il layout per il nuovo orientamento è completato e presentato, chiamare NotifyLayoutCompleted per informare Windows che può completare l'animazione di rotazione e visualizzare la schermata dell'app.
Ecco come potrebbe essere il codice nel gestore eventi per DisplayInformation::OrientationChanged :
CoreWindowResizeManager^ resizeManager = Windows::UI::Core::CoreWindowResizeManager::GetForCurrentView();
// ... build the layout for the new display orientation ...
resizeManager->NotifyLayoutCompleted();
Quando un utente ruota l'orientamento dello schermo, Windows 10 mostra un'animazione indipendente dall'app come feedback per l'utente. Esistono tre parti dell'animazione che si verificano nell'ordine seguente:
- Windows 10 riduce l'immagine originale.
- Windows 10 trattiene l'immagine per il tempo necessario per ricompilare il nuovo layout. Questo è l'intervallo di tempo che sarebbe bello ridurre, perché l'app probabilmente non ne ha bisogno.
- Quando la finestra di layout scade o quando viene ricevuta una notifica di completamento del layout, Windows ruota l'immagine e quindi viene eseguito lo zoom a dissolvenza incrociata sul nuovo orientamento.
Come suggerito nel terzo punto elenco, quando un'app chiama NotifyLayoutCompleted, Windows 10 arresta la finestra di timeout, completa l'animazione di rotazione e restituisce il controllo all'app, che ora disegna nel nuovo orientamento dello schermo. L'effetto complessivo è che l'app ora si sente un po' più fluida e reattiva e funziona in modo un po' più efficiente!
Appendice A: Applicazione di matrici per la rotazione dello schermo (2D)
Nel codice di esempio in Ridimensionamento della catena di scambio e pre-rotazione del suo contenuto (e nell'Esempio di rotazione della catena di scambio DXGI), si potrebbe aver notato che erano presenti matrici di rotazione separate per l'output Direct2D e l'output Direct3D. Esaminiamo prima le matrici 2D.
Esistono due motivi per cui non è possibile applicare le stesse matrici di rotazione a contenuto Direct2D e Direct3D:
Il primo è che utilizzano modelli di coordinate cartesiani diversi. Direct2D utilizza la regola destrorsa, in cui la coordinata y aumenta in valore positivo spostandosi verso l'alto rispetto all'origine. Invece, Direct3D utilizza la regola sinistrorsa, in cui la coordinata y aumenta in valore positivo spostandosi verso destra rispetto all'origine. Il risultato è che l'origine delle coordinate dello schermo si trova in alto a sinistra per Direct2D, mentre l'origine dello schermo (il piano di proiezione) si trova in basso a sinistra per Direct3D. Per maggiori informazioni, vedere Sistemi di coordinate 3D.
Il secondo è che le matrici di rotazione 3D devono essere specificate in modo esplicito per evitare errori di arrotondamento.
La catena di scambio presuppone che l'origine si trovi in basso a sinistra, quindi è necessario eseguire una rotazione per allineare il sistema di coordinate Direct2D destrorso con quello sinistrorso utilizzato dalla catena di scambio. In particolare, si riposiziona l'immagine sotto il nuovo orientamento sinistrorso moltiplicando la matrice di rotazione con una matrice di traslazione per l'origine del sistema di coordinate ruotato e si trasforma l'immagine dallo spazio di coordinate di CoreWindow allo spazio di coordinate della catena di scambio. L'app deve anche applicare in modo coerente questa trasformazione quando il target di rendering Direct2D viene collegato alla catena di scambio. Tuttavia, se l'app sta disegnando su superfici intermedie non associate direttamente alla catena di scambio, non applicare questa trasformazione dello spazio di coordinate.
Il codice per selezionare la matrice corretta tra le quattro possibili rotazioni potrebbe essere simile al seguente (tenere presente la traslazione alla nuova origine del sistema di coordinate):
// 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();
}
Dopo aver ottenuto la matrice di rotazione e l'origine corrette per l'immagine 2D, impostarla con una chiamata a ID2D1DeviceContext::SetTransform tra le chiamate a ID2D1DeviceContext::BeginDraw e ID2D1DeviceContext::EndDraw.
Avvertenza Direct2D non dispone di uno stack di trasformazione. Se l'app utilizza anche ID2D1DeviceContext::SetTransform come parte del codice di disegno, questa matrice deve essere post-moltiplicata per qualsiasi altra trasformazione applicata.
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();
Alla successiva presentazione della catena di scambio, l'immagine 2D verrà ruotata in modo da corrispondere al nuovo orientamento dello schermo.
Appendice B: Applicazione di matrici per la rotazione dello schermo (3D)
Nel codice di esempio in Ridimensionamento della catena di scambio e pre-rotazione del suo contenuto (e nell'Esempio di rotazione della catena di scambio DXGI), abbiamo definito una matrice di trasformazione specifica per ogni possibile orientamento dello schermo. Verranno ora esaminate le matrici per la rotazione di scene 3D. Come in precedenza, si crea una serie di matrici per ognuno dei 4 possibili orientamenti. Per evitare errori di arrotondamento e quindi artefatti visivi secondari, dichiarare le matrici in modo esplicito nel codice.
Impostare queste matrici di rotazione 3D nel modo seguente. Le matrici illustrate nell'esempio di codice seguente sono matrici di rotazione standard per 0, 90, 180 e 270 gradi di rotazione dei vertici che definiscono punti nello spazio della scena 3D della telecamera. Il valore di coordinata [x, y, z] di ciascun vertice nella scena viene moltiplicato per questa matrice di rotazione quando viene calcolata la proiezione 2D della scena.
// 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
);
}
Impostare il tipo di rotazione nella catena di scambio con una chiamata a IDXGISwapChain1::SetRotation, come illustrato di seguito:
m_swapChain->SetRotation(rotation);
Ora, nel metodo di rendering implementare codice simile al seguente:
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);
A questo punto, quando viene chiamato, il metodo di rendering moltiplica la matrice di rotazione corrente (come specificato dalla variabile di classe m_orientationTransform3D) con la matrice di proiezione corrente e assegna i risultati di tale operazione come nuova matrice di proiezione per il renderer. Presentare la catena di scambio per visualizzare la scena nell'orientamento dello schermo aggiornato.