Orientasi layar pendukung (DirectX dan C++)

Aplikasi Universal Windows Platform (UWP) Anda dapat mendukung beberapa orientasi layar saat Anda menangani peristiwa DisplayInformation::OrientationChanged. Di sini, kita akan membahas praktik terbaik untuk menangani rotasi layar di aplikasi UWP DirectX Anda, sehingga perangkat keras grafis perangkat Windows 10 digunakan secara efisien dan efektif.

Sebelum memulai, ingatlah bahwa perangkat keras grafis selalu menghasilkan data piksel dengan cara yang sama, terlepas dari orientasi perangkat. Windows 10 perangkat dapat menentukan orientasi tampilan mereka saat ini (dengan semacam sensor, atau dengan tombol perangkat lunak) dan memungkinkan pengguna untuk mengubah pengaturan tampilan. Karena itu, Windows 10 sendiri menangani rotasi gambar untuk memastikan gambar "tegak" berdasarkan orientasi perangkat. Secara default, aplikasi Anda menerima pemberitahuan bahwa sesuatu telah berubah dalam orientasi, misalnya, ukuran jendela. Ketika ini terjadi, Windows 10 segera memutar gambar untuk tampilan akhir. Untuk tiga dari empat orientasi layar tertentu (dibahas nanti), Windows 10 menggunakan sumber daya grafis dan komputasi tambahan untuk menampilkan gambar akhir.

Untuk aplikasi DirectX UWP, objek DisplayInformation menyediakan data orientasi tampilan dasar yang dapat dikueri aplikasi Anda. Orientasi default adalah lanskap, di mana lebar piksel tampilan lebih besar dari tinggi; orientasi alternatif adalah potret, di mana tampilan diputar 90 derajat di kedua arah dan lebar menjadi kurang dari tinggi.

Windows 10 mendefinisikan empat mode orientasi tampilan tertentu:

  • Lanskap—orientasi tampilan default untuk Windows 10, dan dianggap sebagai sudut dasar atau identitas untuk rotasi (0 derajat).
  • Potret—layar telah diputar searah jaring jam 90 derajat (atau berlawanan arah jaring 270 derajat).
  • Lanskap, dibalik—layar telah diputar 180 derajat (terbalik).
  • Potret, dibalik—layar telah diputar searah jaga 270 derajat (atau berlawanan arah jaring jam 90 derajat).

Saat tampilan berputar dari satu orientasi ke orientasi lainnya, Windows 10 secara internal melakukan operasi rotasi untuk menyelaraskan gambar yang digambar dengan orientasi baru, dan pengguna melihat gambar tegak di layar.

Selain itu, Windows 10 menampilkan animasi transisi otomatis untuk menciptakan pengalaman pengguna yang lancar saat beralih dari satu orientasi ke orientasi lainnya. Saat orientasi tampilan bergeser, pengguna melihat pergeseran ini sebagai zoom tetap dan animasi rotasi gambar layar yang ditampilkan. Waktu dialokasikan oleh Windows 10 ke aplikasi untuk tata letak dalam orientasi baru.

Secara keseluruhan, ini adalah proses umum untuk menangani perubahan orientasi layar:

  1. Gunakan kombinasi nilai batas jendela dan data orientasi tampilan untuk menjaga rantai pertukaran selaras dengan orientasi tampilan asli perangkat.
  2. Beri tahu Windows 10 orientasi rantai pertukaran menggunakan IDXGISwapChain1::SetRotation.
  3. Ubah kode penyajian untuk menghasilkan gambar yang selaras dengan orientasi pengguna perangkat.

Mengubah ukuran rantai pertukaran dan melakukan pra-rotasi kontennya

Untuk melakukan pengubahan ukuran tampilan dasar dan melakukan pra-putar kontennya di aplikasi DirectX UWP Anda, terapkan langkah-langkah berikut:

  1. Tangani peristiwa DisplayInformation::OrientationChanged .
  2. Mengubah ukuran rantai pertukaran ke dimensi baru jendela.
  3. Panggil IDXGISwapChain1::SetRotation untuk mengatur orientasi rantai pertukaran.
  4. Buat ulang sumber daya dependen ukuran jendela apa pun, seperti target render Anda dan buffer data piksel lainnya.

Sekarang mari kita lihat langkah-langkah tersebut secara lebih rinci.

Langkah pertama Anda adalah mendaftarkan handler untuk peristiwa DisplayInformation::OrientationChanged . Kejadian ini dimunculkan di aplikasi Anda setiap kali orientasi layar berubah, seperti saat tampilan diputar.

Untuk menangani peristiwa DisplayInformation::OrientationChanged , Anda menyambungkan handler untuk DisplayInformation::OrientationChanged dalam metode SetWindow yang diperlukan, yang merupakan salah satu metode antarmuka IFrameworkView yang harus diterapkan penyedia tampilan Anda.

