Compartir a través de


Compatibilidad con la orientación de pantalla (DirectX y C++)

La aplicación Plataforma universal de Windows (UWP) puede admitir varias orientaciones de pantalla al controlar el evento DisplayInformation::OrientationChanged. Aquí analizaremos los procedimientos recomendados para controlar la rotación de pantalla en tu aplicación DirectX para UWP, de modo que el hardware gráfico del dispositivo Windows 10 se use de forma eficaz y eficaz.

Antes de empezar, recuerde que el hardware gráfico siempre genera datos de píxeles de la misma manera, independientemente de la orientación del dispositivo. Los dispositivos Windows 10 pueden determinar su orientación de pantalla actual (con algún tipo de sensor o con un botón de alternancia de software) y permitir que los usuarios cambien la configuración de pantalla. Por este motivo, Windows 10 controla la rotación de las imágenes para asegurarse de que están "verticales" en función de la orientación del dispositivo. De forma predeterminada, la aplicación recibe la notificación de que algo ha cambiado en la orientación, por ejemplo, un tamaño de ventana. Cuando esto sucede, Windows 10 gira inmediatamente la imagen para la presentación final. Para tres de las cuatro orientaciones de pantalla específicas (descritas más adelante), Windows 10 usa recursos gráficos adicionales y cálculos para mostrar la imagen final.

En el caso de las aplicaciones DirectX para UWP, el objeto DisplayInformation proporciona datos básicos de orientación de visualización que la aplicación puede consultar. La orientación predeterminada es horizontal, donde el ancho de píxel de la pantalla es mayor que el alto; la orientación alternativa es vertical, donde la pantalla gira 90 grados en cualquier dirección y el ancho se vuelve menor que el alto.

Windows 10 define cuatro modos de orientación de pantalla específicos:

  • Horizontal: la orientación de visualización predeterminada para Windows 10 y se considera el ángulo de base o identidad para la rotación (0 grados).
  • Vertical: la pantalla se ha girado en el sentido de las agujas del reloj 90 grados (o en sentido contrario a las agujas del reloj 270 grados).
  • Horizontal, volteado: la pantalla se ha girado 180 grados (al revés).
  • Vertical, volteado: la pantalla se ha girado en el sentido de las agujas del reloj 270 grados (o en sentido contrario a las agujas del reloj 90 grados).

Cuando la pantalla gira de una orientación a otra, Windows 10 realiza internamente una operación de rotación para alinear la imagen dibujada con la nueva orientación y el usuario ve una imagen vertical en la pantalla.

Además, Windows 10 muestra animaciones de transición automática para crear una experiencia de usuario fluida al cambiar de una orientación a otra. A medida que cambia la orientación de la pantalla, el usuario ve estos cambios como una animación fija de zoom y rotación de la imagen de pantalla mostrada. Windows 10 asigna el tiempo a la aplicación para el diseño en la nueva orientación.

En general, este es el proceso general para controlar los cambios en la orientación de la pantalla:

  1. Use una combinación de los valores enlazados de la ventana y los datos de orientación de visualización para mantener la cadena de intercambio alineada con la orientación de visualización nativa del dispositivo.
  2. Notifique a Windows 10 la orientación de la cadena de intercambio mediante IDXGISwapChain1::SetRotation.
  3. Cambie el código de representación para generar imágenes alineadas con la orientación del usuario del dispositivo.

Cambio de tamaño de la cadena de intercambio y rotación previa de su contenido

Para realizar un cambio de tamaño de pantalla básico y girar previamente su contenido en la aplicación DirectX para UWP, implemente estos pasos:

  1. Controle el evento DisplayInformation::OrientationChanged .
  2. Cambie el tamaño de la cadena de intercambio a las nuevas dimensiones de la ventana.
  3. Llame a IDXGISwapChain1::SetRotation para establecer la orientación de la cadena de intercambio.
  4. Vuelva a crear los recursos dependientes de tamaño de ventana, como los destinos de representación y otros búferes de datos de píxeles.

Ahora echemos un vistazo a esos pasos con más detalle.

El primer paso es registrar un controlador para el evento DisplayInformation::OrientationChanged . Este evento se genera en la aplicación cada vez que cambia la orientación de la pantalla, como cuando se gira la pantalla.

