Interop DirectX dan XAML

Anda dapat menggunakan Extensible Application Markup Language (XAML) bersama dengan Microsoft DirectX bersama-sama dalam game atau aplikasi Universal Windows Platform (UWP). Kombinasi XAML dan DirectX memungkinkan Anda membangun kerangka kerja antarmuka pengguna fleksibel yang beroperasi dengan konten yang dirender DirectX Anda; yang sangat berguna untuk aplikasi intensif grafis. Topik ini menjelaskan struktur aplikasi UWP yang menggunakan DirectX, dan mengidentifikasi jenis penting yang akan digunakan saat membangun aplikasi UWP Anda untuk bekerja dengan DirectX.

Jika aplikasi Anda terutama berfokus pada penyajian 2D, maka Anda mungkin ingin menggunakan pustaka Win2D Windows Runtime. Pustaka tersebut dikelola oleh Microsoft, dan dibangun di atas teknologi Direct2D inti. Win2D sangat menyederhanakan pola penggunaan untuk mengimplementasikan grafik 2D, dan mencakup abstraksi yang bermanfaat untuk beberapa teknik yang dijelaskan dalam dokumen ini. Lihat halaman proyek untuk detail selengkapnya. Dokumen ini mencakup panduan untuk pengembang aplikasi yang memilih untuk tidak menggunakan Win2D.

Catatan