Dalam contoh kode ini, penanganan aktivitas untuk DisplayInformation::OrientationChanged adalah metode yang disebut OnOrientationChanged. Ketika DisplayInformation::OrientationChanged dimunculkan, itu pada gilirannya memanggil metode yang disebut SetCurrentOrientation yang kemudian memanggil 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();
    }
}

Selanjutnya, Anda mengubah ukuran rantai pertukaran untuk orientasi layar baru dan menyiapkannya untuk memutar konten alur grafis saat penyajian dilakukan. Dalam contoh ini, DirectXBase::CreateWindowSizeDependentResources adalah metode yang menangani panggilan IDXGISwapChain::ResizeBuffers, mengatur matriks rotasi 3D dan 2D, memanggil SetRotation, dan membuat ulang sumber daya Anda.

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

}

Setelah menyimpan nilai tinggi dan lebar jendela saat ini untuk kali berikutnya metode ini dipanggil, konversikan nilai piksel independen perangkat (DIP) untuk batas tampilan ke piksel. Dalam sampel, Anda memanggil ConvertDipsToPixels, yang merupakan fungsi sederhana yang menjalankan kode ini:

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

Anda menambahkan 0,5f untuk memastikan pembulatan ke nilai bilangan bulat terdekat.

Selain itu, koordinat CoreWindow selalu didefinisikan dalam DIP. Untuk Windows 10 dan versi Windows yang lebih lama, DIP didefinisikan sebagai 1/96 inci, dan diselaraskan dengan definisi OS naik. Saat orientasi tampilan berputar ke mode potret, aplikasi membalik lebar dan tinggi CoreWindow, dan ukuran target render (batas) harus berubah sesuai. Karena koordinat Direct3D selalu dalam piksel fisik, Anda harus mengonversi dari nilai DIP CoreWindow menjadi nilai piksel bilangan bulat sebelum Anda meneruskan nilai-nilai ini ke Direct3D untuk menyiapkan rantai pertukaran.

Proses-bijaksana, Anda melakukan sedikit lebih banyak pekerjaan daripada yang Anda lakukan jika Anda hanya mengubah ukuran rantai pertukaran: Anda benar-benar memutar komponen Direct2D dan Direct3D gambar Anda sebelum Anda menyusunnya untuk presentasi, dan Anda memberi tahu rantai pertukaran bahwa Anda telah merender hasilnya dalam orientasi baru. Berikut adalah sedikit detail lebih lanjut tentang proses ini, seperti yang ditunjukkan dalam contoh kode untuk DX::D eviceResources::CreateWindowSizeDependentResources:

  • Tentukan orientasi baru tampilan. Jika tampilan telah dibalik dari lanskap ke potret, atau sebaliknya, tukar nilai tinggi dan lebar—berubah dari nilai DIP menjadi piksel, tentu saja—untuk batas tampilan.

  • Kemudian, periksa untuk melihat apakah rantai pertukaran telah dibuat. Jika belum dibuat, buat dengan memanggil IDXGIFactory2::CreateSwapChainForCoreWindow. Jika tidak, mengubah ukuran buffer swap chain yang ada ke dimensi tampilan baru dengan memanggil IDXGISwapchain:ResizeBuffers. Meskipun Anda tidak perlu mengubah ukuran rantai pertukaran untuk peristiwa rotasi—Anda mengeluarkan konten yang sudah diputar oleh alur penyajian Anda, bagaimanapun—ada peristiwa perubahan ukuran lainnya, seperti peristiwa snap dan fill, yang memerlukan pengubahan ukuran.

  • Setelah itu, atur transformasi matriks 2-D atau 3-D yang sesuai untuk diterapkan ke piksel atau simpul (masing-masing) dalam alur grafis saat merendernya ke rantai pertukaran. Kami memiliki 4 kemungkinan matriks rotasi:

    • lanskap (DXGI_MODE_ROTATION_IDENTITY)
    • potret (DXGI_MODE_ROTATION_ROTATE270)
    • lanskap, terbalik (DXGI_MODE_ROTATION_ROTATE180)
    • potret, dibalik (DXGI_MODE_ROTATION_ROTATE90)

    Matriks yang benar dipilih berdasarkan data yang disediakan oleh Windows 10 (seperti hasil DisplayInformation::OrientationChanged) untuk menentukan orientasi tampilan, dan akan dikalikan dengan koordinat setiap piksel (Direct2D) atau vertex (Direct3D) dalam adegan, secara efektif memutarnya agar selaras dengan orientasi layar. (Perhatikan bahwa di Direct2D, asal layar didefinisikan sebagai sudut kiri atas, sementara di Direct3D asal didefinisikan sebagai pusat logis jendela.)