Para controlar el evento DisplayInformation::OrientationChanged, conecta el controlador para DisplayInformation::OrientationChanged en el método SetWindow necesario, que es uno de los métodos de la interfaz IFrameworkView que el proveedor de vistas debe implementar.

En este ejemplo de código, el controlador de eventos para DisplayInformation::OrientationChanged es un método denominado OnOrientationChanged. Cuando se genera DisplayInformation::OrientationChanged , a su vez llama a un método denominado SetCurrentOrientation que, a continuación, llama a 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();
    }
}

A continuación, cambie el tamaño de la cadena de intercambio para la nueva orientación de pantalla y prepárelo para girar el contenido de la canalización gráfica cuando se realiza la representación. En este ejemplo, DirectXBase::CreateWindowSizeDependentResources es un método que controla la llamada a IDXGISwapChain::ResizeBuffers, estableciendo una matriz de rotación 3D y 2D, llamando a SetRotation y recreando los 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);

}

Después de guardar los valores de alto y ancho actuales de la ventana para la próxima vez que se llame a este método, convierta los valores de píxeles independientes del dispositivo (DIP) para los límites de pantalla a píxeles. En el ejemplo, se llama a ConvertDipsToPixels, que es una función sencilla que ejecuta este código:

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

Agregue 0,5f para garantizar el redondeo al valor entero más cercano.

Como un lado, las coordenadas CoreWindow siempre se definen en DIP. Para Windows 10 y versiones anteriores de Windows, una DIP se define como 1/96 de pulgada y se alinea con la definición del sistema operativo de arriba. Cuando la orientación de la pantalla gira al modo vertical, la aplicación voltea el ancho y el alto de CoreWindow y el tamaño de destino de representación (límites) debe cambiar en consecuencia. Dado que las coordenadas de Direct3D siempre están en píxeles físicos, debe convertir de los valores DIP de CoreWindow a valores enteros de píxeles antes de pasar estos valores a Direct3D para configurar la cadena de intercambio.

En proceso, estás haciendo un poco más de trabajo de lo que harías si simplemente cambiaste el tamaño de la cadena de intercambio: estás girando realmente los componentes Direct2D y Direct3D de la imagen antes de componerlos para su presentación, y estás indicando a la cadena de intercambio que has representado los resultados en una nueva orientación. Este es un poco más detallado sobre este proceso, como se muestra en el ejemplo de código para DX::D eviceResources::CreateWindowSizeDependentResources:

  • Determine la nueva orientación de la pantalla. Si la pantalla se ha volteado de horizontal a vertical, o viceversa, cambie los valores de alto y ancho ,cambiados de valores DIP a píxeles, por supuesto, para los límites de visualización.

  • A continuación, compruebe si se ha creado la cadena de intercambio. Si no se ha creado, créelo llamando a IDXGIFactory2::CreateSwapChainForCoreWindow. De lo contrario, cambie el tamaño de los búferes de la cadena de intercambio existente a las nuevas dimensiones de visualización llamando a IDXGISwapchain:ResizeBuffers. Aunque no es necesario cambiar el tamaño de la cadena de intercambio para el evento de rotación, la salida del contenido ya girado por la canalización de representación, después de todo, hay otros eventos de cambio de tamaño, como eventos de ajuste y relleno, que requieren cambiar el tamaño.

  • Después, establezca la transformación de matriz 2D o 3D adecuada para aplicarla a los píxeles o los vértices (respectivamente) en la canalización de gráficos al representarlos en la cadena de intercambio. Tenemos cuatro matrices de rotación posibles:

    • horizontal (DXGI_MODE_ROTATION_IDENTITY)
    • vertical (DXGI_MODE_ROTATION_ROTATE270)
    • horizontal, volteado (DXGI_MODE_ROTATION_ROTATE180)
    • vertical, volteado (DXGI_MODE_ROTATION_ROTATE90)

    La matriz correcta se selecciona en función de los datos proporcionados por Windows 10 (como los resultados de DisplayInformation::OrientationChanged) para determinar la orientación de la pantalla, y se multiplicará por las coordenadas de cada píxel (Direct2D) o vértice (Direct3D) en la escena, girando eficazmente para alinearse con la orientación de la pantalla. (Tenga en cuenta que en Direct2D, el origen de la pantalla se define como la esquina superior izquierda, mientras que en Direct3D el origen se define como el centro lógico de la ventana).

