Compartilhar via


Dar suporte à orientação de tela (DirectX e C++)

Seu aplicativo UWP (Plataforma Universal do Windows) pode dar suporte a várias orientações de tela quando você manipula o evento DisplayInformation::OrientationChanged . Aqui, discutiremos as práticas recomendadas para lidar com a rotação da tela em seu aplicativo UWP DirectX, para que o hardware gráfico do dispositivo Windows 10 seja usado de forma eficiente e eficaz.

Antes de começar, lembre-se de que o hardware gráfico sempre gera dados de pixel da mesma maneira, independentemente da orientação do dispositivo. Os dispositivos Windows 10 podem determinar sua orientação de exibição atual (com algum tipo de sensor ou com uma alternância de software) e permitir que os usuários alterem as configurações de exibição. Por isso, o próprio Windows 10 lida com a rotação das imagens para garantir que elas estejam "na vertical" com base na orientação do dispositivo. Por padrão, seu aplicativo recebe a notificação de que algo mudou na orientação, por exemplo, o tamanho de uma janela. Quando isso acontece, o Windows 10 gira imediatamente a imagem para exibição final. Para três das quatro orientações de tela específicas (discutidas posteriormente), Windows 10 usa recursos gráficos adicionais e computação para exibir a imagem final.

Para aplicativos UWP DirectX, o objeto DisplayInformation fornece dados básicos de orientação de exibição que seu aplicativo pode consultar. A orientação padrão é paisagem, onde a largura de pixel da tela é maior que a altura; a orientação alternativa é retrato, onde a tela é girada 90 graus em qualquer direção e a largura se torna menor que a altura.

Windows 10 define quatro modos de orientação de exibição específicos:

  • Paisagem — a orientação de exibição padrão para Windows 10 e é considerada a base ou o ângulo de identidade para rotação (0 graus).
  • Retrato - a tela foi girada 90 graus no sentido horário (ou 270 graus no sentido anti-horário).
  • Paisagem, invertida - a tela foi girada 180 graus (virada de cabeça para baixo).
  • Retrato, invertido - a tela foi girada 270 graus no sentido horário (ou 90 graus no sentido anti-horário).

Quando a exibição gira de uma orientação para outra, Windows 10 executa internamente uma operação de rotação para alinhar a imagem desenhada com a nova orientação e o usuário vê uma imagem vertical na tela.

Além disso, o Windows 10 exibe animações de transição automáticas para criar uma experiência de usuário suave ao mudar de uma orientação para outra. À medida que a orientação da tela muda, o usuário vê essas mudanças como uma animação fixa de zoom e rotação da imagem da tela exibida. O tempo é alocado pelo Windows 10 para o aplicativo para layout na nova orientação.

No geral, este é o processo geral para lidar com alterações na orientação da tela:

  1. Use uma combinação dos valores de limites de janela e os dados de orientação de exibição para manter a cadeia de troca alinhada com a orientação de exibição nativa do dispositivo.
  2. Notifique Windows 10 sobre a orientação da cadeia de troca usando IDXGISwapChain1::SetRotation.
  3. Altere o código de renderização para gerar imagens alinhadas com a orientação do usuário do dispositivo.

Redimensionando a cadeia de troca e pré-girando seu conteúdo

Para executar um redimensionamento básico de exibição e pré-girar seu conteúdo em seu aplicativo DirectX UWP, implemente estas etapas:

  1. Manipule o evento DisplayInformation::OrientationChanged.
  2. Redimensione a cadeia de troca para as novas dimensões da janela.
  3. Chame IDXGISwapChain1::SetRotation para definir a orientação da cadeia de troca.
  4. Recrie todos os recursos dependentes do tamanho da janela, como seus destinos de renderização e outros buffers de dados de pixel.

Agora vamos examinar essas etapas com um pouco mais de detalhes.

Sua primeira etapa é registrar um manipulador para o evento DisplayInformation::OrientationChanged . Esse evento é gerado em seu aplicativo sempre que a orientação da tela é alterada, como quando a exibição é girada.