Catatan Untuk informasi selengkapnya tentang transformasi 2-D yang digunakan untuk rotasi dan cara menentukannya, lihat Menentukan matriks untuk rotasi layar (2-D). Untuk informasi selengkapnya tentang transformasi 3-D yang digunakan untuk rotasi, lihat Menentukan matriks untuk rotasi layar (3-D).

 

Sekarang, inilah bit pentingnya: panggil IDXGISwapChain1::SetRotation dan berikan dengan matriks rotasi Anda yang diperbarui, seperti ini:

m_swapChain->SetRotation(rotation);

Anda juga menyimpan matriks rotasi yang dipilih di mana metode render Anda bisa mendapatkannya saat menghitung proyeksi baru. Anda akan menggunakan matriks ini saat merender proyeksi 3-D akhir atau menyusun tata letak 2-D akhir Anda. (Ini tidak secara otomatis menerapkannya untuk Anda.)

Setelah itu, buat target render baru untuk tampilan 3-D yang diputar, serta buffer stensil kedalaman baru untuk tampilan. Atur viewport rendering 3-D untuk adegan yang diputar dengan memanggil ID3D11DeviceContext:RSSetViewports.

Terakhir, jika Anda memiliki gambar 2-D untuk diputar atau ditata, buat target render 2-D sebagai bitmap yang dapat ditulis untuk rantai pertukaran ukuran menggunakan ID2D1DeviceContext::CreateBitmapFromDxgiSurface dan komposit tata letak baru Anda untuk orientasi yang diperbarui. Atur properti apa pun yang Anda butuhkan pada target render, seperti mode anti-aliasing (seperti yang terlihat dalam contoh kode).

Sekarang, sajikan rantai pertukaran.

Mengurangi penundaan rotasi dengan menggunakan CoreWindowResizeManager

Secara default, Windows 10 menyediakan jendela waktu yang singkat tetapi nyata untuk aplikasi apa pun, terlepas dari model atau bahasa aplikasi, untuk menyelesaikan rotasi gambar. Namun, kemungkinannya adalah ketika aplikasi Anda melakukan perhitungan rotasi menggunakan salah satu teknik yang dijelaskan di sini, itu akan dilakukan dengan baik sebelum jendela waktu ini ditutup. Anda ingin mendapatkan waktu kembali dan menyelesaikan animasi rotasi, kan? Di situlah CoreWindowResizeManager masuk.

Berikut cara menggunakan CoreWindowResizeManager: ketika peristiwa DisplayInformation::OrientationChanged dinaikkan, panggil CoreWindowResizeManager::GetForCurrentView dalam handler untuk peristiwa guna mendapatkan instans CoreWindowResizeManager dan, ketika tata letak untuk orientasi baru selesai dan disajikan, panggil NotifyLayoutCompleted untuk membiarkan Windows mengetahui bahwa ia dapat menyelesaikan animasi rotasi dan menampilkan layar aplikasi.

Berikut adalah tampilan kode dalam penanganan aktivitas Anda untuk DisplayInformation::OrientationChanged :

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

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

resizeManager->NotifyLayoutCompleted();

Saat pengguna memutar orientasi tampilan, Windows 10 menampilkan animasi yang independen dari aplikasi Anda sebagai umpan balik kepada pengguna. Ada tiga bagian untuk animasi yang terjadi dalam urutan berikut:

  • Windows 10 menyusutkan gambar asli.
  • Windows 10 menyimpan gambar untuk waktu yang diperlukan untuk membangun kembali tata letak baru. Ini adalah jendela waktu yang ingin Anda kurangi, karena aplikasi Anda mungkin tidak memerlukan semuanya.
  • Saat jendela tata letak kedaluwarsa, atau saat pemberitahuan penyelesaian tata letak diterima, Windows memutar gambar lalu memudar silang zoom ke orientasi baru.

Seperti yang disarankan dalam poin ketiga, saat aplikasi memanggil NotifyLayoutCompleted, Windows 10 menghentikan jendela batas waktu, menyelesaikan animasi rotasi dan mengembalikan kontrol ke aplikasi Anda, yang sekarang menggambar dalam orientasi tampilan baru. Efek keseluruhannya adalah bahwa aplikasi Anda sekarang terasa sedikit lebih cairan dan responsif, dan bekerja sedikit lebih efisien!

Lampiran A: Menerapkan matriks untuk rotasi layar (2-D)

Dalam contoh kode dalam Mengubah ukuran rantai swap dan melakukan pra-rotasi kontennya (dan dalam sampel rotasi rantai pertukaran DXGI), Anda mungkin telah memperhatikan bahwa kami memiliki matriks rotasi terpisah untuk output Direct2D dan output Direct3D. Mari kita lihat matriks 2-D, pertama.