API DirectX tidak didefinisikan sebagai Windows Jenis runtime, tetapi Anda dapat menggunakan C++/WinRT untuk mengembangkan aplikasi XAML UWP yang beroperasi dengan DirectX. Jika Anda memperhitungkan kode yang memanggil DirectX ke komponen C++/WinRT Windows Runtime (WRC) sendiri, maka Anda dapat menggunakan WRC tersebut di aplikasi UWP (bahkan C# satu) yang kemudian menggabungkan XAML dan DirectX.

XAML dan DirectX

DirectX menyediakan dua pustaka yang kuat untuk grafik 2D dan 3D, masing-masing—Direct2D dan Direct3D. Meskipun XAML menyediakan dukungan untuk primitif dan efek 2D dasar, banyak aplikasi pemodelan dan game membutuhkan dukungan grafis yang lebih kompleks. Untuk ini, Anda dapat menggunakan Direct2D dan Direct3D untuk merender grafik yang lebih kompleks, dan menggunakan XAML untuk elemen antarmuka pengguna (UI) yang lebih tradisional.

Jika Anda menerapkan interop XAML dan DirectX kustom, maka Anda perlu mengetahui dua konsep ini.

  • Permukaan bersama adalah wilayah berukuran tampilan, yang ditentukan oleh XAML, yang dapat Anda gunakan DirectX untuk menggambar secara tidak langsung dengan menggunakan jenis Windows::UI::Xaml::Media::ImageSource. Untuk permukaan bersama, Anda tidak mengontrol waktu yang tepat saat konten baru muncul di layar. Sebaliknya, pembaruan pada permukaan bersama akan disinkronkan ke pembaruan kerangka kerja XAML.
  • Rantai pertukaran mewakili kumpulan buffer yang digunakan untuk menampilkan grafik dengan latensi minimal. Biasanya, rantai pertukaran diperbarui pada 60 bingkai per detik secara terpisah dari utas UI. Namun, rantai pertukaran menggunakan lebih banyak memori dan sumber daya CPU untuk mendukung pembaruan yang cepat, dan relatif sulit digunakan, karena Anda harus mengelola beberapa utas.

Pertimbangkan untuk apa Anda menggunakan DirectX. Apakah Anda akan menggunakannya untuk menyusun atau menganimasikan kontrol tunggal yang pas dalam dimensi jendela tampilan? Apakah akan berisi output yang perlu dirender dan dikontrol secara real-time, seperti dalam game? Jika demikian, maka Anda mungkin perlu menerapkan rantai pertukaran. Jika tidak, Anda akan baik-baik saja menggunakan permukaan bersama.

Setelah menentukan bagaimana Anda berniat menggunakan DirectX, Anda menggunakan salah satu jenis Windows Runtime berikut untuk menggabungkan penyajian DirectX ke dalam aplikasi UWP Anda.

  • Jika Anda ingin membuat gambar statis, atau menggambar gambar kompleks pada interval berbasis peristiwa, maka gambar ke permukaan bersama dengan Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Jenis itu menangani permukaan gambar DirectX berukuran. Biasanya, Anda menggunakan jenis ini saat menyusun gambar atau tekstur sebagai bitmap untuk ditampilkan dalam dokumen atau elemen UI. Ini tidak berfungsi dengan baik untuk interaktivitas real-time, seperti game berkinerja tinggi. Itu karena pembaruan pada objek SurfaceImageSource disinkronkan ke pembaruan antarmuka pengguna XAML, dan yang dapat memperkenalkan latensi ke dalam umpan balik visual yang Anda berikan kepada pengguna, seperti kecepatan bingkai yang berfluktuasi, atau respons buruk yang dirasakan terhadap input real-time. Pembaruan masih cukup cepat untuk kontrol dinamis atau simulasi data.
  • Jika gambar lebih besar dari real estat layar yang disediakan, dan dapat digerakkan atau diperbesar oleh pengguna, maka gunakan Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Jenis itu menangani permukaan gambar DirectX berukuran lebih besar dari layar. Seperti SurfaceImageSource, Anda menggunakan ini saat menyusun gambar atau kontrol yang kompleks secara dinamis. Dan, juga seperti SurfaceImageSource, itu tidak berfungsi dengan baik untuk game berkinerja tinggi. Beberapa contoh elemen XAML yang dapat menggunakan VirtualSurfaceImageSource adalah kontrol peta, atau penampil dokumen besar yang padat gambar.
  • Jika Anda menggunakan DirectX untuk menyajikan grafik yang diperbarui secara real-time, atau dalam situasi di mana pembaruan harus datang pada interval latensi rendah reguler, maka gunakan kelas SwapChainPanel , sehingga Anda dapat menyegarkan grafik tanpa menyinkronkan ke timer refresh kerangka kerja XAML. Dengan SwapChainPanel Anda dapat mengakses rantai pertukaran perangkat grafis (IDXGISwapChain1) secara langsung, dan melapisi XAML di atas target render. SwapChainPanel berfungsi sangat baik untuk game dan aplikasi DirectX layar penuh yang memerlukan antarmuka pengguna berbasis XAML. Anda harus mengenal DirectX dengan baik untuk menggunakan pendekatan ini, termasuk teknologi Microsoft DirectX Graphics Infrastructure (DXGI), Direct2D, dan Direct3D. Untuk informasi selengkapnya, lihat Panduan Pemrograman untuk Direct3D 11.

SurfaceImageSource

SurfaceImageSource memberi Anda permukaan bersama DirectX untuk digambar; kemudian menyusun bit ke dalam konten aplikasi.

Tip

Aplikasi sampel Penspasian baris (DirectWrite) dan font yang dapat diunduh (DirectWrite) menunjukkan SurfaceImageSource.

Pada tingkat yang sangat tinggi, berikut adalah proses untuk membuat dan memperbarui SurfaceImageSource.

  • Buat perangkat Direct 3D, perangkat Direct 2D, dan konteks perangkat Direct 2D.
  • Buat SurfaceImageSource, dan atur perangkat Direct 2D (atau Direct 3D) pada perangkat tersebut.
  • Mulai menggambar di SurfaceImageSource untuk mendapatkan permukaan DXGI.
  • Gambar ke permukaan DXGI dengan Direct2D (atau Direct3D).
  • Akhiri menggambar di SurfaceImageSource setelah selesai.
  • Atur SurfaceImageSource pada Gambar XAML atau ImageBrush untuk menampilkannya di UI XAML Anda.

Dan berikut adalah penyelaman yang lebih mendalam tentang langkah-langkah tersebut, dengan contoh kode sumber.

  1. Anda dapat mengikuti kode yang ditampilkan dan dijelaskan di bawah ini dengan membuat proyek baru di Microsoft Visual Studio. Buat proyek Aplikasi Kosong (C++/WinRT). Targetkan versi terbaru yang tersedia secara umum (yaitu, bukan pratinjau) dari SDK Windows.

  2. Buka pch.h, dan tambahkan yang berikut ini termasuk yang sudah ada di bawah.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. Tambahkan direktif yang using ditunjukkan di bawah ini ke bagian MainPage.cppatas , di bawah yang sudah ada. Juga dalam MainPage.cpp, ganti implementasi MainPage::ClickHandler yang ada dengan daftar yang ditunjukkan di bawah ini. Kode ini membuat perangkat Direct 3D, perangkat Direct 2D, dan konteks perangkat Direct 2D. Untuk melakukannya, ia memanggil D3D11CreateDevice, D2D1CreateDevice, dan ID2D1Device::CreateDeviceContext.

    // MainPage.cpp | paste this below the existing using directives
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    // MainPage.cpp | paste this to replace the existing MainPage::ClickHandler
    void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
    {
        myButton().Content(box_value(L"Clicked"));
    
        uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1
        };
    
        // Create the Direct3D device.
        winrt::com_ptr<::ID3D11Device> d3dDevice;
        D3D_FEATURE_LEVEL supportedFeatureLevel;
        winrt::check_hresult(::D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            d3dDevice.put(),
            &supportedFeatureLevel,
            nullptr)
        );
    
        // Get the DXGI device.
        winrt::com_ptr<::IDXGIDevice> dxgiDevice{
            d3dDevice.as<::IDXGIDevice>() };
    
        // Create the Direct2D device and a corresponding context.
        winrt::com_ptr<::ID2D1Device> d2dDevice;
        ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
        winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
        winrt::check_hresult(
            d2dDevice->CreateDeviceContext(
                D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                d2dDeviceContext.put()
            )
        );
    }
    
  4. Selanjutnya, tambahkan kode untuk membuat SurfaceImageSource, dan atur perangkat Direct 2D (atau Direct 3D) dengan memanggil ISurfaceImageSourceNativeWithD2D::SetDevice.

    Catatan

    Jika Anda akan menggambar ke SurfaceImageSource dari utas latar belakang, maka Anda juga harus memastikan bahwa perangkat DXGI mengaktifkan akses multi-utas (seperti yang ditunjukkan pada kode di bawah). Untuk alasan performa, Anda harus melakukannya hanya jika Anda akan menggambar dari utas latar belakang.

    Tentukan ukuran permukaan bersama dengan melewati tinggi dan lebar ke konstruktor SurfaceImageSource . Anda juga dapat menunjukkan apakah permukaan membutuhkan dukungan alfa (oacity).

    Untuk mengatur perangkat, dan menjalankan operasi gambar, kita memerlukan penunjuk ke ISurfaceImageSourceNativeWithD2D. Untuk mendapatkannya, kueri objek SurfaceImageSource untuk antarmuka ISurfaceImageSourceNativeWithD2D yang mendasar .

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    SurfaceImageSource surfaceImageSource(500, 500);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        surfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
  5. Panggil ISurfaceImageSourceNativeWithD2D::BeginDraw untuk mengambil permukaan DXGI (antarmuka IDXGISurface ). Anda dapat memanggil ISurfaceImageSourceNativeWithD2D::BeginDraw (dan perintah gambar selanjutnya) dari utas latar belakang jika Anda telah mengaktifkan akses multi-utas. Dalam langkah ini Anda juga membuat bitmap dari permukaan DXGI, dan mengaturnya ke dalam konteks perangkat Direct 2D.

    Dalam parameter offset , ISurfaceImageSourceNativeWithD2D::BeginDraw mengembalikan offset titik (nilai x,y) dari persegi target yang diperbarui. Anda dapat menggunakan offset tersebut untuk menentukan tempat menggambar konten yang diperbarui dengan ID2D1DeviceContext.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
    RECT updateRect{ 0, 0, 500, 500 };
    POINT offset{ 0, 0 };
    HRESULT beginDrawHR = sisNativeWithD2D->BeginDraw(
        updateRect,
        __uuidof(::IDXGISurface),
        dxgiSurface.put_void(),
        &offset);
    
    // Create render target.
    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiSurface.get(),
            nullptr,
            bitmap.put()
        )
    );
    
    // Set context's render target.
    d2dDeviceContext->SetTarget(bitmap.get());
    
  6. Gunakan konteks perangkat Direct 2D untuk menggambar konten SurfaceImageSource. Hanya area yang ditentukan untuk pembaruan pada langkah sebelumnya dalam parameter updateRect yang digambar.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
        beginDrawHR == D2DERR_RECREATE_TARGET)
    {
        // The Direct3D and Direct2D devices were lost and need to be re-created.
        // Recovery steps are:
        // 1) Re-create the Direct3D and Direct2D devices
        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
        //    device
        // 3) Redraw the contents of the SurfaceImageSource
    }
    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
    {
        // The devices were not lost but the entire contents of the surface
        // were. Recovery steps are:
        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
        //    device again
        // 2) Redraw the entire contents of the SurfaceImageSource
    }
    else
    {
        // Draw using Direct2D context.
        d2dDeviceContext->BeginDraw();
    
        d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
        winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Chocolate),
            D2D1::BrushProperties(0.8f),
            brush.put()));
    
        D2D1_SIZE_F const size{ 500, 500 };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
        d2dDeviceContext->EndDraw();
    }
    
  7. Panggil ISurfaceImageSourceNativeWithD2D::EndDraw untuk menyelesaikan bitmap (Anda harus memanggil ISurfaceImageSourceNativeWithD2D::EndDraw hanya dari utas UI). Kemudian atur SurfaceImageSource pada Gambar XAML (atau ImageBrush) untuk menampilkannya di UI XAML Anda.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    sisNativeWithD2D->EndDraw();
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(surfaceImageSource);
    

    Catatan

    Memanggil SurfaceImageSource::SetSource (diwarisi dari IBitmapSource::SetSource) saat ini memberikan pengecualian. Jangan menyebutnya dari objek SurfaceImageSource Anda.

    Catatan

    Hindari menggambar ke SurfaceImageSource saat Jendela Anda tersembunyi atau tidak aktif, jika tidak , ISurfaceImageSourceNativeWithD2D API akan gagal. Tangani peristiwa di sekitar visibilitas jendela dan penangguhan aplikasi untuk mencapainya.

  8. Terakhir, tambahkan elemen Gambar berikut di dalam markup XAML yang ada di MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. Anda kini dapat membuat dan menjalankan aplikasi. Klik tombol untuk melihat konten SurfaceImageSource yang ditampilkan di Gambar.

    A thick, dark orange rectanglular outline against a lighter orange background