Nota Para obtener más información sobre las transformaciones 2D que se usan para la rotación y cómo definirlas, consulte Definición de matrices para la rotación de pantalla (2D). Para obtener más información sobre las transformaciones 3D usadas para la rotación, consulte Definición de matrices para la rotación de pantalla (3D).

 

Ahora, este es el bit importante: llame a IDXGISwapChain1::SetRotation y proporcione la matriz de rotación actualizada, como esta:

m_swapChain->SetRotation(rotation);

También almacena la matriz de rotación seleccionada donde el método render puede obtenerla cuando calcula la nueva proyección. Usará esta matriz cuando represente la proyección 3D final o componga el diseño final 2D. (No la aplica automáticamente).

Después, cree un nuevo destino de representación para la vista 3D girada, así como un nuevo búfer de galería de símbolos de profundidad para la vista. Establezca la ventanilla de representación 3D para la escena girada llamando a ID3D11DeviceContext:RSSetViewports.

Por último, si tiene imágenes 2D para girar o diseñar, cree un destino de representación 2D como un mapa de bits grabable para la cadena de intercambio cambiado con ID2D1DeviceContext::CreateBitmapFromDxgiSurface y componga el nuevo diseño para la orientación actualizada. Establezca las propiedades que necesite en el destino de representación, como el modo de suavizado de alias (como se muestra en el ejemplo de código).

Ahora, presente la cadena de intercambio.

Reducir el retraso de rotación mediante CoreWindowResizeManager

De forma predeterminada, Windows 10 proporciona una ventana de tiempo breve pero notable para cualquier aplicación, independientemente del modelo o el lenguaje de la aplicación, para completar la rotación de la imagen. Sin embargo, es probable que cuando la aplicación realice el cálculo de rotación mediante una de las técnicas descritas aquí, se hará bien antes de que se haya cerrado este período de tiempo. Te gustaría recuperar ese tiempo y completar la animación de rotación, ¿verdad? Ahí es donde entra coreWindowResizeManager.

Aquí se muestra cómo usar CoreWindowResizeManager: cuando se genera un evento DisplayInformation::OrientationChanged, llame a CoreWindowResizeManager::GetForCurrentView dentro del controlador para que el evento obtenga una instancia de CoreWindowResizeManager y, cuando se complete y presente el diseño de la nueva orientación, llame a NotifyLayoutCompleted. para informar a Windows de que puede completar la animación de rotación y mostrar la pantalla de la aplicación.

Este es el aspecto que podría tener el código del controlador de eventos para DisplayInformation::OrientationChanged :

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

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

resizeManager->NotifyLayoutCompleted();

Cuando un usuario gira la orientación de la pantalla, Windows 10 muestra una animación independiente de la aplicación como comentarios al usuario. Hay tres partes en esa animación que se producen en el orden siguiente:

  • Windows 10 reduce la imagen original.
  • Windows 10 contiene la imagen durante el tiempo necesario para volver a generar el nuevo diseño. Este es el período de tiempo que le gustaría reducir, ya que la aplicación probablemente no necesita todo esto.
  • Cuando expira la ventana de diseño o cuando se recibe una notificación de finalización del diseño, Windows gira la imagen y, a continuación, reduce los zooms de atenuación cruzada a la nueva orientación.

Como se sugiere en la tercera viñeta, cuando una aplicación llama a NotifyLayoutCompleted, Windows 10 detiene la ventana de tiempo de espera, completa la animación de rotación y devuelve el control a la aplicación, que ahora está dibujando en la nueva orientación de pantalla. El efecto general es que la aplicación ahora se siente un poco más fluida y con capacidad de respuesta, y funciona un poco más eficazmente!

Apéndice A: Aplicación de matrices para la rotación de pantalla (2D)

En el código de ejemplo de Cambio de tamaño de la cadena de intercambio y la rotación previa de su contenido (y en el ejemplo de rotación de la cadena de intercambio DXGI), es posible que haya observado que teníamos matrices de rotación independientes para la salida de Direct2D y la salida de Direct3D. Echemos un vistazo a las matrices 2D, primero.

