Partilhar via


Suporte à orientação da tela (DirectX e C++)

Seu aplicativo da Plataforma Universal do Windows (UWP) pode oferecer 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 emite 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 causa disso, 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 mais tarde), o Windows 10 usa recursos gráficos e computação adicionais 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 do pixel da tela é maior do que a altura; A orientação alternativa é retrato, onde o visor é girado 90 graus em qualquer direção e a largura torna-se menor do que a altura.

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

  • Paisagem — a orientação de exibição padrão para o Windows 10 e é considerada o ângulo de base ou identidade para rotação (0 graus).
  • Retrato—o ecrã foi rodado 90 graus no sentido dos ponteiros do relógio (ou 270 graus no sentido anti-horário).
  • Paisagem, invertida — o ecrã foi rodado 180 graus (virado de cabeça para baixo).
  • Retrato, invertido—o ecrã foi rodado no sentido horário 270 graus (ou anti-horário 90 graus).

Quando a tela gira de uma orientação para outra, o 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ática 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ê esses deslocamentos como uma animação fixa de zoom e rotação da imagem da tela exibida. O tempo é alocado pelo Windows 10 para a aplicação ajustar o layout à 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 dos limites da janela e dos dados de orientação de exibição para manter a cadeia de permuta alinhada com a orientação de exibição nativa do dispositivo.
  2. Notifique o Windows 10 sobre a orientação da cadeia de permuta usando IDXGISwapChain1::SetRotation.
  3. Altere o código de renderização para gerar imagens alinhadas com a orientação do usuário do dispositivo.

Redimensionamento da cadeia de permuta e pré-rotação do seu conteúdo

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

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

Agora vamos olhar para essas etapas com um pouco mais de detalhes.

A sua primeira etapa é registar 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 seu gestor para DisplayInformation::OrientationChanged no método necessário SetWindow, que é um dos métodos da interface IFrameworkView que o seu fornecedor de vista deve implementar.

Neste exemplo de código, o manipulador de eventos para DisplayInformation::OrientationChanged é um método chamado OnOrientationChanged. Quando o evento DisplayInformation::OrientationChanged é gerado, ele, por sua vez, chama um método chamado SetCurrentOrientation, que então 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, redimensione a cadeia de permuta para a nova orientação da tela e prepare-a 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 de IDXGISwapChain::ResizeBuffers, define uma matriz de rotação 3D e uma 2D, chama SetRotation e recria os 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.

Por falar nisso, as coordenadas CoreWindow são sempre definidas em DIPs. Para o Windows 10 e versões anteriores do Windows, um DIP é definido como 1/96 de polegada e alinhado à definição do sistema operacional de até. Quando a orientação de exibição gira para o modo retrato, o aplicativo inverte a largura e a altura do CoreWindowe 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, deve-se converter os valores DIP do CoreWindowpara valores de pixels inteiros antes de passar esses valores para o Direct3D para configurar a cadeia de permuta.

Em termos de processo, você está a fazer um pouco mais de trabalho do que faria se simplesmente redimensionasse a cadeia de troca: está efetivamente a girar os componentes Direct2D e Direct3D da sua imagem antes de os compor para apresentação e está a informar a cadeia de troca de que processou os resultados em uma nova orientação. Aqui estão alguns detalhes adicionais sobre este processo, como mostrado no exemplo de código para DX::DeviceResources::CreateWindowSizeDependentResources:

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

  • Em seguida, verifique se a cadeia de permuta foi criada. Se não tiver sido criado, crie-o chamando IDXGIFactory2::CreateSwapChainForCoreWindow. Caso contrário, redimensione os buffers da cadeia de permuta existente para as novas dimensões de exibição chamando IDXGISwapchain:ResizeBuffers. Embora você não precise redimensionar a cadeia de permuta para o evento de rotação — afinal, você está enviando 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 a aplicar aos pixels ou aos vértices (respectivamente) na linha de processamento gráfico ao renderizá-los para a 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 tela, e será multiplicada pelas coordenadas de cada pixel (Direct2D) ou vértice (Direct3D) na cena, girando-as efetivamente para se alinhar à 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.)

Nota 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 (2-D). 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-lhe sua matriz de rotação atualizada, assim:

m_swapChain->SetRotation(rotation);

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

Depois disso, crie um novo alvo de renderização para a vista 3D rotacionada, bem como um novo buffer de estêncil de profundidade para a vista. 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 permuta redimensionada usando ID2D1DeviceContext::CreateBitmapFromDxgiSurface e componha seu novo layout para a orientação atualizada. Configure quaisquer propriedades que sejam necessárias no destino de renderização, como o modo de suavização de borda (como se vê 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 percetí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, isso 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 CoreWindowResizeManager.

Aqui está como usar CoreWindowResizeManager: quando um evento DisplayInformation::OrientationChanged for gerado, chame CoreWindowResizeManager::GetForCurrentView dentro do manipulador do evento para obter uma instância de CoreWindowResizeManager e, quando o layout da nova orientação estiver concluído e apresentado, chame o NotifyLayoutCompleted para informar ao Windows que é possível concluir a animação de rotação e exibir o ecrã do aplicativo.

Veja como o código no manipulador de eventos para DisplayInformation::OrientationChanged pode parecer:

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

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

resizeManager->NotifyLayoutCompleted();

Quando um utilizador roda a orientação do ecrã, o Windows 10 mostra uma animação independente da sua aplicação como feedback ao utilizador. Há três partes nessa 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 reconstruir o novo layout. Esta é a janela de tempo que você gostaria de reduzir, porque seu aplicativo provavelmente não precisa de tudo isso.
  • Quando a sessão de layout expira ou quando uma notificação de conclusão de layout é recebida, o Windows roda a imagem e, em seguida, aplica um efeito de cross-fade com zoom para uma nova orientação.

Como sugerido no terceiro marcador, quando uma aplicação chama NotifyLayoutCompleted, o Windows 10 interrompe a janela de tempo limite, conclui a animação de rotação e retorna o controle para o 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 do ecrã (2-D)

No código de exemplo em Redimensionando a cadeia de permuta e pré-rotacionando seu conteúdo (e no exemplo de rotação da cadeia de permuta 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 2D.

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. O Direct2D usa a regra da mão direita, onde a coordenada y aumenta em valor positivo ao mover-se para cima a partir da origem. No entanto, o Direct3D usa a regra da mão esquerda, onde a coordenada y aumenta para valores positivos à 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.

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

A swap chain assume que a origem está localizada no canto inferior esquerdo, pelo que é necessário realizar uma rotação para alinhar o sistema de coordenadas destro do Direct2D com o canhoto utilizado pela swap chain. Especificamente, você reposiciona a imagem sob a nova orientação para a esquerda multiplicando a matriz de rotação por uma matriz de translação para a origem do sistema de coordenadas girado, e transforma a imagem, especificamente, do espaço de coordenadas do CoreWindowpara o espaço de coordenadas da cadeia de permuta. Seu aplicativo também deve aplicar consistentemente essa transformação quando o destino de renderização Direct2D estiver conectado à cadeia de permuta. No entanto, se seu aplicativo estiver desenhando em superfícies intermediárias que não estão associadas diretamente à cadeia de permuta, não aplique essa transformação de espaço de coordenadas.

Seu código para selecionar a matriz correta das quatro rotações possíveis pode ter esta aparência (esteja ciente da tradução para a origem do novo 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 O Direct2D não tem uma pilha de transformação. Se seu 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();

Da próxima vez que apresentar a cadeia de permuta, a sua imagem 2D será rodada para corresponder à nova orientação do ecrã.

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

No código de exemplo em Redimensionando a cadeia de permuta e pré-rotacionando seu conteúdo (e no exemplo de rotação da cadeia de permuta DXGI), definimos uma matriz de transformação específica para cada orientação de tela possível. Agora, vamos ver as matrizes de rotação de cenas tridimensionais. Como antes, você cria um conjunto de matrizes para cada uma das 4 orientações possíveis. Para evitar erros de arredondamento e, portanto, pequenos artefatos visuais, 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 esta 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 permuta com uma chamada para IDXGISwapChain1::SetRotation, desta forma:

m_swapChain->SetRotation(rotation);

Agora, em seu método de renderização, 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 o renderizador. Apresente a cadeia de permuta para ver a cena na orientação de exibição atualizada.