VirtualSurfaceImageSource

VirtualSurfaceImageSource memperluas SurfaceImageSource, dan untuk skenario di mana konten berpotensi terlalu besar untuk dipaskan pada layar sekaligus (dan/atau terlalu besar agar pas dalam memori video sebagai tekstur tunggal), sehingga konten harus divirtualisasi agar dapat dirender secara optimal. Misalnya, aplikasi pemetaan, atau kanvas dokumen besar.

Tip

Aplikasi sampel penintaan Kompleks menunjukkan VirtualSurfaceImageSource.

VirtualSurfaceImageSource berbeda dari SurfaceImageSource karena menggunakan panggilan balik—IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded—yang Anda terapkan untuk memperbarui wilayah permukaan saat terlihat di layar. Anda tidak perlu menghapus wilayah yang tersembunyi, karena kerangka kerja XAML menanganinya untuk Anda.

Ada baiknya untuk membiasakan diri dengan SurfaceImageSource (lihat bagian SurfaceImageSource di atas) sebelum mengatasi VirtualSurfaceImageSource. Tetapi pada tingkat yang sangat tinggi, berikut adalah proses untuk membuat dan memperbarui VirtualSurfaceImageSource.

  • Terapkan antarmuka IVirtualSurfaceImageSourceCallbackNative .
  • Buat perangkat Direct 3D, perangkat Direct 2D, dan konteks perangkat Direct 2D.
  • Buat VirtualSurfaceImageSource, dan atur perangkat Direct 2D (atau Direct 3D) pada perangkat tersebut.
  • Panggil RegisterForUpdatesNeeded di VirtualSurfaceImageSource.
  • Di panggilan balik UpdatesNeeded Anda, panggil GetUpdateRectCount dan GetUpdateRects.
  • Render persegi panjang pembaruan (menggunakan BeginDrawEndDraw/ seperti untuk SurfaceImageSource).
  • Atur SurfaceImageSource pada Gambar XAML atau ImageBrush untuk menampilkannya di UI XAML Anda.