Hay dos motivos por los que no se pueden aplicar las mismas matrices de rotación al contenido de Direct2D y Direct3D:

  • Uno, usan diferentes modelos de coordenadas cartesianos. Direct2D usa la regla derecha, donde la coordenada y aumenta en el valor positivo que se mueve hacia arriba desde el origen. Sin embargo, Direct3D usa la regla izquierda, donde la coordenada y aumenta en el valor positivo hacia la derecha desde el origen. El resultado es el origen de las coordenadas de pantalla se encuentra en la parte superior izquierda de Direct2D, mientras que el origen de la pantalla (el plano de proyección) está en la parte inferior izquierda de Direct3D. (Consulte sistemas de coordenadas 3D para obtener más información).

    sistema de coordenadas direct3d.sistema de coordenadas direct2d.

  • Dos, las matrices de rotación 3D deben especificarse explícitamente para evitar errores de redondeo.

La cadena de intercambio supone que el origen se encuentra en la parte inferior izquierda, por lo que debe realizar una rotación para alinear el sistema de coordenadas Direct2D de mano derecha con el izquierdo utilizado por la cadena de intercambio. En concreto, cambia la posición de la imagen bajo la nueva orientación izquierda multiplicando la matriz de rotación con una matriz de traducción para el origen del sistema de coordenadas girado y transforma la imagen desde el espacio de coordenadas de CoreWindow al espacio de coordenadas de la cadena de intercambio. La aplicación también debe aplicar de forma coherente esta transformación cuando el destino de representación de Direct2D está conectado con la cadena de intercambio. Sin embargo, si la aplicación está dibujando en superficies intermedias que no están asociadas directamente con la cadena de intercambio, no aplique esta transformación del espacio de coordenadas.

El código para seleccionar la matriz correcta de las cuatro rotaciones posibles podría tener este aspecto (tenga en cuenta la traducción al nuevo origen del 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();
}
    

Después de tener la matriz de rotación y el origen correctos para la imagen 2D, establézcalo con una llamada a ID2D1DeviceContext::SetTransform entre las llamadas a ID2D1DeviceContext::BeginDraw e ID2D1DeviceContext::EndDraw.

Advertencia Direct2D no tiene una pila de transformación. Si la aplicación también usa ID2D1DeviceContext ::SetTransform como parte de su código de dibujo, esta matriz debe multiplicarse después de cualquier otra transformación que haya 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();

La próxima vez que presente la cadena de intercambio, la imagen 2D se rotará para que coincida con la nueva orientación de visualización.

Apéndice B: Aplicar matrices para la rotación de pantalla (3D)

En el código de ejemplo de Cambio de tamaño de la cadena de intercambio y la rotación previa de su contenido (y en el ejemplo de rotación de la cadena de intercambio DXGI), definimos una matriz de transformación específica para cada orientación de pantalla posible. Ahora, echemos un vistazo a las matrices para girar escenas 3D. Como antes, se crea un conjunto de matrices para cada una de las 4 orientaciones posibles. Para evitar errores de redondeo y, por tanto, artefactos visuales menores, declare las matrices explícitamente en el código.

Estas matrices de rotación 3D se configuran de la manera siguiente. Las matrices que se muestran en el siguiente ejemplo de código son matrices de rotación estándar para 0, 90, 180 y 270 grados de rotación de los vértices que definen puntos en el espacio de la escena 3D de la cámara. El valor de coordenada de cada vértice [x, y, z] de la escena se multiplica por esta matriz de rotación cuando se calcula la proyección 2D de la escena.

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

Establezca el tipo de rotación en la cadena de intercambio con una llamada a IDXGISwapChain1::SetRotation, de la siguiente manera:

m_swapChain->SetRotation(rotation);

Ahora, en el método render, implemente código similar al siguiente:

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

Ahora, al llamar al método render, multiplica la matriz de rotación actual (según lo especificado por la variable de clase m_orientationTransform3D) con la matriz de proyección actual y asigna los resultados de esa operación como la nueva matriz de proyección para el representador. Presente la cadena de intercambio para ver la escena en la orientación de visualización actualizada.