Para manipular o evento DisplayInformation::OrientationChanged, conecte o manipulador para DisplayInformation::OrientationChanged no método SetWindow necessário, que é um dos métodos da interface IFrameworkView que seu provedor de exibição deve implementar.

Neste exemplo de código, o manipulador de eventos para DisplayInformation::OrientationChanged é um método chamado OnOrientationChanged. Quando DisplayInformation::OrientationChanged é gerado, ele, por sua vez, chama um método chamado SetCurrentOrientation que, em seguida, chama 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();
    }
}

Em seguida, você redimensiona a cadeia de troca para a nova orientação da tela e a prepara para girar o conteúdo do pipeline gráfico quando a renderização for executada. Neste exemplo, DirectXBase::CreateWindowSizeDependentResources é um método que lida com a chamada IDXGISwapChain::ResizeBuffers, definindo uma matriz de rotação 3D e 2D, chamando SetRotation e recriando seus recursos.

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

}

Depois de salvar os valores atuais de altura e largura da janela para a próxima vez que esse método for chamado, converta os valores de pixel independente do dispositivo (DIP) para os limites de exibição em pixels. No exemplo, você chama ConvertDipsToPixels, que é uma função simples que executa este código:

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

Você adiciona o 0,5f para garantir o arredondamento para o valor inteiro mais próximo.

Como um aparte, as coordenadas CoreWindow são sempre definidas em DIPs. Para Windows 10 e versões anteriores do Windows, um DIP é definido como 1/96 de polegada e alinhado à definição do sistema operacional de up. Quando a orientação de exibição gira para o modo retrato, o aplicativo inverte a largura e a altura do CoreWindow e o tamanho do destino de renderização (limites) deve ser alterado de acordo. Como as coordenadas do Direct3D estão sempre em pixels físicos, você deve converter os valores DIP do CoreWindow em valores de pixel inteiro antes de passar esses valores para o Direct3D para configurar a cadeia de troca.

Em termos de processo, você está fazendo um pouco mais de trabalho do que faria se simplesmente redimensionasse a cadeia de troca: na verdade, você está girando os componentes Direct2D e Direct3D de sua imagem antes de compô-los para apresentação e está informando à cadeia de troca que renderizou os resultados em uma nova orientação. Aqui está um pouco mais de detalhes sobre esse processo, conforme mostrado no exemplo de código para DX::D eviceResources::CreateWindowSizeDependentResources:

  • Determine a nova orientação da tela. Se a tela mudou de paisagem para retrato, ou vice-versa, troque os valores de altura e largura - alterados de valores DIP para pixels, é claro - para os limites de exibição.

  • Em seguida, verifique se a cadeia de troca foi criada. Se ele não tiver sido criado, crie-o chamando IDXGIFactory2::CreateSwapChainForCoreWindow. Caso contrário, redimensione os buffers da cadeia de troca existente para as novas dimensões de exibição chamando IDXGISwapchain:ResizeBuffers. Embora você não precise redimensionar a cadeia de troca para o evento de rotação — afinal, você está gerando o conteúdo já girado pelo pipeline de renderização — há outros eventos de alteração de tamanho, como eventos de ajuste e preenchimento, que exigem redimensionamento.

  • Depois disso, defina a transformação de matriz 2D ou 3D apropriada para aplicar aos pixels ou vértices (respectivamente) no pipeline de gráficos ao renderizá-los na cadeia de troca. Temos 4 matrizes de rotação possíveis:

    • Paisagem (DXGI_MODE_ROTATION_IDENTITY)
    • Retrato (DXGI_MODE_ROTATION_ROTATE270)
    • paisagem, invertida (DXGI_MODE_ROTATION_ROTATE180)
    • Retrato, invertido (DXGI_MODE_ROTATION_ROTATE90)

    A matriz correta é selecionada com base nos dados fornecidos pelo Windows 10 (como os resultados de DisplayInformation::OrientationChanged) para determinar a orientação da exibição e será multiplicada pelas coordenadas de cada pixel (Direct2D) ou vértice (Direct3D) na cena, girando-os efetivamente para alinhá-los à orientação da tela. (Observe que, no Direct2D, a origem da tela é definida como o canto superior esquerdo, enquanto no Direct3D a origem é definida como o centro lógico da janela.)