Dan berikut adalah penyelaman yang lebih mendalam tentang langkah-langkah tersebut, dengan contoh kode sumber.

  1. Anda dapat mengikuti kode yang ditampilkan dan dijelaskan di bawah ini dengan membuat proyek baru di Microsoft Visual Studio. Buat proyek Aplikasi Kosong (C++/WinRT), dan beri nama VSISDemo (penting untuk memberikan nama ini kepada proyek jika Anda akan menyalin-menempelkan dalam daftar kode yang diberikan di bawah). Targetkan versi terbaru yang tersedia secara umum (yaitu, bukan pratinjau) dari SDK Windows.

  2. Buka pch.h, dan tambahkan yang berikut ini termasuk yang sudah ada di bawah.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. Dalam langkah ini, Anda akan menyediakan implementasi antarmuka IVirtualSurfaceUpdatesCallbackNative . Tambahkan item File Header (.h) baru ke proyek, dan beri nama CallbackImplementation.h. Ganti isi file tersebut dengan daftar di bawah ini. Kode dijelaskan setelah daftar.

    #include "pch.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct CallbackImplementation : winrt::implements<CallbackImplementation, ::IVirtualSurfaceUpdatesCallbackNative>
        {
            CallbackImplementation(
                winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D,
                winrt::com_ptr<::IVirtualSurfaceImageSourceNative> const& vsisNative,
                winrt::com_ptr<::ID2D1DeviceContext> const& d2dDeviceContext) :
                m_sisNativeWithD2D(sisNativeWithD2D),
                m_vsisNative(vsisNative),
                m_d2dDeviceContext(d2dDeviceContext)
            {}
    
            IFACEMETHOD(UpdatesNeeded)()
            {
                HRESULT hr = S_OK;
    
                ULONG drawingBoundsCount = 0;
                m_vsisNative->GetUpdateRectCount(&drawingBoundsCount);
    
                std::unique_ptr<RECT[]> drawingBounds(
                    new RECT[drawingBoundsCount]);
    
                m_vsisNative->GetUpdateRects(
                    drawingBounds.get(),
                    drawingBoundsCount);
    
                for (ULONG i = 0; i < drawingBoundsCount; ++i)
                {
                    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
                    POINT offset{ 0, 0 };
                    HRESULT beginDrawHR = m_sisNativeWithD2D->BeginDraw(
                        drawingBounds[i],
                        __uuidof(::IDXGISurface),
                        dxgiSurface.put_void(),
                        &offset);
    
                    // Create render target.
                    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
                    winrt::check_hresult(
                        m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
                            dxgiSurface.get(),
                            nullptr,
                            bitmap.put()
                        )
                    );
    
                    // Set context's render target.
                    m_d2dDeviceContext->SetTarget(bitmap.get());
    
                    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
                        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
                        beginDrawHR == D2DERR_RECREATE_TARGET)
                    {
                        // The Direct3D and Direct2D devices were lost and need to be re-created.
                        // Recovery steps are:
                        // 1) Re-create the Direct3D and Direct2D devices
                        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
                        //    device
                        // 3) Redraw the contents of the SurfaceImageSource
                    }
                    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
                    {
                        // The devices were not lost but the entire contents of the surface
                        // were. Recovery steps are:
                        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
                        //    device again
                        // 2) Redraw the entire contents of the SurfaceImageSource
                    }
                    else
                    {
                        // Draw using Direct2D context.
                        m_d2dDeviceContext->BeginDraw();
    
                        m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
                        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
                        winrt::check_hresult(m_d2dDeviceContext->CreateSolidColorBrush(
                            D2D1::ColorF(D2D1::ColorF::Chocolate),
                            D2D1::BrushProperties(0.8f),
                            brush.put()));
    
                        D2D1_SIZE_F const size{ drawingBounds[i].right - drawingBounds[i].left, drawingBounds[i].bottom - drawingBounds[i].top };
                        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
                        m_d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
                        m_d2dDeviceContext->EndDraw();
                    }
    
                    m_sisNativeWithD2D->EndDraw();
                }
    
                return hr;
            }
    
        private:
            winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> m_sisNativeWithD2D{ nullptr };
            winrt::com_ptr<::IVirtualSurfaceImageSourceNative> m_vsisNative{ nullptr };
            winrt::com_ptr<::ID2D1DeviceContext> m_d2dDeviceContext{ nullptr };
        };
    }
    

    Setiap kali wilayah VirtualSurfaceImageSource perlu diperbarui, kerangka kerja memanggil implementasi IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (ditunjukkan di atas).

    Itu dapat terjadi baik ketika kerangka kerja menentukan bahwa wilayah perlu digambar (saat pengguna menggeser atau memperbesar tampilan permukaan, misalnya), atau setelah aplikasi Anda memanggil IVirtualSurfaceImageSourceNative::Invalidate di wilayah tersebut.

    Dalam implementasi IVirtualSurfaceImageSourceNative::UpdatesNeeded, gunakan metode IVirtualSurfaceImageSourceNative::GetUpdateRectCount dan IVirtualSurfaceImageSourceNative::GetUpdateRects untuk menentukan wilayah permukaan mana yang harus digambar.

    Untuk setiap wilayah yang harus diperbarui, gambar konten tertentu ke wilayah tersebut, tetapi batasi gambar Anda ke wilayah terikat untuk performa yang lebih baik. Spesifikasi panggilan metode ISurfaceImageSourceNativeWithD2D sama dengan surfaceImageSource (lihat bagian SurfaceImageSource di atas).

    Catatan

    Hindari menggambar ke VirtualSurfaceImageSource saat Jendela Anda tersembunyi atau tidak aktif, jika tidak , ISurfaceImageSourceNativeWithD2D API akan gagal. Tangani peristiwa di sekitar visibilitas jendela dan penangguhan aplikasi untuk mencapainya.

  4. Di kelas MainPage , kita akan menambahkan anggota jenis CallbackImplementation. Kami juga akan membuat perangkat Direct 3D, perangkat Direct 2D, dan konteks perangkat Direct 2D. Untuk melakukannya, kita akan memanggil D3D11CreateDevice, D2D1CreateDevice, dan ID2D1Device::CreateDeviceContext.

    Ganti konten MainPage.idl, MainPage.h, dan MainPage.cpp dengan konten daftar di bawah ini.

    // MainPage.idl
    namespace VSISDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    #include "CallbackImplementation.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
    
        private:
            winrt::com_ptr<::IVirtualSurfaceUpdatesCallbackNative> m_cbi{ nullptr };
        };
    }
    
    namespace winrt::VSISDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    namespace winrt::VSISDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  5. Selanjutnya, tambahkan kode untuk membuat VirtualSurfaceImageSource dengan ukuran yang Anda inginkan, dan atur perangkat Direct 2D (atau Direct 3D) dengan memanggil ISurfaceImageSourceNativeWithD2D::SetDevice.

    Catatan

    Jika Anda akan menggambar ke VirtualSurfaceImageSource dari utas latar belakang, maka Anda juga harus memastikan bahwa perangkat DXGI mengaktifkan akses multi-utas (seperti yang ditunjukkan pada kode di bawah). Untuk alasan performa, Anda harus melakukannya hanya jika Anda akan menggambar dari utas latar belakang.

    Untuk mengatur perangkat, dan menjalankan operasi gambar, kita memerlukan penunjuk ke ISurfaceImageSourceNativeWithD2D. Untuk mendapatkannya, kueri objek VirtualSurfaceImageSource untuk antarmuka ISurfaceImageSourceNativeWithD2D yang mendasarnya .

    Kueri juga untuk IVirtualSurfaceImageSourceNative, dan panggil IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, menyediakan implementasi IVirtualSurfaceUpdatesCallbackNative Anda.

    Kemudian atur SurfaceImageSource pada Gambar XAML (atau ImageBrush) untuk menampilkannya di UI XAML Anda.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    VirtualSurfaceImageSource virtualSurfaceImageSource(2000, 2000);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        virtualSurfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
    winrt::com_ptr<::IVirtualSurfaceImageSourceNative> vsisNative{
        virtualSurfaceImageSource.as<::IVirtualSurfaceImageSourceNative>() };
    
    m_cbi = winrt::make<CallbackImplementation>(sisNativeWithD2D, vsisNative, d2dDeviceContext);
    vsisNative->RegisterForUpdatesNeeded(m_cbi.as<::IVirtualSurfaceUpdatesCallbackNative>().get());
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(virtualSurfaceImageSource);
    
  6. Terakhir, tambahkan elemen Gambar berikut di dalam markup XAML yang ada di MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. Anda kini dapat membuat dan menjalankan aplikasi. Klik tombol untuk melihat konten VirtualSurfaceImageSource yang ditampilkan di Gambar.