Ada dua alasan kami tidak dapat menerapkan matriks rotasi yang sama ke konten Direct2D dan Direct3D:

  • Satu, mereka menggunakan model koordinat Kartesius yang berbeda. Direct2D menggunakan aturan tangan kanan, di mana koordinat y meningkat dalam nilai positif bergerak ke atas dari asal. Namun, Direct3D menggunakan aturan sebelah kiri, di mana koordinat y meningkat dalam nilai positif ke kanan dari asal. Hasilnya adalah asal koordinat layar terletak di kiri atas untuk Direct2D, sementara asal untuk layar (bidang proyeksi) berada di kiri bawah untuk Direct3D. (Lihat sistem koordinat 3-D untuk informasi selengkapnya.)

    direct3d coordinate system.direct2d coordinate system.

  • Dua, matriks rotasi 3-D harus ditentukan secara eksplisit untuk menghindari kesalahan pembulatan.

Rantai pertukaran mengasumsikan bahwa asal terletak di kiri bawah, jadi Anda harus melakukan rotasi untuk menyelaraskan sistem koordinat Direct2D dengan tangan kiri yang digunakan oleh rantai pertukaran. Secara khusus, Anda memposisikan ulang gambar di bawah orientasi sebelah kiri baru dengan mengalikan matriks rotasi dengan matriks terjemahan untuk asal sistem koordinat yang diputar, dan mengubah gambar dari ruang koordinat CoreWindow ke ruang koordinat rantai pertukaran. Aplikasi Anda juga harus secara konsisten menerapkan transformasi ini saat target render Direct2D terhubung dengan rantai pertukaran. Namun, jika aplikasi Anda menggambar permukaan perantara yang tidak terkait langsung dengan rantai pertukaran, jangan terapkan transformasi ruang koordinat ini.

Kode Anda untuk memilih matriks yang benar dari empat rotasi yang mungkin terlihat seperti ini (ketahui terjemahan ke asal sistem koordinat baru):

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

Setelah Anda memiliki matriks rotasi dan asal yang benar untuk gambar 2-D, atur dengan panggilan ke ID2D1DeviceContext::SetTransform antara panggilan Anda ke ID2D1DeviceContext::BeginDraw dan ID2D1DeviceContext::EndDraw.

Peringatan Direct2D tidak memiliki tumpukan transformasi. Jika aplikasi Anda juga menggunakan ID2D1DeviceContext::SetTransform sebagai bagian dari kode gambarnya, matriks ini perlu dikalikan pasca ke transformasi lain yang telah Anda terapkan.

 

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

Saat berikutnya Anda menyajikan rantai pertukaran, gambar 2-D Anda akan diputar agar sesuai dengan orientasi tampilan baru.

Lampiran B: Menerapkan matriks untuk rotasi layar (3-D)

Dalam kode contoh dalam Mengubah ukuran rantai swap dan memutar kontennya terlebih dahulu (dan dalam sampel rotasi rantai pertukaran DXGI), kami menentukan matriks transformasi tertentu untuk setiap orientasi layar yang mungkin. Sekarang, mari kita lihat matriks untuk memutar adegan 3-D. Seperti sebelumnya, Anda membuat sekumpulan matriks untuk masing-masing dari 4 orientasi yang mungkin. Untuk mencegah kesalahan pembulatan dan dengan demikian artefak visual kecil, deklarasikan matriks secara eksplisit dalam kode Anda.

Anda menyiapkan matriks rotasi 3-D ini sebagai berikut. Matriks yang ditunjukkan dalam contoh kode berikut adalah matriks rotasi standar untuk rotasi 0, 90, 180, dan 270 derajat dari simpul yang menentukan titik di ruang adegan 3-D kamera. Setiap nilai koordinat [x, y, z] vertex dalam adegan dikalikan dengan matriks rotasi ini ketika proyeksi 2-D adegan dihitung.

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

Anda mengatur jenis rotasi pada rantai pertukaran dengan panggilan ke IDXGISwapChain1::SetRotation, seperti ini:

m_swapChain->SetRotation(rotation);

Sekarang, dalam metode render Anda, terapkan beberapa kode yang mirip dengan ini:

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

Sekarang, ketika Anda memanggil metode render Anda, metode ini mengalikan matriks rotasi saat ini (seperti yang ditentukan oleh variabel kelas m_orientationTransform3D) dengan matriks proyeksi saat ini, dan menetapkan hasil operasi tersebut sebagai matriks proyeksi baru untuk perender Anda. Sajikan rantai pertukaran untuk melihat adegan dalam orientasi tampilan yang diperbarui.