Observação Para obter mais informações sobre as transformações 2D usadas para rotação e como defini-las, consulte Definindo matrizes para rotação de tela (2D). Para obter mais informações sobre as transformações 3D usadas para rotação, consulte Definindo matrizes para rotação de tela (3D).

 

Agora, aqui está a parte importante: chame IDXGISwapChain1::SetRotation e forneça a ele sua matriz de rotação atualizada, desta forma:

m_swapChain->SetRotation(rotation);

Você também armazena a matriz de rotação selecionada onde seu método de renderização pode obtê-la ao calcular a nova projeção. Você usará essa matriz ao renderizar sua projeção 3D final ou compor seu layout 2D final. (Ele não o aplica automaticamente para você.)

Depois disso, crie um novo destino de renderização para a visualização 3D girada, bem como um novo buffer de estêncil de profundidade para a visualização. Defina o visor de renderização 3D para a cena girada chamando ID3D11DeviceContext:RSSetViewports.

Por fim, se você tiver imagens 2D para girar ou dispor, crie um destino de renderização 2D como um bitmap gravável para a cadeia de troca redimensionada usando ID2D1DeviceContext::CreateBitmapFromDxgiSurface e componha seu novo layout para a orientação atualizada. Defina todas as propriedades necessárias no destino de renderização, como o modo de suavização de serrilhado (como visto no exemplo de código).

Agora, apresente a cadeia de troca.

Reduza o atraso de rotação usando CoreWindowResizeManager

Por padrão, o Windows 10 fornece uma janela de tempo curta, mas perceptível, para qualquer aplicativo, independentemente do modelo ou idioma do aplicativo, concluir a rotação da imagem. No entanto, é provável que, quando seu aplicativo executar o cálculo de rotação usando uma das técnicas descritas aqui, ele seja feito bem antes que essa janela de tempo seja fechada. Você gostaria de recuperar esse tempo e completar a animação de rotação, certo? É aí que entra o CoreWindowResizeManager.

Veja como usar CoreWindowResizeManager: quando um evento DisplayInformation::OrientationChanged for gerado, chame CoreWindowResizeManager::GetForCurrentView dentro do manipulador para que o evento obtenha uma instância de CoreWindowResizeManager e, quando o layout da nova orientação for concluído e apresentado, chame o NotifyLayoutCompleted para que o Windows saiba que ele pode concluir a animação de rotação e exibir a tela do aplicativo.

Veja como pode ser o código em seu manipulador de eventos para DisplayInformation::OrientationChanged :

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

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

resizeManager->NotifyLayoutCompleted();

Quando um usuário gira a orientação da exibição, Windows 10 mostra uma animação independente do seu aplicativo como comentários para o usuário. Há três partes dessa animação que acontecem na seguinte ordem:

  • O Windows 10 reduz a imagem original.
  • O Windows 10 mantém a imagem pelo tempo necessário para recompilar o novo layout. Essa é a janela de tempo que você gostaria de reduzir, porque seu aplicativo provavelmente não precisa de tudo isso.
  • Quando a janela de layout expira ou quando uma notificação de conclusão de layout é recebida, o Windows gira a imagem e, em seguida, o zoom de fade cruzado para uma nova orientação.

Conforme sugerido no terceiro marcador, quando um aplicativo chama NotifyLayoutCompleted, Windows 10 interrompe a janela de tempo limite, conclui a animação de rotação e retorna o controle para seu aplicativo, que agora está desenhando na nova orientação de exibição. O efeito geral é que seu aplicativo agora parece um pouco mais fluido e responsivo e funciona com um pouco mais de eficiência!

Apêndice A: Aplicação de matrizes para rotação de tela (2-D)

No código de exemplo em Redimensionando a cadeia de troca e pré-girando seu conteúdo (e no exemplo de rotação da cadeia de troca DXGI), você deve ter notado que tínhamos matrizes de rotação separadas para saída Direct2D e saída Direct3D. Vejamos primeiro as matrizes 2-D.