SwapChainPanel dan game

SwapChainPanel adalah jenis Windows Runtime yang dirancang untuk mendukung grafis dan game berkinerja tinggi, di mana Anda mengelola rantai pertukaran secara langsung. Dalam hal ini, Anda membuat rantai pertukaran DirectX Anda sendiri dan mengelola presentasi konten yang Anda render. Fitur lain dari SwapChainPanel adalah Anda dapat melapisi elemen XAML lainnya di depannya.

Untuk memastikan performa yang baik, ada batasan tertentu untuk jenis SwapChainPanel .

  • Tidak boleh ada lebih dari 4 instans SwapChainPanel per aplikasi.
  • Anda harus mengatur tinggi dan lebar rantai pertukaran DirectX (dalam DXGI_SWAP_CHAIN_DESC1) ke dimensi elemen rantai pertukaran saat ini. Jika tidak, maka konten tampilan akan diskalakan agar pas (menggunakan DXGI_SCALING_STRETCH).
  • Anda harus mengatur mode penskalakan rantai pertukaran DirectX (dalam DXGI_SWAP_CHAIN_DESC1) ke DXGI_SCALING_STRETCH.
  • Anda harus membuat rantai pertukaran DirectX dengan memanggil IDXGIFactory2::CreateSwapChainForComposition.

Anda memperbarui SwapChainPanel berdasarkan kebutuhan aplikasi Anda, dan tidak disinkronkan dengan pembaruan kerangka kerja XAML. Jika Anda perlu menyinkronkan pembaruan SwapChainPanel dengan kerangka kerja XAML, maka daftar untuk peristiwa Windows::UI::Xaml::Media::CompositionTarget::Rendering. Jika tidak, Anda harus mempertimbangkan masalah lintas alur jika Anda mencoba memperbarui elemen XAML dari utas yang berbeda dari yang memperbarui SwapChainPanel.

Jika Anda perlu menerima input pointer latensi rendah ke SwapChainPanel Anda, maka gunakan SwapChainPanel::CreateCoreIndependentInputSource. Metode tersebut mengembalikan objek CoreIndependentInputSource yang dapat digunakan untuk menerima peristiwa input pada latensi minimal pada utas latar belakang. Perhatikan bahwa setelah metode ini dipanggil, peristiwa input penunjuk XAML normal tidak akan dinaikkan untuk SwapChainPanel, karena semua input akan dialihkan ke utas latar belakang.

Berikut adalah proses untuk membuat dan memperbarui objek SwapChainPanel .

  1. Anda dapat mengikuti kode yang ditampilkan dan dijelaskan di bawah ini dengan membuat proyek baru di Microsoft Visual Studio. Buat proyek Blank App (C++/WinRT), dan beri nama SCPDemo (penting untuk memberikan nama ini kepada proyek jika Anda akan menyalin-menempelkan dalam daftar kode yang diberikan di bawah). Targetkan versi terbaru yang tersedia secara umum (yaitu, bukan pratinjau) dari SDK Windows.

  2. Buka pch.h, dan tambahkan yang berikut ini termasuk yang sudah ada di bawah.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. Di kelas MainPage , pertama-tama kita akan membuat perangkat Direct 3D, perangkat Direct 2D, dan konteks perangkat Direct 2D. Untuk melakukannya, kita akan memanggil D3D11CreateDevice, D2D1CreateDevice, dan ID2D1Device::CreateDeviceContext.

    Ganti konten MainPage.idl, MainPage.h, dan MainPage.cpp dengan konten daftar di bawah ini.

    // MainPage.idl
    namespace SCPDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    
    namespace winrt::SCPDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
        };
    }
    
    namespace winrt::SCPDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    
    namespace winrt::SCPDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  4. Bungkus markup XAML Anda dalam elemen SwapChainPanel dengan x:Name. Elemen XAML yang dibungkus akan dirender di depan SwapChainPanel.

    <!-- MainPage.xaml -->
     <SwapChainPanel x:Name="swapChainPanel">
     	<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
     		<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
     	</StackPanel>
     </SwapChainPanel>
    

    Anda kemudian dapat mengakses objek SwapChainPanel tersebut melalui fungsi pengakses dengan nama yang sama, seperti yang akan kita lihat.

  5. Selanjutnya, panggil IDXGIFactory2::CreateSwapChainForComposition untuk membuat rantai pertukaran.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get the DXGI adapter.
    winrt::com_ptr< ::IDXGIAdapter > dxgiAdapter;
    dxgiDevice->GetAdapter(dxgiAdapter.put());
    
    // Get the DXGI factory.
    winrt::com_ptr< ::IDXGIFactory2 > dxgiFactory;
    dxgiFactory.capture(dxgiAdapter, &IDXGIAdapter::GetParent);
    
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc { 0 };
    swapChainDesc.Width = 500;
    swapChainDesc.Height = 500;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swapchain 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;
    swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // We recommend using this swap effect for all applications.
    swapChainDesc.Flags = 0;
    
    // Create a swap chain by calling IDXGIFactory2::CreateSwapChainForComposition.
    winrt::com_ptr< ::IDXGISwapChain1 > swapChain;
    dxgiFactory->CreateSwapChainForComposition(
        d3dDevice.get(),
        &swapChainDesc,
        nullptr,
        swapChain.put());
    
  6. Dapatkan ISwapChainPanelNative dari SwapChainPanel yang Anda beri nama swapChainPanel. Panggilan ISwapChainPanelNative::SetSwapChain untuk mengatur rantai pertukaran pada SwapChainPanel.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get native interface for SwapChainPanel
    auto panelNative{ swapChainPanel().as<ISwapChainPanelNative>() };
    
    winrt::check_hresult(
        panelNative->SetSwapChain(swapChain.get())
    );
    
  7. Terakhir, gambar ke rantai pertukaran DirectX, lalu sajikan untuk menampilkan konten.

    // 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),
            96.f,
            96.f
        );
    
    winrt::com_ptr<::IDXGISurface> dxgiBackBuffer;
    swapChain->GetBuffer(0, __uuidof(dxgiBackBuffer), dxgiBackBuffer.put_void());
    
    winrt::com_ptr< ::ID2D1Bitmap1 > targetBitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.get(),
            &bitmapProperties,
            targetBitmap.put()
        )
    );
    
    d2dDeviceContext->SetTarget(targetBitmap.get());
    
    // Draw using Direct2D context.
    d2dDeviceContext->BeginDraw();
    
    d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
    winrt::com_ptr<::ID2D1SolidColorBrush> brush;
    winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::Chocolate),
        D2D1::BrushProperties(0.8f),
        brush.put()));
    
    D2D1_SIZE_F const size{ 500, 500 };
    D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
    d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
    d2dDeviceContext->EndDraw();
    
    swapChain->Present(1, 0);
    

    Elemen XAML di-refresh saat tata letak runtime Windows/logika render memberi sinyal pembaruan.

  8. Anda kini dapat membuat dan menjalankan aplikasi. Klik tombol untuk melihat konten SwapChainPanel yang ditampilkan di belakang elemen XAML lainnya.

    A Direct2D-rendered rectangle behind a XAML button element

Catatan

Secara umum, aplikasi DirectX Anda harus membuat rantai pertukaran dalam orientasi lanskap, dan sama dengan ukuran jendela tampilan (yang biasanya merupakan resolusi layar asli di sebagian besar Microsoft Store game). Itu memastikan bahwa aplikasi Anda menggunakan implementasi rantai pertukaran optimal saat tidak memiliki overlay XAML yang terlihat. Jika aplikasi diputar ke mode potret, aplikasi Anda harus memanggil IDXGISwapChain1::SetRotation pada rantai pertukaran yang ada, menerapkan transformasi ke konten jika diperlukan, lalu memanggil SetSwapChain lagi pada rantai pertukaran yang sama. Demikian pula, aplikasi Anda harus memanggil SetSwapChain lagi pada rantai pertukaran yang sama setiap kali rantai pertukaran diubah ukurannya dengan memanggil IDXGISwapChain::ResizeBuffers.