Há dois motivos pelos quais não podemos aplicar as mesmas matrizes de rotação ao conteúdo Direct2D e Direct3D:

  • Primeiro, eles usam diferentes modelos de coordenadas cartesianas. Direct2D usa a regra destra, em que a coordenada y aumenta em valor positivo movendo-se para cima a partir da origem. No entanto, o Direct3D usa a regra canhota, em que a coordenada y aumenta em valor positivo para a direita a partir da origem. O resultado é que a origem das coordenadas da tela está localizada no canto superior esquerdo do Direct2D, enquanto a origem da tela (o plano de projeção) está no canto inferior esquerdo do Direct3D. (Consulte sistemas de coordenadas 3D para obter mais informações.)

    Sistema de coordenadas Direct3D.sistema de coordenadas direct2d.

  • Dois, as matrizes de rotação 3D devem ser especificadas explicitamente para evitar erros de arredondamento.

A cadeia de troca pressupõe que a origem esteja localizada no canto inferior esquerdo, portanto, você deve executar uma rotação para alinhar o sistema de coordenadas Direct2D destro com o canhoto usado pela cadeia de troca. Especificamente, você reposiciona a imagem sob a nova orientação à esquerda multiplicando a matriz de rotação por uma matriz de conversão para a origem do sistema de coordenadas girado e transforma a imagem do espaço de coordenadas do CoreWindow no espaço de coordenadas da cadeia de troca. Seu aplicativo também deve aplicar consistentemente essa transformação quando o destino de renderização Direct2D estiver conectado à cadeia de troca. No entanto, se o aplicativo estiver desenhando em superfícies intermediárias que não estão associadas diretamente à cadeia de troca, não aplique essa transformação de espaço de coordenadas.

Seu código para selecionar a matriz correta entre as quatro rotações possíveis pode ser semelhante a esta (esteja ciente da tradução para a nova origem do sistema de coordenadas):

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

Depois de ter a matriz de rotação e a origem corretas para a imagem 2D, defina-a com uma chamada para ID2D1DeviceContext::SetTransform entre suas chamadas para ID2D1DeviceContext::BeginDraw e ID2D1DeviceContext::EndDraw.

Aviso : Direct2D não tem uma pilha de transformação. Se o aplicativo também estiver usando o ID2D1DeviceContext::SetTransform como parte de seu código de desenho, essa matriz precisará ser pós-multiplicada para qualquer outra transformação que você tenha aplicado.

 

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

Na próxima vez que você apresentar a cadeia de troca, sua imagem 2D será girada para corresponder à nova orientação de exibição.

Apêndice B: Aplicação de matrizes para rotação de tela (3D)

No código de exemplo em Redimensionando a cadeia de troca e pré-girando seu conteúdo (e no exemplo de rotação da cadeia de troca DXGI), definimos uma matriz de transformação específica para cada orientação de tela possível. Agora, vamos dar uma olhada nas matrizes para girar cenas 3D. Como antes, você cria um conjunto de matrizes para cada uma das 4 orientações possíveis. Para evitar erros de arredondamento e, portanto, artefatos visuais secundários, declare as matrizes explicitamente em seu código.

Você configura essas matrizes de rotação 3D da seguinte maneira. As matrizes mostradas no exemplo de código a seguir são matrizes de rotação padrão para rotações de 0, 90, 180 e 270 graus dos vértices que definem pontos no espaço de cena 3D da câmera. O valor da coordenada [x, y, z] de cada vértice na cena é multiplicado por essa matriz de rotação quando a projeção 2D da cena é calculada.

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

Você define o tipo de rotação na cadeia de troca com uma chamada para IDXGISwapChain1::SetRotation, desta forma:

m_swapChain->SetRotation(rotation);

Agora, em seu método render, implemente algum código semelhante a este:

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

Agora, quando você chama seu método de renderização, ele multiplica a matriz de rotação atual (conforme especificado pela variável de classe m_orientationTransform3D) pela matriz de projeção atual e atribui os resultados dessa operação como a nova matriz de projeção para seu renderizador. Apresente a cadeia de troca para ver a cena na orientação de exibição atualizada.