Contoh kode swapchain komposisi

Contoh kode ini menggunakan Windows Implementation Libraries (WIL). Cara mudah untuk menginstal WIL adalah dengan membuka Visual Studio, klik Proyek>Kelola Paket NuGet...>Telusuri, ketik, atau tempel Microsoft.Windows.ImplementationLibrary di kotak pencarian, pilih item di hasil pencarian, lalu klik Instal untuk menginstal paket untuk proyek tersebut.

Contoh 1—Membuat manajer presentasi pada sistem yang mendukung API swapchain komposisi

Seperti disebutkan, API swapchain komposisi memerlukan driver yang didukung untuk berfungsi. Contoh berikut menggambarkan bagaimana aplikasi Anda dapat membuat manajer presentasi jika sistem mendukung API. Ini ditunjukkan sebagai TryCreatefungsi -style yang mengembalikan manajer presentasi hanya jika API didukung. Fungsi ini juga menunjukkan cara membuat perangkat Direct3D dengan benar untuk mendukung manajer presentasi.

Contoh C++

bool TryCreatePresentationManager(
    _In_ bool requestDirectPresentation,
    _Out_ ID3D11Device** ppD3DDevice,
    _Outptr_opt_result_maybenull_ IPresentationManager **ppPresentationManager)
{
    // Null the presentation manager and Direct3D device initially
    *ppD3DDevice = nullptr;
    *ppPresentationManager = nullptr;

    // Direct3D device creation flags. The composition swapchain API requires that applications disable internal
    // driver threading optimizations, as these optimizations are incompatible with the
    // composition swapchain API. If this flag is not present, then the API will fail the call to create the
    // presentation factory.
    UINT deviceCreationFlags =
        D3D11_CREATE_DEVICE_BGRA_SUPPORT |
        D3D11_CREATE_DEVICE_SINGLETHREADED |
        D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;

    // Create the Direct3D device.
    com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;
    FAIL_FAST_IF_FAILED(D3D11CreateDevice(
        nullptr,                   // No adapter
        D3D_DRIVER_TYPE_HARDWARE,  // Hardware device
        nullptr,                   // No module
        deviceCreationFlags,       // Device creation flags
        nullptr, 0,                // Highest available feature level
        D3D11_SDK_VERSION,         // API version
        ppD3DDevice,               // Resulting interface pointer
        nullptr,                   // Actual feature level
        &d3dDeviceContext));       // Device context

    // Call the composition swapchain API export to create the presentation factory.
    com_ptr_failfast<IPresentationFactory> presentationFactory;
    FAIL_FAST_IF_FAILED(CreatePresentationFactory(
        (*ppD3DDevice),
        IID_PPV_ARGS(&presentationFactory)));

    // Determine whether the system is capable of supporting the composition swapchain API based
    // on the capability that's reported by the presentation factory. If your application
    // wants direct presentation (that is, presentation without the need for DWM to
    // compose, using MPO or iflip), then we query for direct presentation support.
    bool isSupportedOnSystem;
    if (requestDirectPresentation)
    {
        isSupportedOnSystem = presentationFactory->IsPresentationSupportedWithIndependentFlip();
    }
    else
    {
        isSupportedOnSystem = presentationFactory->IsPresentationSupported();
    }

    // Create the presentation manager if it is supported on the current system.
    if (isSupportedOnSystem)
    {
        FAIL_FAST_IF_FAILED(presentationFactory->CreatePresentationManager(ppPresentationManager));
    }

    return isSupportedOnSystem;
}

Contoh 2—Membuat manajer presentasi dan permukaan presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan membuat manajer presentasi dan permukaan presentasi untuk mengikat visual di pohon visual. Contoh berikutnya akan menunjukkan cara mengikat permukaan presentasi ke dalam pohon visual DirectComposition dan Windows.UI.Composition.

Contoh C++ (DCompositionGetTargetStatistics)

bool MakePresentationManagerAndPresentationSurface(
    _Out_ ID3D11Device** ppD3dDevice,
    _Out_ IPresentationManager** ppPresentationManager,
    _Out_ IPresentationSurface** ppPresentationSurface,
    _Out_ unique_handle& compositionSurfaceHandle)
{
    // Null the output pointers initially.
    *ppD3dDevice = nullptr;
    *ppPresentationManager = nullptr;
    *ppPresentationSurface = nullptr;
    compositionSurfaceHandle.reset();

    com_ptr_failfast<IPresentationManager> presentationManager;
    com_ptr_failfast<IPresentationSurface> presentationSurface;
    com_ptr_failfast<ID3D11Device> d3d11Device;

    // Call the function we defined previously to create a Direct3D device and presentation manager, if
    // the system supports it.
    if (TryCreatePresentationManager(
        true, // Request presentation with independent flip.
        &d3d11Device,
        &presentationManager) == false)
    {
        // Return 'false' out of the call if the composition swapchain API is unsupported. Assume the caller
        // will handle this somehow, such as by falling back to DXGI.
        return false;
    }

    // Use DirectComposition to create a composition surface handle.
    FAIL_FAST_IF_FAILED(DCompositionCreateSurfaceHandle(
        COMPOSITIONOBJECT_ALL_ACCESS,
        nullptr,
        compositionSurfaceHandle.addressof()));

    // Create presentation surface bound to the composition surface handle.
    FAIL_FAST_IF_FAILED(presentationManager->CreatePresentationSurface(
        compositionSurfaceHandle.get(),
        presentationSurface.addressof()));

    // Return the Direct3D device, presentation manager, and presentation surface to the caller for future
    // use.
    *ppD3dDevice = d3d11Device.detach();
    *ppPresentationManager = presentationManager.detach();
    *ppPresentationSurface = presentationSurface.detach();

    // Return 'true' out of the call if the composition swapchain API is supported and we were able to
    // create a presentation manager.
    return true;
}

Contoh 3—Mengikat permukaan presentasi ke kuas permukaan Windows.UI.Composition

Contoh berikut menggambarkan bagaimana aplikasi Anda akan mengikat handel permukaan komposisi yang terikat ke permukaan presentasi, seperti yang dibuat dalam contoh di atas, ke kuas permukaan Windows.UI.Composition (WinComp), yang kemudian dapat terikat ke visual sprite di pohon visual aplikasi Anda.

Contoh C++

void BindPresentationSurfaceHandleToWinCompTree(
    _In_ ICompositor * pCompositor,
    _In_ ISpriteVisual * pVisualToBindTo, // The sprite visual we want to bind to.
    _In_ unique_handle& compositionSurfaceHandle)
{
    // QI an interop compositor from the passed compositor.
    com_ptr_failfast<ICompositorInterop> compositorInterop;
    FAIL_FAST_IF_FAILED(pCompositor->QueryInterface(IID_PPV_ARGS(&compositorInterop)));

    // Create a composition surface for the presentation surface's composition surface handle.
    com_ptr_failfast<ICompositionSurface> compositionSurface;
    FAIL_FAST_IF_FAILED(compositorInterop->CreateCompositionSurfaceForHandle(
        compositionSurfaceHandle.get(),
        &compositionSurface));

    // Create a composition surface brush, and bind the surface to it.
    com_ptr_failfast<ICompositionSurfaceBrush> surfaceBrush;
    FAIL_FAST_IF_FAILED(pCompositor->CreateSurfaceBrush(&surfaceBrush));
    FAIL_FAST_IF_FAILED(surfaceBrush->put_Surface(compositionSurface.get()));

    // Bind the brush to the visual.
    auto brush = surfaceBrush.query<ICompositionBrush>();
    FAIL_FAST_IF_FAILED(pVisualToBindTo->put_Brush(brush.get()));
}

Contoh 4—Mengikat permukaan presentasi ke visual DirectComposition

Contoh berikut menggambarkan bagaimana aplikasi Anda akan mengikat permukaan presentasi ke visual DirectComposition (DComp) di pohon visual.

Contoh C++

void BindPresentationSurfaceHandleToDCompTree(
    _In_ IDCompositionDevice* pDCompDevice,
    _In_ IDCompositionVisual* pVisualToBindTo,
    _In_ unique_handle& compositionSurfaceHandle) // The composition surface handle that was
                                                  // passed to CreatePresentationSurface.
{
    // Create a DComp surface to wrap the presentation surface.
    com_ptr_failfast<IUnknown> dcompSurface;
    FAIL_FAST_IF_FAILED(pDCompDevice->CreateSurfaceFromHandle(
        compositionSurfaceHandle.get(),
        &dcompSurface));

    // Bind the presentation surface to the visual.
    FAIL_FAST_IF_FAILED(pVisualToBindTo->SetContent(dcompSurface.get()));
}

Contoh 5—Mengatur mode alfa dan ruang warna pada permukaan presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan mengatur mode alfa dan ruang warna pada permukaan presentasi. Mode alfa menjelaskan apakah atau tidak, dan bagaimana, saluran alfa dalam tekstur harus ditafsirkan. Ruang warna menjelaskan ruang warna yang dirujuk oleh piksel tekstur.

Semua pembaruan properti seperti ini akan berlaku sebagai bagian dari aplikasi saat ini, dan berlaku secara atomik bersama pembaruan buffer yang merupakan bagian dari yang ada. Hadiah juga dapat, jika aplikasi Anda ingin, tidak memperbarui buffer sama sekali, dan sebaliknya, hanya terdiri dari pembaruan properti. Setiap permukaan presentasi yang buffernya tidak diperbarui pada hadiah tertentu tetap terikat pada buffer apa pun yang mereka terikat sebelum saat ini.

Contoh C++

void SetAlphaModeAndColorSpace(
    _In_ IPresentationSurface* pPresentationSurface)
{
    // Set alpha mode.
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetAlphaMode(DXGI_ALPHA_MODE_IGNORE));

    // Set color space to full RGB.
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709));
}

Contoh 6—Mengatur margin kotak surat pada permukaan presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan menentukan margin kotak surat pada permukaan presentasi. Kotak surat digunakan untuk mengisi area tertentu di luar konten permukaan itu sendiri, untuk memperhitungkan rasio aspek yang berbeda antara konten dan perangkat tampilan tempat konten ditampilkan. Contoh yang baik dari ini adalah bilah hitam yang sering terlihat di atas dan di bawah konten saat menonton di PC film yang diformat untuk bioskop layar lebar. API swapchain komposisi memungkinkan Anda menentukan margin untuk merender kotak surat dalam kasus seperti itu.

Saat ini, area kotak surat selalu diisi dengan warna hitam buram.

Sebagai konten pohon visual, permukaan presentasi ada di ruang koordinat visual host-nya. Dengan adanya kotak surat, asal ruang koordinat visual sesuai dengan kiri atas kotak surat. Artinya, buffer itu sendiri ada offset ke ruang koordinat visual. Batas dihitung sebagai ukuran buffer ditambah ukuran kotak surat.

Berikut ini menggambarkan di mana buffer dan kotak surat ada di ruang koordinat visual, dan bagaimana batas dihitung.

Letterboxing margins

Terakhir, jika transformasi yang diterapkan ke permukaan presentasi dilengkapi dengan offset, maka area yang tidak dicakup oleh buffer atau kotak surat dianggap di luar batas konten, dan diperlakukan sebagai transparan—mirip dengan bagaimana konten pohon visual lainnya diperlakukan di luar batas konten.

Kotak surat dan mengubah interaksi

Untuk memberikan ukuran margin kotak surat yang konsisten terlepas dari skala yang diterapkan aplikasi Anda ke buffer untuk mengisi beberapa area konten, DWM akan mencoba merender margin pada ukuran yang konsisten terlepas dari skala, tetapi dengan mempertimbangkan hasil transformasi yang telah diterapkan ke permukaan presentasi.

Dengan cara lain, margin kotak surat diterapkan secara teknis sebelum transformasi permukaan presentasi diterapkan, tetapi mengkompensasi skala apa pun yang mungkin menjadi bagian dari transformasi tersebut. Artinya, transformasi permukaan presentasi didekonstruksi menjadi dua komponen —bagian dari transformasi yang menskalakan, dan sisa transformasi.

Letterboxing and transform interaction 1

Misalnya, dengan margin kotak surat 100px, dan tidak ada transformasi yang diterapkan pada permukaan presentasi, buffer yang dihasilkan akan dirender tanpa skala, dan margin kotak surat akan lebar 100px.

Letterboxing and transform interaction 2

Contoh lain, dengan margin kotak surat 100px, dan transformasi skala 2x yang diterapkan pada permukaan presentasi, buffer yang dihasilkan akan dirender dengan skala 2x, dan margin kotak surat yang terlihat di layar masih akan menjadi 100px dalam semua ukuran.

Letterboxing and transform interaction 3

Contoh lain, dengan transformasi rotasi 45 derajat, margin kotak surat yang dihasilkan akan muncul 100px, dan margin kotak surat akan diputar dengan buffer.

Letterboxing and transform interaction 4

Contoh lain, dengan transformasi skala 2x dan rotasi 45 derajat, gambar akan diputar dan diskalakan, dan margin kotak surat juga akan lebar 100px dan diputar dengan buffer.

Letterboxing and transform interaction 5

Jika transformasi skala tidak dapat diekstrak secara tidak ambigu dari transformasi yang diterapkan aplikasi Anda ke permukaan presentasi, seperti jika skala X atau Y yang dihasilkan adalah 0, margin kotak surat akan dirender dengan seluruh transformasi yang diterapkan, dan kami tidak akan mencoba mengimbangi skala.

Letterboxing and transform interaction 6

Contoh C++

 void SetContentLayoutAndFill(
    _In_ IPresentationSurface* pPresentationSurface)
{
    // Set layout properties. Each layout property is described below.

    // The source RECT describes the area from the bound buffer that will be sampled from. The
    // RECT is in source texture coordinates. Below we indicate that we'll sample from a
    // 100x100 area on the source texture.
    RECT sourceRect;
    sourceRect.left = 0;
    sourceRect.top = 0;
    sourceRect.right = 100;
    sourceRect.bottom = 100;
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetSourceRect(&sourceRect));

    // The presentation transform defines how the source rect will be transformed when
    // rendering the buffer. In this case, we indicate we want to scale it 2x in both
    // width and height, and we also want to offset it 50 pixels to the right.
    PresentationTransform transform = { 0 };
    transform.M11 = 2.0f; // X scale 2x
    transform.M22 = 2.0f; // Y scale 2x
    transform.M31 = 50.0f; // X offset 50px
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetTransform(&transform));

    // The letterboxing parameters describe how to letterbox the content. Letterboxing
    // is commonly used to fill area not covered by video/surface content when scaling to
    // an aspect ratio that doesn't match the aspect ratio of the surface itself. For
    // example, when viewing content formatted for the theater on a 1080p home screen, one
    // can typically see black "bars" on the top and bottom of the video, covering the space
    // on screen that wasn't covered by the video due to the differing aspect ratios of the
    // content and the display device. The composition swapchain API allows the user to specify
    // letterboxing margins, which describe the number of pixels to surround the surface
    // content with on screen. In this case, surround the top and bottom of our content with
    // 100 pixel tall letterboxing.
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetLetterboxingMargins(
        0.0f,
        100.0f,
        0.0f,
        100.0f));
}

Contoh 7—Mengatur pembatasan konten pada permukaan presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan mencegah aplikasi lain membaca kembali konten permukaan presentasi (dari teknologi seperti PrintScreen, DDA, Capture API, dan lainnya) untuk tujuan konten yang dilindungi. Ini juga menunjukkan cara membatasi tampilan permukaan presentasi hanya untuk satu output DXGI.

Jika readback konten dinonaktifkan, maka ketika aplikasi Anda mencoba membaca kembali, gambar yang diambil akan berisi hitam buram sebagai pengganti permukaan presentasi. Jika permukaan presentasi dibatasi tampilan pada IDXGIOutput, maka akan muncul sebagai hitam buram pada semua output lainnya.

Contoh C++

void SetContentRestrictions(
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ IDXGIOutput* pOutputToRestrictTo)
{
    // Disable readback of the surface via printscreen, bitblt, etc.
    FAIL_FAST_IF_FAILED(pPresentationSurface->SetDisableReadback(true));

    // Restrict display of surface to only the passed output.
    FAIL_FAST_IF_FAILED(pPresentationSurface->RestrictToOutput(pOutputToRestrictTo));
}

Contoh 8—Menambahkan buffer presentasi ke manajer presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan mengalokasikan sekumpulan tekstur Direct3D, dan menambahkannya ke manajer presentasi untuk digunakan sebagai buffer presentasi, yang dapat disajikan ke permukaan presentasi.

Seperti disebutkan, tidak ada batasan sekeliling jumlah manajer presentasi yang dapat didaftarkan oleh tekstur. Namun, dalam sebagian besar kasus penggunaan normal, tekstur hanya akan didaftarkan ke satu manajer presentasi.

Perhatikan juga bahwa karena API menerima handel buffer bersama sebagai mata uang saat mendaftarkan tekstur sebagai buffer presentasi di manajer presentasi, dan karena DXGI dapat membuat beberapa buffer bersama untuk satu texture2D, secara teknis aplikasi Anda dapat membuat beberapa handel buffer bersama untuk satu tekstur, dan mendaftarkannya keduanya dengan manajer presentasi, pada dasarnya memiliki efek menambahkan buffer yang sama lebih dari sekali. Ini tidak disarankan, karena akan memutus mekanisme sinkronisasi yang disediakan manajer presentasi, karena dua buffer presentasi yang dilacak secara unik sebenarnya akan sesuai dengan tekstur yang sama. Karena ini adalah API tingkat lanjut, dan karena sebenarnya cukup sulit dalam implementasi untuk mendeteksi kasus ini ketika terjadi, API tidak mencoba memvalidasi skenario ini.

Contoh C++

void AddBuffersToPresentationManager(
    _In_ ID3D11Device* pD3D11Device, // The backing Direct3D device
    _In_ IPresentationManager* pPresentationManager, // Previously-made presentation manager
    _In_ UINT bufferWidth, // The width of the buffers to add
    _In_ UINT bufferHeight, // The height of the buffers to add
    _In_ UINT numberOfBuffersToAdd, // The number of buffers to add to the presentation manager
    _Out_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures, // Array of textures returned
    _Out_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers) // Array of presentation buffers returned
{
    // Clear the returned vectors initially.
    textures.clear();
    presentationBuffers.clear();

    // Add the desired buffers to the presentation manager.
    for (UINT i = 0; i < numberOfBuffersToAdd; i++)
    {
        com_ptr_failfast<ID3D11Texture2D> texture;
        com_ptr_failfast<IPresentationBuffer> presentationBuffer;

        // Call our helper to make a new buffer of the desired type.
        AddNewPresentationBuffer(
            pD3D11Device,
            pPresentationManager,
            bufferWidth,
            bufferHeight,
            &texture,
            &presentationBuffer);

        // Track our buffers in our own set of vectors.
        textures.push_back(texture);
        presentationBuffers.push_back(presentationBuffer);
    }
}

void AddNewPresentationBuffer(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ UINT bufferWidth,
    _In_ UINT bufferHeight,
    _Out_ ID3D11Texture2D** ppTexture2D,
    _Out_ IPresentationBuffer** ppPresentationBuffer)
{
    com_ptr_failfast<ID3D11Texture2D> texture2D;
    unique_handle sharedResourceHandle;

    // Create a shared Direct3D texture and handle with the passed attributes.
    MakeD3D11Texture(
        pD3D11Device,
        bufferWidth,
        bufferHeight,
        &texture2D,
        out_param(sharedResourceHandle));

    // Add the texture2D to the presentation manager, and get back a presentation buffer.
    com_ptr_failfast<IPresentationBuffer> presentationBuffer;
    FAIL_FAST_IF_FAILED(pPresentationManager->AddBufferFromSharedHandle(
        sharedResourceHandle.get(),
        &presentationBuffer));

    // Return back the texture and buffer presentation buffer.
    *ppTexture2D = texture2D.detach();
    *ppPresentationBuffer = presentationBuffer.detach();
}

void MakeD3D11Texture(
    _In_ ID3D11Device* pD3D11Device,
    _In_ UINT textureWidth,
    _In_ UINT textureHeight,
    _Out_ ID3D11Texture2D** ppTexture2D,
    _Out_ HANDLE* sharedResourceHandle)
{
    D3D11_TEXTURE2D_DESC textureDesc = {};

    // Width and height can be anything within max texture size of the adapter backing the Direct3D
    // device.
    textureDesc.Width = textureWidth;
    textureDesc.Height = textureHeight;

    // MipLevels and ArraySize must be 1.
    textureDesc.MipLevels = 1;
    textureDesc.ArraySize = 1;

    // Format can be one of the following:
    //   DXGI_FORMAT_B8G8R8A8_UNORM
    //   DXGI_FORMAT_R8G8B8A8_UNORM
    //   DXGI_FORMAT_R16G16B16A16_FLOAT
    //   DXGI_FORMAT_R10G10B10A2_UNORM
    //   DXGI_FORMAT_NV12
    //   DXGI_FORMAT_YUY2
    //   DXGI_FORMAT_420_OPAQUE
    // For this 
    textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;

    // SampleDesc count and quality must be 1 and 0 respectively.
    textureDesc.SampleDesc.Count = 1;
    textureDesc.SampleDesc.Quality = 0;

    // Usage must be D3D11_USAGE_DEFAULT.
    textureDesc.Usage = D3D11_USAGE_DEFAULT;

    // BindFlags must include D3D11_BIND_SHADER_RESOURCE for RGB textures, and D3D11_BIND_DECODER
    // for YUV textures. For RGB textures, it is likely your application will want to specify
    // D3D11_BIND_RENDER_TARGET in order to render to it.
    textureDesc.BindFlags =
        D3D11_BIND_SHADER_RESOURCE |
        D3D11_BIND_RENDER_TARGET;

    // MiscFlags should include D3D11_RESOURCE_MISC_SHARED and D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
    // and might also include D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE if your application wishes to
    // qualify for MPO and iflip. If D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE is not provided, then the
    // content will not qualify for MPO or iflip, but can still be composed by DWM
    textureDesc.MiscFlags =
        D3D11_RESOURCE_MISC_SHARED |
        D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
        D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE;

    // CPUAccessFlags must be 0.
    textureDesc.CPUAccessFlags = 0;

    // Use Direct3D to create a texture 2D matching the desired attributes.
    com_ptr_failfast<ID3D11Texture2D> texture2D;
    FAIL_FAST_IF_FAILED(pD3D11Device->CreateTexture2D(&textureDesc, nullptr, &texture2D));

    // Create a shared handle for the texture2D.
    unique_handle sharedBufferHandle;
    auto dxgiResource = texture2D.query<IDXGIResource1>();
    FAIL_FAST_IF_FAILED(dxgiResource->CreateSharedHandle(
        nullptr,
        GENERIC_ALL,
        nullptr,
        &sharedBufferHandle));

    // Return the handle to the caller.
    *ppTexture2D = texture2D.detach();
    *sharedResourceHandle = sharedBufferHandle.release();
}

Contoh 9—Menghapus buffer presentasi dari manajer presentasi, dan masa pakai objek

Menghapus buffer presentasi dari manajer presentasi semampu merilis objek IPresentationBuffer ke 0 refcount. Namun, ketika ini terjadi, itu tidak selalu berarti bahwa buffer presentasi dapat dirilis. Mungkin ada kasus ketika buffer mungkin masih digunakan, seperti jika buffer presentasi terlihat di layar, atau memiliki penyajian luar biasa yang mereferensikannya.

Singkatnya, manajer presentasi akan melacak bagaimana setiap buffer digunakan oleh aplikasi Anda secara langsung, dan tidak langsung. Ini akan melacak buffer apa yang ditampilkan, apa yang hadir adalah buffer yang luar biasa dan referensi, dan secara internal akan melacak semua status ini untuk memastikan bahwa buffer tidak benar-benar dirilis/tidak terdaftar sampai benar-benar tidak lagi digunakan.

Cara yang baik untuk memikirkan masa pakai buffer adalah bahwa jika aplikasi Anda merilis IPresentationBuffer, itu memberi tahu API bahwa ia tidak akan lagi menggunakan buffer tersebut dalam panggilan di masa mendatang. API swapchain komposisi akan melacak ini, serta cara lain buffer digunakan, dan itu akan sepenuhnya melepaskan buffer hanya ketika benar-benar aman untuk melakukannya.

Singkatnya—aplikasi Anda harus merilis IPresentationBuffer ketika selesai, dan tahu bahwa API swapchain komposisi akan sepenuhnya merilis buffer ketika semua penggunaan lain juga telah selesai.

Berikut adalah ilustrasi konsep di atas.

Removing presentation buffers

Dalam contoh ini, kami memiliki manajer presentasi dengan dua buffer presentasi. Buffer 1 memiliki tiga referensi yang membuatnya tetap hidup.

  • Aplikasi Anda memegang IPresentationBuffer yang mereferensikannya.
  • Internal ke API, itu sedang disimpan sebagai buffer terikat permukaan presentasi saat ini, karena merupakan buffer terakhir yang terikat aplikasi Anda, yang kemudian disajikan.
  • Ada juga hadiah yang luar biasa dalam antrean saat ini yang berniat menampilkan buffer 1 di permukaan presentasi.

Buffer 1 juga memegang referensi pada alokasi tekstur Direct3D yang sesuai. Aplikasi Anda juga memiliki REFERENSI ID3D11Texture2D yang merujuk alokasi tersebut.

Buffer 2 tidak memiliki referensi yang lebih luar biasa dari sisi aplikasi, juga tidak alokasi tekstur yang direferensikannya, tetapi buffer 2 tetap hidup karena itu adalah apa yang ditampilkan saat ini oleh permukaan presentasi di layar. Ketika Present 1 diproses, dan buffer 1 ditampilkan di layar sebagai gantinya, buffer 2 tidak akan memiliki referensi lagi, dan akan dirilis, pada gilirannya merilis referensinya pada alokasi Direct3D texture2D-nya.

Contoh C++

void ReleasePresentationManagerBuffersExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager)
{
    com_ptr_failfast<ID3D11Texture2D> texture2D;
    com_ptr_failfast<IPresentationBuffer> newBuffer;

    // Create a new texture/presentation buffer.
    AddNewPresentationBuffer(
        pD3D11Device,
        pPresentationManager,
        200, // Buffer width
        200, // Buffer height
        &texture2D,
        &newBuffer);

    // Release the IPresentationBuffer. This indicates that we have no more intention to use it
    // from the application side. However, if we have any presents outstanding that reference
    // the buffer, it will be kept alive internally and released when all outstanding presents
    // have been retired.
    newBuffer.reset();

    // When the presentation buffer is truly released internally, it will in turn release its
    // reference on the texture2D which it corresponds to. Your application must also release
    // its own references to the texture2D for it to be released.
    texture2D.reset();
}

Contoh 10—Mengubah ukuran buffer di manajer presentasi

Contoh berikut menggambarkan bagaimana aplikasi Anda akan melakukan perubahan ukuran buffer presentasi. Misalnya, jika layanan streaming film streaming 480p tetapi memutuskan untuk beralih format ke 1080p karena ketersediaan bandwidth jaringan tinggi, maka ia ingin merealokasi buffer-nya dari 480p ke 1080p sehingga dapat mulai menyajikan konten 1080p.

Di DXGI, ini disebut sebagai operasi pengubahan ukuran . DXGI merealokasi semua buffer secara atomik, yang mahal, dan dapat menghasilkan glitching di layar. Contoh di sini menjelaskan cara mencapai pengubahan ukuran atom sesuai dengan DXGI dalam API swapchain komposisi. Contoh selanjutnya akan menunjukkan cara melakukan pengubahan ukuran dengan cara yang mengejutkan , menspasikan realokasi di beberapa hadiah, mencapai performa yang lebih baik daripada mengubah ukuran swapchain atom.

Contoh C++

void ResizePresentationManagerBuffersExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager) // Previously-made presentation manager.
{
    // Vectors representing the IPresentationBuffer and ID3D11Texture2D collections.
    vector<com_ptr_failfast<ID3D11Texture2D>> textures;
    vector<com_ptr_failfast<IPresentationBuffer>> presentationBuffers;

    // Add 6 50x50 buffers to the presentation manager. See previous example for definition of
    // this function.
    AddBuffersToPresentationManager(
        pD3D11Device,
        pPresentationManager,
        50, // Buffer width
        50, // Buffer height
        6, // Number of buffers
        textures,
        presentationBuffers);

    // Release all the buffers we just added. The presentation buffers internally will be released
    // when any other references are also removed (outstanding presents, display references, etc.).
    textures.clear();
    presentationBuffers.clear();

    // Add 6 new 100x100 buffers to the presentation manager to replace the
    // old ones we just removed.
    AddBuffersToPresentationManager(
        pD3D11Device,
        pPresentationManager,
        100, // Buffer width
        100, // Buffer height
        6, // Number of buffers
        textures,
        presentationBuffers);
}

Contoh 11—Menyinkronkan presentasi menggunakan peristiwa buffer yang tersedia, dan menangani peristiwa yang hilang manajer presentasi

Jika aplikasi Anda akan mengeluarkan sajian yang melibatkan buffer presentasi yang telah digunakan di presentasi sebelumnya, maka Anda harus memastikan bahwa presentasi sebelumnya selesai untuk dirender dan menyajikan buffer presentasi lagi. API swapchain komposisi menyediakan beberapa mekanisme yang berbeda untuk memfasilitasi sinkronisasi ini. Yang termudah adalah peristiwa buffer presentasi yang tersedia, yang merupakan objek peristiwa NT yang disinyalir ketika buffer tersedia untuk digunakan (artinya, semua presentasi sebelumnya telah memasuki status pensiun atau pensiun) dan tidak ditandatangani sebaliknya. Contoh ini menggambarkan cara menggunakan peristiwa buffer yang tersedia untuk memilih buffer yang tersedia untuk disajikan. Ini juga menggambarkan cara mendengarkan kesalahan perangkat yang hilang yang secara moral setara dengan kehilangan perangkat DXGI, dan memerlukan rekreasi manajer presentasi.

Manajer presentasi kehilangan kesalahan

Manajer presentasi dapat hilang jika mengalami kesalahan secara internal yang tidak dapat dipulihkan. Aplikasi Anda harus menghancurkan manajer presentasi yang hilang, dan membuat yang baru untuk digunakan sebagai gantinya. Ketika manajer presentasi hilang, manajer presentasi akan berhenti menerima hadiah lebih lanjut. Setiap upaya untuk memanggil Sajikan pada manajer presentasi yang hilang akan mengembalikan PRESENTATION_ERROR_LOST. Namun, semua metode lain akan berfungsi secara normal. Ini untuk memastikan bahwa aplikasi Anda hanya perlu memeriksa PRESENTATION_ERROR_LOST panggilan saat ini, dan tidak perlu mengantisipasi/menangani kesalahan yang hilang pada setiap panggilan API. Jika aplikasi Anda ingin memeriksa, atau diberi tahu, kesalahan yang hilang di luar panggilan saat ini, maka aplikasi dapat menggunakan peristiwa yang hilang yang dikembalikan oleh IPresentationManager::GetLostEvent.

Menyajikan antrean dan waktu

Presentasi yang dikeluarkan oleh manajer presentasi dapat menentukan waktu tertentu untuk menargetkan. Waktu target ini adalah waktu ideal yang akan coba ditunjukkan oleh sistem saat ini. Karena layar diperbarui pada irama terbatas, saat ini tidak mungkin muncul dengan tepat pada waktu yang ditentukan, tetapi akan ditampilkan sedekat mungkin dengan waktu yang dapat dilakukan.

Waktu target ini adalah waktu relatif sistem, atau waktu sistem telah berjalan sejak diaktifkan, dalam ratusan unit nanodetik. Cara mudah untuk menghitung waktu saat ini adalah dengan mengkueri nilai QueryPerformanceCounter (QPC) saat ini, membaginya dengan frekuensi QPC sistem, dan mengalikannya dengan 10.000.000. Selain batas pembulatan dan presisi, ini akan menghitung waktu saat ini dalam unit yang berlaku. Sistem juga bisa mendapatkan waktu saat ini dengan memanggil MfGetSystemTime, atau QueryInterruptTimePrecise.

Setelah waktu saat ini diketahui, aplikasi Anda biasanya akan memancarkan presentasi pada peningkatan offset dari waktu saat ini.

Terlepas dari waktu target, hadiah selalu diproses dalam urutan antrean. Bahkan jika hadiah menargetkan waktu yang lebih lama dari sebelumnya, itu tidak akan diproses sampai setelah yang ada sebelumnya diproses. Ini pada dasarnya berarti bahwa hadiah apa pun yang tidak menargetkan di lain waktu daripada yang sebelumnya akan menimpa yang ada sebelumnya. Ini juga berarti bahwa jika aplikasi Anda ingin mengeluarkan hadiah sedini mungkin, maka itu tidak dapat menetapkan waktu target (dalam hal ini waktu target akan tetap seperti saat terakhir hadir), atau menetapkan waktu target 0. Keduanya akan memiliki efek yang sama. Jika aplikasi Anda tidak ingin menunggu hadiah sebelumnya selesai agar hadiah baru berlangsung, maka aplikasi harus membatalkan presentasi sebelumnya. Contoh di masa mendatang menjelaskan cara melakukan ini.

Present Queue and timing

T=3s: Sekarang 1, 2, 3 semua siap, dan 3, menjadi hadiah terakhir, menang. T=4s: Sekarang 4, 5, 6 semua siap, dan 6, menjadi hadiah terakhir, menang.

Diagram di atas mengilustrasikan contoh hadiah yang luar biasa dalam antrean saat ini. Menyajikan 1 target waktu 3 detik. Jadi tidak ada hadiah yang akan berlangsung sampai 3s. Namun, present 2 sebenarnya menargetkan waktu sebelumnya, 2s, dan menyajikan 3 target 3s juga. Dengan demikian, pada saat 3s, menyajikan 1, 2, dan 3 semuanya akan selesai, dan sekarang 3 akan menjadi saat ini untuk benar-benar berlaku. Setelah itu, saat ini berikutnya, 4, akan terpenuhi pada 4 detik, tetapi segera ditimpa oleh Present 5, yang menargetkan 0s, dan Present 6, yang tidak memiliki target waktu yang ditetapkan. Baik 5 dan 6 efektif berlaku sedini mungkin.

Contoh C++

bool SimpleEventSynchronizationExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
    _In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
    // Track a time we'll be presenting to below. Default to the current time, then increment by
    // 1/10th of a second every present.
    SystemInterruptTime presentTime;
    QueryInterruptTimePrecise(&presentTime.value);

    // Build an array of events that we can wait on to perform various actions in our work loop.
    vector<unique_event> waitEvents;

    // The lost event will be the first event in our list. This is an event that signifies that
    // something went wrong in the system (due to extreme conditions such as memory pressure, or
    // driver issues) that indicate that the presentation manager has been lost, and should no
    // longer be used, and instead should be recreated.
    unique_event lostEvent;
    pPresentationManager->GetLostEvent(&lostEvent);
    waitEvents.emplace_back(std::move(lostEvent));

    // Add each buffer's available event to the list of events we will be waiting on.
    for (UINT bufferIndex = 0; bufferIndex < presentationBuffers.size(); bufferIndex++)
    {
        unique_event availableEvent;
        presentationBuffers[bufferIndex]->GetAvailableEvent(&availableEvent);
        waitEvents.emplace_back(std::move(availableEvent));
    }

    // Iterate for 120 presents.
    constexpr UINT numberOfPresents = 120;
    for (UINT onPresent = 0; onPresent < numberOfPresents; onPresent++)
    {
        // Advance our present time 1/10th of a second in the future. Note the API accepts
        // time in 100ns units, or 1/1e7 of a second, meaning that 1 million units correspond to
        // 1/10th of a second.
        presentTime.value += 1'000'000;

        // Wait for the lost event or an available buffer. Since WaitForMultipleObjects prioritizes
        // lower-indexed events, it is recommended to put any higher importance events (like the
        // lost event) first, and then follow up with buffer available events.
        DWORD waitResult = WaitForMultipleObjects(
            static_cast<UINT>(waitEvents.size()),
            reinterpret_cast<HANDLE*>(waitEvents.data()),
            FALSE,
            INFINITE);

        // Failfast if the wait hit an error.
        FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= waitEvents.size());

        // Our lost event was the first event in the array. If this is signaled, the caller
        // should recreate the presentation manager. This is very similar to how Direct3D devices
        // can be lost. Assume our caller knows to handle this return value appropriately.
        if (waitResult == WAIT_OBJECT_0)
        {
            return false;
        }

        // Otherwise, compute the buffer corresponding to the available event that was signaled.
        UINT bufferIndex = waitResult - (WAIT_OBJECT_0 + 1);

        // Draw red to that buffer
        DrawColorToSurface(
            pD3D11Device,
            textures[bufferIndex],
            1.0f, // red
            0.0f, // green
            0.0f); // blue

        // Bind the presentation buffer to the presentation surface. Changes in this binding will take
        // effect on the next present, and the binding persists across presents. That is, any number
        // of subsequent presents will imply this binding until it is changed. It is completely fine
        // to only update buffers for a subset of the presentation surfaces owned by a presentation
        // manager on a given present - the implication is that it simply didn't update.
        //
        // Similarly, note that if your application were to call SetBuffer on the same presentation
        // surface multiple times without calling present, this is fine. The policy is last writer
        // wins.
        //
        // Your application may present without first binding a presentation surface to a buffer.
        // The result will be that presentation surface will simply have no content on screen,
        // similar to how DComp and WinComp surfaces appear in a tree before they are rendered to.
        // In that case system content will show through where the buffer would have been.
        //
        // Your application may also set a 'null' buffer binding after previously having bound a
        // buffer and present - the end result is the same as if your application had presented
        // without ever having set the content.
        pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());

        // Present at the targeted time. Note that a present can target only a single time. If an
        // application wants to updates two buffers at two different times, then it must present
        // two times.
        //
        // Presents are always processed in queue order. A present will not take effect before any
        // previous present in the queue, even if it targets an earlier time. In such a case, when
        // the previous present is processed, the next present will also be processed immediately,
        // and override that previous present.
        //
        // For this reason, if your application wishes to present "now" or "early as possible", then
        // it can simply present, without setting a target time. The implied target time will be 0,
        // and the new present will override the previous present.
        //
        // If your application wants to present truly "now", and not wait for previous presents in the
        // queue to be processed, then it will need to cancel previous presents. A future example
        // demonstrates how to do this.
        //
        // Your application will receive PRESENTATION_ERROR_LOST if it attempts to Present a lost
        // presentation manager. This is the only call that will return such an error. A lost
        // presentation manager functions normally in every other case, so applications need only
        // to handle this error at the time they call Present.
        pPresentationManager->SetTargetTime(presentTime);
        HRESULT hrPresent = pPresentationManager->Present();
        if (hrPresent == PRESENTATION_ERROR_LOST)
        {
            // Our presentation manager has been lost. Return 'false' to the caller to indicate that
            // the presentation manager should be recreated.
            return false;
        }
        else
        {
            FAIL_FAST_IF_FAILED(hrPresent);
        }
    }

    return true;
}

void DrawColorToSurface(
    _In_ ID3D11Device* pD3D11Device,
    _In_ const com_ptr_failfast<ID3D11Texture2D>& texture2D,
    _In_ float redValue,
    _In_ float greenValue,
    _In_ float blueValue)
{
    com_ptr_failfast<ID3D11DeviceContext> D3DDeviceContext;
    com_ptr_failfast<ID3D11RenderTargetView> renderTargetView;

    // Get the immediate context from the D3D11 device.
    pD3D11Device->GetImmediateContext(&D3DDeviceContext);

    // Create a render target view of the passed texture.
    auto resource = texture2D.query<ID3D11Resource>();
    FAIL_FAST_IF_FAILED(pD3D11Device->CreateRenderTargetView(
        resource.get(),
        nullptr,
        renderTargetView.addressof()));

    // Clear the texture with the specified color.
    float clearColor[4] = { redValue, greenValue, blueValue, 1.0f }; // red, green, blue, alpha
    D3DDeviceContext->ClearRenderTargetView(renderTargetView.get(), clearColor);
}

Contoh 12—Sinkronisasi tingkat lanjut—alur kerja pembatasan ke ukuran antrean saat ini yang luar biasa menggunakan pagar sinkronisasi saat ini, dan menangani pengelola presentasi kehilangan peristiwa

Contoh berikut menggambarkan bagaimana aplikasi Anda dapat mengirimkan sejumlah besar hadiah untuk masa depan, lalu tidur hingga jumlah hadiah yang masih menurun ke jumlah tertentu. Kerangka kerja seperti Windows Media Foundation membatasi cara ini untuk meminimalkan jumlah bangun CPU yang terjadi, sambil tetap memastikan bahwa antrean saat ini tidak habis (yang akan mencegah pemutaran yang lancar, dan menyebabkan kesalahan). Ini memiliki efek meminimalkan penggunaan daya CPU selama alur kerja presentasi. Aplikasi Anda akan mengantre jumlah maksimum presentasi (berdasarkan jumlah buffer presentasi yang telah mereka alokasikan), lalu tidur hingga tepat sebelum antrean saat ini habis untuk mengisi ulang antrean.

Contoh C++

bool FenceSynchronizationExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
    _In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
    // Track a time we'll be presenting to below. Default to the current time, then increment by
    // 1/10th of a second every present.
    SystemInterruptTime presentTime;
    QueryInterruptTimePrecise(&presentTime.value);

    // Get present retiring fence.
    com_ptr_failfast<ID3D11Fence> presentRetiringFence;
    FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentRetiringFence(
        IID_PPV_ARGS(&presentRetiringFence)));

    // Get the lost event to query before presentation.
    unique_event lostEvent;
    pPresentationManager->GetLostEvent(&lostEvent);

    // Create an event to synchronize to our queue depth with. We'll use Direct3D to signal this event
    // when our synchronization fence indicates reaching a specific present.
    unique_event presentQueueSyncEvent;
    presentQueueSyncEvent.create(EventOptions::ManualReset);

    // Cycle the present queue 10 times.
    constexpr UINT numberOfPresentRefillCycles = 10;
    for (UINT onRefillCycle = 0; onRefillCycle < numberOfPresentRefillCycles; onRefillCycle++)
    {
        // Fill up presents for all presentation buffers. We compare the presentation manager's
        // next present ID to the present confirmed fence's value to figure out how
        // far ahead we are. We stop when we've issued presents for all buffers.
        while ((pPresentationManager->GetNextPresentId() -
                presentRetiringFence->GetCompletedValue()) < presentationBuffers.size())
        {
            // Present buffers in cyclical pattern. We can figure out the current buffer to
            // present by taking the modulo of the next present ID by the number of buffers. Note that the
            // first present of a presentation manager always has a present ID of 1 and increments by 1 on
            // each subsequent present. A present ID of 0 is conceptually meant to indicate that "no
            // presents have taken place yet".
            UINT bufferIndex = static_cast<UINT>(
                pPresentationManager->GetNextPresentId() % presentationBuffers.size());

            // Assert that the passed buffer is tracked as available for presentation. Because we throttle
            // based on the total number of buffers, this should always be true.
            NT_ASSERT(presentationBuffers[bufferIndex]->IsAvailable());

            // Advance our present time 1/10th of a second in the future.
            presentTime.value += 1'000'000;

            // Draw red to the texture.
            DrawColorToSurface(
                pD3D11Device,
                textures[bufferIndex],
                1.0f, // red
                0.0f, // green
                0.0f); // blue

            // Bind the presentation buffer to the presentation surface.
            pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());

            // Present at the targeted time.
            pPresentationManager->SetTargetTime(presentTime);
            HRESULT hrPresent = pPresentationManager->Present();
            if (hrPresent == PRESENTATION_ERROR_LOST)
            {
                // Our presentation manager has been lost. Return 'false' to the caller to indicate that
                // the presentation manager should be recreated.
                return false;
            }
            else
            {
                FAIL_FAST_IF_FAILED(hrPresent);
            }
        };

        // Now that the buffer is full, go to sleep until the present queue has been drained to
        // the desired queue depth. To figure out the appropriate present to wake on, we subtract
        // the desired wake queue depth from the presentation manager's last present ID. We
        // use Direct3D's SetEventOnCompletion to signal our wait event when that particular present
        // is retiring, and then wait on that event. Note that the semantic of SetEventOnCompletion
        // is such that even if we happen to call it after the fence has already reached the
        // requested value, the event will be set immediately.
        constexpr UINT wakeOnQueueDepth = 2;
        presentQueueSyncEvent.ResetEvent();
        FAIL_FAST_IF_FAILED(presentRetiringFence->SetEventOnCompletion(
            pPresentationManager->GetNextPresentId() - 1 - wakeOnQueueDepth,
            presentQueueSyncEvent.get()));

        HANDLE waitHandles[] = { lostEvent.get(), presentQueueSyncEvent.get() };
        DWORD waitResult = WaitForMultipleObjects(
            ARRAYSIZE(waitHandles),
            waitHandles,
            FALSE,
            INFINITE);

        // Failfast if we hit an error during our wait.
        FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= ARRAYSIZE(waitHandles));

        if (waitResult == WAIT_OBJECT_0)
        {
            // The lost event was signaled - return 'false' to the caller to indicate that
            // the presentation manager was lost.
            return false;
        }

        // Iterate into another refill cycle.
    }

    return true;
}

Contoh 13—Gangguan VSync dan dukungan antrean balik perangkat keras

Bentuk baru manajemen antrean balik yang disebut antrean balik perangkat keras telah diperkenalkan bersama API ini, pada dasarnya memungkinkan perangkat keras GPU untuk mengelola hadir sepenuhnya secara independen dari CPU. Manfaat utama dari ini adalah efisiensi daya. Ketika CPU tidak perlu terlibat dalam proses presentasi, lebih sedikit daya yang digambar.

Kelemahan untuk memiliki handel GPU yang disajikan secara independen adalah bahwa status CPU tidak dapat segera mencerminkan ketika hadiah ditampilkan. konsep API swapchain komposisi seperti peristiwa buffer yang tersedia, pagar sinkronisasi, dan statistik sajian tidak akan segera diperbarui. Sebagai gantinya, GPU hanya akan me-refresh status CPU secara berkala tentang apa yang disajikan telah ditampilkan. Ini berarti bahwa umpan balik ke aplikasi mengenai status saat ini akan datang dengan latensi.

Aplikasi Anda biasanya akan peduli ketika beberapa hadiah ditampilkan, tetapi tidak peduli begitu banyak tentang hadiah lain. Misalnya, jika masalah aplikasi Anda 10 muncul, aplikasi mungkin memutuskan bahwa aplikasi ingin mengetahui kapan tanggal 8 ditampilkan, sehingga dapat mulai mengisi ulang antrean saat ini lagi. Dalam hal ini, satu-satunya hadiah yang benar-benar menginginkan umpan balik dari adalah yang ke-8. Ini tidak berencana untuk melakukan apa pun ketika 1-7 atau 9 ditampilkan.

Apakah menyajikan status CPU pembaruan bergantung pada apakah perangkat keras GPU dikonfigurasi untuk menghasilkan gangguan VSync ketika ada ditampilkan. Gangguan VSync ini membangunkan CPU jika belum aktif, dan CPU kemudian menjalankan kode tingkat kernel khusus untuk memperbarui dirinya sendiri pada hadiah yang telah terjadi pada GPU sejak terakhir kali diperiksa, pada gilirannya memperbarui mekanisme umpan balik seperti peristiwa buffer yang tersedia, pagar pensiun saat ini, dan menyajikan statistik.

Untuk memungkinkan aplikasi Anda secara eksplisit menunjukkan presentasi mana yang harus mengeluarkan gangguan VSync, manajer presentasi mengekspos metode IPresentationManager::ForceVSyncInterrupt , yang menentukan apakah presentasi berikutnya harus mengeluarkan interupsi VSync atau tidak. Pengaturan ini berlaku untuk semua presentasi di masa mendatang hingga diubah, seperti IPresentationManager::SetTargetTime dan IPresentationManager::SetPreferredPresentDuration.

Jika pengaturan ini diaktifkan pada saat tertentu, maka perangkat keras akan segera memberi tahu CPU ketika ada ditampilkan, menggunakan lebih banyak daya, tetapi memastikan bahwa CPU segera diberi tahu ketika ada yang terjadi untuk memungkinkan aplikasi merespons secepat mungkin. Jika pengaturan ini dinonaktifkan pada saat tertentu, maka sistem akan diizinkan untuk menungguhkan pembaruan CPU ketika saat ini ditampilkan—menghemat daya, tetapi menuguhkan umpan balik.

Aplikasi Anda biasanya tidak akan memaksa gangguan VSync untuk hadiah apa pun kecuali hadiah yang ingin disinkronkan dengannya. Dalam contoh di atas, karena aplikasi Anda ingin bangun ketika hadir ke-8 ditunjukkan untuk mengisi ulang antreannya saat ini, aplikasi akan meminta agar menyajikan 8 sinyal gangguan VSync, tetapi menyajikan 1-7 dan sekarang 9 tidak.

Secara default, jika aplikasi Anda tidak mengonfigurasi pengaturan ini, maka manajer presentasi akan selalu memberi sinyal gangguan VSync ketika setiap presentasi ditampilkan. Aplikasi yang tidak peduli tentang penggunaan daya, atau tidak menyadari dukungan antrean balik perangkat keras, tidak dapat memanggil ForceVSyncInterrupt, dan mereka akan dijamin untuk disinkronkan dengan benar sebagai hasilnya. Aplikasi yang mengetahui dukungan antrean balik perangkat keras dapat secara eksplisit mengontrol pengaturan ini untuk meningkatkan efisiensi daya.

Berikut ini adalah diagram yang menjelaskan perilaku API sehubungan dengan pengaturan interupsi VSync.

VSync interrupts

Contoh C++

bool ForceVSyncInterruptPresentsExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
    _In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
    // Track a time we'll be presenting to below. Default to the current time, then increment by
    // 1/10th of a second every present.
    SystemInterruptTime presentTime;
    QueryInterruptTimePrecise(&presentTime.value);

    // Get present retiring fence.
    com_ptr_failfast<ID3D11Fence> presentRetiringFence;
    FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentRetiringFence(
        IID_PPV_ARGS(&presentRetiringFence)));

    // Get the lost event to query before presentation.
    unique_event lostEvent;
    pPresentationManager->GetLostEvent(&lostEvent);

    // Create an event to synchronize to our queue depth with. We will use Direct3D to signal this event
    // when our synchronization fence indicates reaching a specific present.
    unique_event presentQueueSyncEvent;
    presentQueueSyncEvent.create(EventOptions::ManualReset);

    // Issue 10 presents, and wake when the present queue is 2 entries deep (which happens when
    // present 7 is retiring).
    constexpr UINT wakeOnQueueDepth = 2;
    constexpr UINT numberOfPresents = 10;
    const UINT presentIdToWakeOn = numberOfPresents - 1 - wakeOnQueueDepth;
    while (pPresentationManager->GetNextPresentId() <= numberOfPresents)
    {
        UINT bufferIndex = static_cast<UINT>(
            pPresentationManager->GetNextPresentId() % presentationBuffers.size());

        // Advance our present time 1/10th of a second in the future.
        presentTime.value += 1'000'000;

        // Draw red to the texture.
        DrawColorToSurface(
            pD3D11Device,
            textures[bufferIndex],
            1.0f, // red
            0.0f, // green
            0.0f); // blue

        // Bind the presentation buffer to the presentation surface.
        pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());

        // Present at the targeted time.
        pPresentationManager->SetTargetTime(presentTime);

        // If this present is not going to retire the present that we want to wake on when it is shown, then
        // we don't need immediate updates to buffer available events, present retiring fence, or present
        // statistics. As such, we can mark it as not requiring a VSync interrupt, to allow for greater
        // power efficiency on machines with hardware flip queue support.
        bool forceVSyncInterrupt = (pPresentationManager->GetNextPresentId() == (presentIdToWakeOn + 1));
        pPresentationManager->ForceVSyncInterrupt(forceVSyncInterrupt);

        HRESULT hrPresent = pPresentationManager->Present();
        if (hrPresent == PRESENTATION_ERROR_LOST)
        {
            // Our presentation manager has been lost. Return 'false' to the caller to indicate that
            // the presentation manager should be recreated.
            return false;
        }
        else
        {
            FAIL_FAST_IF_FAILED(hrPresent);
        }
    }

    // Now that the buffer is full, go to sleep until presentIdToWakeOn has begun retiring. We
    // configured the subsequent present to force a VSync interrupt when it is shown, which will ensure
    // this wait is completed immediately.
    presentQueueSyncEvent.ResetEvent();
    FAIL_FAST_IF_FAILED(presentRetiringFence->SetEventOnCompletion(
        presentIdToWakeOn,
        presentQueueSyncEvent.get()));

    HANDLE waitHandles[] = { lostEvent.get(), presentQueueSyncEvent.get() };
    DWORD waitResult = WaitForMultipleObjects(
        ARRAYSIZE(waitHandles),
        waitHandles,
        FALSE,
        INFINITE);

    // Failfast if we hit an error during our wait.
    FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= ARRAYSIZE(waitHandles));

    if (waitResult == WAIT_OBJECT_0)
    {
        // The lost event was signaled - return 'false' to the caller to indicate that
        // the presentation manager was lost.
        return false;
    }

    return true;
}

Contoh 14—Pembatalan menyajikan yang dijadwalkan untuk masa mendatang

Aplikasi media yang dalam antrean hadir untuk masa depan mungkin memutuskan untuk membatalkan hadiah yang sebelumnya telah mereka terbitkan. Ini mungkin terjadi, misalnya, jika aplikasi Anda memutar kembali video, telah mengeluarkan sejumlah besar bingkai untuk masa depan, dan pengguna memutuskan untuk menjeda pemutaran video. Dalam hal ini, aplikasi Anda akan ingin tetap berpegang pada bingkai saat ini, dan membatalkan bingkai di masa mendatang yang belum menjadi antrean. Ini juga dapat terjadi jika aplikasi media memutuskan untuk memindahkan pemutaran ke titik yang berbeda dalam video. Dalam hal ini, aplikasi Anda akan ingin membatalkan semua hadiah yang belum menjadi antrean untuk posisi lama dalam video, dan menggantinya dengan hadiah untuk posisi baru. Dalam hal ini, setelah membatalkan presentasi sebelumnya, aplikasi Anda dapat mengeluarkan hadiah baru di masa mendatang yang sesuai dengan titik baru dalam video.

Contoh C++

void PresentCancelExample(
    _In_ IPresentationManager* pPresentationManager,
    _In_ UINT64 firstPresentIDToCancelFrom)

{
    // Assume we've issued a number of presents in the future. Something happened in the app, and
    // we want to cancel the issued presents that occur after a specified time or present ID. This
    // may happen, for example, when the user pauses playback from inside a media application. The
    // application will want to cancel all presents posted targeting beyond the pause time. The
    // cancel will apply to all previously posted presents whose present IDs are at least
    // 'firstPresentIDToCancelFrom'. Note that Present IDs are always unique, and never recycled,
    // so even if a present is canceled, no subsequent present will ever reuse its present ID.
    //
    // Also note that if some presents we attempt to cancel can't be canceled because they've
    // already started queueing, then no error will be returned, they simply won't be canceled as
    // requested. Cancelation takes a "best effort" approach.
    FAIL_FAST_IF_FAILED(pPresentationManager->CancelPresentsFrom(firstPresentIDToCancelFrom));

    // In the case where the media application scrubbed to a different position in the video, it may now
    // choose to issue new presents to replace the ones canceled. This is not illustrated here, but
    // previous examples that demonstrate presentation show how this may be achieved.
}

Contoh 15—Operasi mengubah ukuran buffer yang terhuyung-katung untuk meningkatkan performa

Contoh ini menggambarkan bagaimana aplikasi Anda dapat mengubah ukuran buffer yang terhuyung-huyung untuk meningkatkan performa melalui DXGI. Ingat contoh perubahan ukuran kami sebelumnya, di mana klien layanan streaming film ingin mengubah resolusi pemutaran dari 720p menjadi 1080p. Di DXGI, aplikasi akan melakukan operasi pengubahan ukuran pada swapchain DXGI, yang secara atomik akan membuang semua buffer sebelumnya, dan merealokasi semua buffer 1080p baru sekaligus, dan menambahkannya ke swapchain. Jenis perubahan ukuran atom buffer ini mahal, dan berpotensi memakan waktu lama dan menyebabkan gangguan. API baru memberikan kontrol yang lebih baik atas buffer presentasi individual. Dengan demikian, buffer dapat dialokasikan dan diganti satu per satu di beberapa hadiah untuk membagi beban kerja dari waktu ke waktu. Ini memiliki lebih sedikit dampak pada yang ada dan jauh lebih kecil kemungkinannya untuk menyebabkan kesalahan. Pada dasarnya, untuk manajer presentasi dengan buffer presentasi n, untuk presentasi 'n', aplikasi Anda dapat menghapus buffer presentasi lama pada ukuran lama, mengalokasikan buffer presentasi baru pada ukuran baru, dan menyajikannya. Setelah 'n' hadir, semua buffer akan berada pada ukuran baru.

Contoh C++

bool StaggeredResizeExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ vector<com_ptr_failfast<ID3D11Texture2D>> textures,
    _In_ vector<com_ptr_failfast<IPresentationBuffer>> presentationBuffers)
{
    // Track a time we'll be presenting to below. Default to the current time, then increment by
    // 1/10th of a second every present.
    SystemInterruptTime presentTime;
    QueryInterruptTimePrecise(&presentTime.value);

    // Assume textures/presentationBuffers vector contains 10 100x100 buffers, and we want to resize
    // our swapchain to 200x200. Instead of reallocating 10 200x200 buffers all at once,
    // like DXGI does today, we can stagger the reallocation across multiple presents. For
    // each present, we can allocate one buffer at the new size, and replace one old buffer
    // at the old size with the new one at the new size. After 10 presents, we will have
    // reallocated all our buffers, and we will have done so in a manner that's much less
    // likely to produce delays or glitches.
    constexpr UINT numberOfBuffers = 10;
    for (UINT bufferIndex = 0; bufferIndex < numberOfBuffers; bufferIndex++)
    {
        // Advance our present time 1/10th of a second in the future.
        presentTime.value += 1'000'000;

        // Release the old texture/presentation buffer at the presented index.
        auto& replacedTexture = textures[bufferIndex];
        auto& replacedPresentationBuffer = presentationBuffers[bufferIndex];
        replacedTexture.reset();
        replacedPresentationBuffer.reset();

        // Create a new texture/presentation buffer in its place.
        AddNewPresentationBuffer(
            pD3D11Device,
            pPresentationManager,
            200, // Buffer width
            200, // Buffer height
            &replacedTexture,
            &replacedPresentationBuffer);

        // Draw red to the new texture.
        DrawColorToSurface(
            pD3D11Device,
            replacedTexture,
            1.0f, // red
            0.0f, // green
            0.0f); // blue

        // Bind the presentation buffer to the presentation surface.
        pPresentationSurface->SetBuffer(replacedPresentationBuffer.get());

        // Present at the targeted time.
        pPresentationManager->SetTargetTime(presentTime);
        HRESULT hrPresent = pPresentationManager->Present();
        if (hrPresent == PRESENTATION_ERROR_LOST)
        {
            // Our presentation manager has been lost. Return 'false' to the caller to indicate that
            // the presentation manager should be recreated.
            return false;
        }
        else
        {
            FAIL_FAST_IF_FAILED(hrPresent);
        }
    }

    return true;
}

Contoh 16—Membaca dan memproses statistik sajikan

Laporan API menyajikan statistik untuk setiap hadiah yang dikirimkan. Pada tingkat tinggi, statistik saat ini adalah mekanisme umpan balik yang menjelaskan bagaimana hadiah tertentu diproses atau ditunjukkan oleh sistem. Ada berbagai jenis statistik yang dapat didaftarkan aplikasi Anda untuk menerima, dan infrastruktur statistik di API itu sendiri dimaksudkan untuk dapat diperluas, sehingga lebih banyak jenis statistik dapat ditambahkan di masa depan. API ini menjelaskan cara membaca kembali statistik, dan menjelaskan jenis statistik yang ditentukan hari ini, dan informasi apa yang mereka sampaikan pada tingkat tinggi.

Contoh C++

// This is an identifier we'll assign to our presentation surface that will be used to reference that
// presentation surface in statistics. This is to avoid referring to a presentation surface by pointer
// in a statistics structure, which has unclear refcounting and lifetime semantics.
static constexpr UINT_PTR myPresentedContentTag = 12345;

bool StatisticsExample(
    _In_ ID3D11Device* pD3D11Device,
    _In_ IPresentationManager* pPresentationManager,
    _In_ IPresentationSurface* pPresentationSurface,
    _In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
    _In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
    // Track a time we'll be presenting to below. Default to the current time, then increment by
    // 1/10th of a second every present.
    SystemInterruptTime presentTime;
    QueryInterruptTimePrecise(&presentTime.value);

    // Register to receive 3 types of statistics.
    FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
        PresentStatisticsKind_CompositionFrame,
        true));
    FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
        PresentStatisticsKind_PresentStatus,
        true));
    FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
        PresentStatisticsKind_IndependentFlipFrame,
        true));

    // Stats come back referencing specific presentation surfaces. We assign 'tags' to presentation
    // surfaces in the API that statistics will use to reference the presentation surface in a
    // statistic.
    pPresentationSurface->SetTag(myPresentedContentTag);

    // Build an array of events that we can wait on.
    vector<unique_event> waitEvents;

    // The lost event will be the first event in our list. This is an event that signifies that
    // something went wrong in the system (due to extreme conditions like memory pressure, or
    // driver issues) that indicate that the presentation manager has been lost, and should no
    // longer be used, and instead should be recreated.
    unique_event lostEvent;
    FAIL_FAST_IF_FAILED(pPresentationManager->GetLostEvent(&lostEvent));
    waitEvents.emplace_back(std::move(lostEvent));

    // The statistics event will be the second event in our list. This event will be signaled
    // by the presentation manager when there are statistics to read back.
    unique_event statisticsEvent;
    FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentStatisticsAvailableEvent(&statisticsEvent));
    waitEvents.emplace_back(std::move(statisticsEvent));

    // Add each buffer's available event to the list of events we will be waiting on.
    for (UINT bufferIndex = 0; bufferIndex < presentationBuffers.size(); bufferIndex++)
    {
        unique_event availableEvent;
        presentationBuffers[bufferIndex]->GetAvailableEvent(&availableEvent);
        waitEvents.emplace_back(std::move(availableEvent));
    }

    // Iterate our workflow 120 times.
    constexpr UINT iterationCount = 120;
    for (UINT i = 0; i < iterationCount; i++)
    {
        // Wait for an event to be signaled.
        DWORD waitResult = WaitForMultipleObjects(
            static_cast<UINT>(waitEvents.size()),
            reinterpret_cast<HANDLE*>(waitEvents.data()),
            FALSE,
            INFINITE);

        // Failfast if the wait hit an error.
        FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= waitEvents.size());

        // Our lost event was the first event in the array. If this is signaled, then the caller
        // should recreate the presentation manager. This is very similar to how Direct3D devices
        // can be lost. Assume our caller knows to handle this return value appropriately.
        if (waitResult == WAIT_OBJECT_0)
        {
            return false;
        }

        // The second event in the array is the statistics event. If this event is signaled,
        // read and process our statistics.
        if (waitResult == (WAIT_OBJECT_0 + 1))
        {
            StatisticsExample_ProcessStatistics(pPresentationManager);
        }
        // Otherwise, the event corresponds to a buffer available event that is signaled.
        // Compute the buffer for the available event that was signaled and present a
        // frame.
        else
        {
            DWORD bufferIndex = waitResult - (WAIT_OBJECT_0 + 2);

            // Draw red to the texture.
            DrawColorToSurface(
                pD3D11Device,
                textures[bufferIndex],
                1.0f, // red
                0.0f, // green
                0.0f); // blue

            // Bind the texture to the presentation surface.
            pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());

            // Advance our present time 1/10th of a second in the future.
            presentTime.value += 1'000'000;

            // Present at the targeted time.
            pPresentationManager->SetTargetTime(presentTime);
            HRESULT hrPresent = pPresentationManager->Present();
            if (hrPresent == PRESENTATION_ERROR_LOST)
            {
                // Our presentation manager has been lost. Return 'false' to the caller to indicate that
                // the presentation manager should be recreated.
                return false;
            }
            else
            {
                FAIL_FAST_IF_FAILED(hrPresent);
            }
        }
    }

    return true;
}

void StatisticsExample_ProcessStatistics(
    _In_ IPresentationManager* pPresentationManager)
{
    // Dequeue a single present statistics item. This will return the item
    // and pop it off the queue of statistics.
    com_ptr_failfast<IPresentStatistics> presentStatisticsItem;
    pPresentationManager->GetNextPresentStatistics(&presentStatisticsItem);

    // Read back the present ID this corresponds to.
    UINT64 presentId = presentStatisticsItem->GetPresentId();
    UNREFERENCED_PARAMETER(presentId);

    // Switch on the type of statistic this item corresponds to.
    switch (presentStatisticsItem->GetKind())
    {
        case PresentStatisticsKind_PresentStatus:
        {
            // Present status statistics describe whether a given present was queued for display,
            // skipped due to some future present being a better candidate to display on a given
            // frame, or canceled via the API.
            auto presentStatusStatistics = presentStatisticsItem.query<IPresentStatusStatistics>();

            // Read back the status
            PresentStatus status = presentStatusStatistics->GetPresentStatus();
            UNREFERENCED_PARAMETER(status);

            // Possible values for status:
            //   PresentStatus_Queued
            //   PresentStatus_Skipped
            //   PresentStatus_Canceled;

            // Depending on the status returned, your application can adjust their workflow
            // accordingly. For example, if your application sees that a large percentage of their
            // presents are skipped, it means they are presenting more frames than the system can
            // display. In such a case, your application might decided to lower the rate at which
            // you present frames.
        }
        break;

        case PresentStatisticsKind_CompositionFrame:
        {
            // Composition frame statistics describe how a given present was used in a DWM frame.
            // It includes information such as which monitors displayed the present, whether the
            // present was composed or directly scanned out via an MPO plane, and rendering
            // properties such as what transforms were applied to the rendering. Composition
            // frame statistics are not issued for iflip presents - only for presents issued by the
            // compositor. iflip presents have their own type of statistic (described next).
            auto compositionFrameStatistics =
                presentStatisticsItem.query<ICompositionFramePresentStatistics>();

            // Stats should come back for the present statistics item that we tagged earlier.
            NT_ASSERT(compositionFrameStatistics->GetContentTag() == myPresentedContentTag);

            // The composition frame ID indicates the DWM frame ID that the present was used
            // in.
            CompositionFrameId frameId = compositionFrameStatistics->GetCompositionFrameId();

            // Get the display instance array to indicate which displays showed the present. Each
            // instance of the presentation surface will have an entry in this array. For example,
            // if your application adds the same presentation surface to four different visuals in the
            // visual tree, then each instance in the tree will have an entry in the display instance
            // array. Similarly, if the presentation surface shows up on multiple monitors, then each
            // monitor instance will be accounted for in the display instance array that is
            // returned.
            //
            // Note that the pointer returned from GetDisplayInstanceArray is valid for the
            // lifetime of the ICompositionFramePresentStatistics. Your application must not attempt
            // to read this pointer after the ICompositionFramePresentStatistics has been released
            // to a refcount of 0.
            UINT displayInstanceArrayCount;
            const CompositionFrameDisplayInstance* pDisplayInstances;
            compositionFrameStatistics->GetDisplayInstanceArray(
                &displayInstanceArrayCount,
                &pDisplayInstances);

            for (UINT i = 0; i < displayInstanceArrayCount; i++)
            {
                const auto& displayInstance = pDisplayInstances[i];

                // The following are fields that are available in a display instance.

                // The LUID, VidPnSource, and unique ID of the output and its owning
                // adapter. The unique ID will be bumped when a LUID/VidPnSource is
                // recycled. Applications should use the unique ID to determine when
                // this happens so that they don't try and correlate stats from one
                // monitor with another.
                displayInstance.outputAdapterLUID;
                displayInstance.outputVidPnSourceId;
                displayInstance.outputUniqueId;

                // The instanceKind field indicates how the present was used. It
                // indicates that the present was composed (rendered to DWM's backbuffer),
                // scanned out (via MPO/DFlip) or composed to an intermediate buffer by DWM
                // for effects.
                displayInstance.instanceKind;

                // The finalTransform field indicates the transform at which the present was
                // shown in world space. It will include all ancestor visual transforms and
                // can be used to know how it was rendered in the global visual tree.
                displayInstance.finalTransform;

                // The requiredCrossAdapterCopy field indicates whether or not we needed to
                // copy your application's buffer to a different adapter in order to display
                // it. Applications should use this to determine whether or not they should
                // reallocate their buffers onto a different adapter for better performance.
                displayInstance.requiredCrossAdapterCopy;

                // The colorSpace field indicates the colorSpace of the output that the
                // present was rendered to.
                displayInstance.colorSpace;

                // For example, if your application sees that the finalTransform is scaling your
                // content by 2x, you might elect to pre-render that scale into your presentation
                // surface, and then add a 1/2 scale. At which point, the finalTransform should
                // be 1x, and some MPO hardware will be more likely to MPO a presentation surface
                // with a 1x scale applied, since some hardware has a maximum they are able to
                // scale in an MPO plane. Similarly, if your application's content is being scaled
                // down on screen, you may wish to simply render its content at a
                // smaller scale to conserve resources, and apply an enlargement transform.
            }

            // Additionally, we can use the CompositionFrameId reported by the statistic
            // to query timing-related information about that specific frame via the new
            // composition timing API, such as when that frame showed up on screen.
            // Note this is achieved using a separate API from the composition swapchain API, but
            // using the composition frame ID reported in the composition swapchain API to
            // properly specify which frame your application wants timing information from.
            COMPOSITION_FRAME_TARGET_STATS frameTargetStats;
            COMPOSITION_TARGET_STATS targetStats[4];
            frameTargetStats.targetCount = ARRAYSIZE(targetStats);
            frameTargetStats.targetStats = targetStats;

            // Specify the frameId that we got from stats in order to pass to the call
            // below and retrieve timing information about that frame.
            frameTargetStats.frameId = frameId;
            FAIL_FAST_IF_FAILED(DCompositionGetTargetStatistics(1, &frameTargetStats));

            // If the frameTargetStats comes back with a 0 frameId, it means the frame isn't
            // part of statistics. This might mean that it has expired out of
            // DCompositionGetTargetStatistics history, but that call keeps a history buffer
            // roughly equivalent to ~5 seconds worth of frame history, so if your application
            // is processing statistics from the presentation manager relatively regularly,
            // by all accounts it shouldn't worry about DCompositionGetTargetStatistics
            // history expiring. The more likely scenario when this occurs is that it's too
            // early, and that this frame isn't part of statistics YET. In that case, your application
            // should defer processing for this frame, and try again later. For the purposes
            // if sample brevity, we don't bother trying again here. A good method to use would
            // be to add this present info to a list of presents that we haven't gotten target
            // statistics for yet, and try again for all presents in that list any time we get
            // a new PresentStatisticsKind_CompositionFrame for a future frame.
            if (frameTargetStats.frameId == frameId)
            {
                // The targetCount will represent the count of outputs the given frame
                // applied to.
                frameTargetStats.targetCount;

                // The targetTime corresponds to the wall clock QPC time DWM was
                // targeting for the frame.
                frameTargetStats.targetTime;

                for (UINT i = 0; i < frameTargetStats.targetCount; i++)
                {
                    const auto& targetStat = frameTargetStats.targetStats[i];

                    // The present time corresponds to the targeted present time of the composition
                    // frame.
                    targetStat.presentTime;

                    // The target ID corresponds to the LUID/VidPnSourceId/Unique ID for the given
                    // target.
                    targetStat.targetId;

                    // The completedStats convey information about the time a compositor frame was
                    // completed, which marks the time any of its associated composition swapchain API
                    // presents entered the displayed state. In particular, your application might wish
                    // to use the 'time' to know if a present showed at a time it expected.
                    targetStat.completedStats.presentCount;
                    targetStat.completedStats.refreshCount;
                    targetStat.completedStats.time;

                    // There is various other timing statistics information conveyed by
                    // DCompositionGetTargetStatistics.
                }
            }
        }
        break;

        case PresentStatisticsKind_IndependentFlipFrame:
        {
            // Independent flip frame statistics describe a present that was shown via
            // independent flip.
            auto independentFlipFrameStatistics =
                presentStatisticsItem.query<IIndependentFlipFramePresentStatistics>();

            // Stats should come back for the present statistics item that we tagged earlier.
            NT_ASSERT(independentFlipFrameStatistics->GetContentTag() == myPresentedContentTag);

            // The driver-approved present duration describes the custom present duration that was
            // approved by the driver and applied during the present. This is how, for example, media
            // will know whether or not they got 24hz mode for their content if they requested it.
            independentFlipFrameStatistics->GetPresentDuration();

            // The displayed time is the time the present was confirmed to have been shown
            // on screen.
            independentFlipFrameStatistics->GetDisplayedTime();

            // The adapter LUID/VidpnSource ID describe the output on which the present took
            // place. Unlike the composition statistic above, we don't report a unique ID here
            // because a monitor recycle would kick the presentation out of iflip.
            independentFlipFrameStatistics->GetOutputAdapterLUID();
            independentFlipFrameStatistics->GetOutputVidPnSourceId();
        }
        break;
    }
}

Contoh 17—menggunakan lapisan abstraksi untuk disajikan menggunakan API swapchain komposisi baru atau DXGI dari aplikasi Anda

Mengingat persyaratan sistem/driver yang lebih tinggi dari API swapchain komposisi baru, aplikasi Anda mungkin ingin menggunakan DXGI jika API baru tidak didukung. Untungnya cukup mudah untuk memperkenalkan lapisan abstraksi yang menggunakan API apa pun yang tersedia untuk melakukan presentasi. Contoh berikut mengilustrasikan cara mencapainya.

Contoh C++

// A base class presentation provider. We'll provide implementations using both DXGI and the new
// composition swapchain API, which the example will use based on what's supported.
class PresentationProvider
{
public:
    virtual void GetBackBuffer(
        _Out_ ID3D11Texture2D** ppBackBuffer) = 0;

    virtual void Present(
        _In_ SystemInterruptTime presentationTime) = 0;

    virtual bool ReadStatistics(
        _Out_ UINT* pPresentCount,
        _Out_ UINT64* pSyncQPCTime) = 0;

    virtual ID3D11Device* GetD3D11DeviceNoRef()
    {
        return m_d3dDevice.get();
    }

protected:
    com_ptr_failfast<ID3D11Device> m_d3dDevice;
};

// An implementation of PresentationProvider using a DXGI swapchain to provide presentation
// functionality.
class DXGIProvider :
    public PresentationProvider
{
public:
    DXGIProvider(
        _In_ UINT width,
        _In_ UINT height,
        _In_ UINT bufferCount)
    {
        com_ptr_failfast<IDXGIAdapter> dxgiAdapter;
        com_ptr_failfast<IDXGIFactory7> dxgiFactory;
        com_ptr_failfast<IDXGISwapChain1> dxgiSwapchain;
        com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;

        // Direct3D device creation flags.
        UINT deviceCreationFlags =
            D3D11_CREATE_DEVICE_BGRA_SUPPORT |
            D3D11_CREATE_DEVICE_SINGLETHREADED;

        // Create the Direct3D device.
        FAIL_FAST_IF_FAILED(D3D11CreateDevice(
            nullptr,                   // No adapter
            D3D_DRIVER_TYPE_HARDWARE,  // Hardware device
            nullptr,                   // No module
            deviceCreationFlags,       // Device creation flags
            nullptr, 0,                // Highest available feature level
            D3D11_SDK_VERSION,         // API version
            &m_d3dDevice,              // Resulting interface pointer
            nullptr,                   // Actual feature level
            &d3dDeviceContext));       // Device context

        // Make our way from the Direct3D device to the DXGI factory.
        auto dxgiDevice = m_d3dDevice.query<IDXGIDevice>();
        FAIL_FAST_IF_FAILED(dxgiDevice->GetParent(IID_PPV_ARGS(&dxgiAdapter)));
        FAIL_FAST_IF_FAILED(dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)));

        // Create a swapchain matching the desired parameters.
        DXGI_SWAP_CHAIN_DESC1 desc = {};
        desc.Width = width;
        desc.Height = height;
        desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;
        desc.BufferCount = bufferCount;
        desc.Scaling = DXGI_SCALING_STRETCH;
        desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
        desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
        FAIL_FAST_IF_FAILED(dxgiFactory->CreateSwapChainForComposition(
            m_d3dDevice.get(),
            &desc,
            nullptr,
            &dxgiSwapchain));

        // Store the swapchain.
        dxgiSwapchain.query_to(&m_swapchain);
    }

    void GetBackBuffer(
        _Out_ ID3D11Texture2D** ppBackBuffer) override
    {
        // Get the backbuffer directly from the swapchain.
        FAIL_FAST_IF_FAILED(m_swapchain->GetBuffer(
            m_swapchain->GetCurrentBackBufferIndex(),
            IID_PPV_ARGS(ppBackBuffer)));
    }

    void Present(
        _In_ SystemInterruptTime presentationTime) override
    {
        // Convert the passed presentation time to a present interval. The implementation is
        // not provided here, as there's a great deal of complexity around computing this
        // most accurately, but it essentially boils down to taking the time from now and
        // figuring out the number of vblanks that corresponds to that time duration.
        UINT vblankIntervals = ComputePresentIntervalFromTime(presentationTime);

        // Issue a present to the swapchain. If we wanted to allow for a time to be specified,
        // code here could convert the time to a present duration, which could be passed here.
        FAIL_FAST_IF_FAILED(m_swapchain->Present(vblankIntervals, 0));
    }

    bool ReadStatistics(
        _Out_ UINT* pPresentCount,
        _Out_ UINT64* pSyncQPCTime) override
    {
        // Zero our output parameters initially.
        *pPresentCount = 0;
        *pSyncQPCTime = 0;

        // Grab frame statistics from the swapchain.
        DXGI_FRAME_STATISTICS frameStatistics;
        FAIL_FAST_IF_FAILED(m_swapchain->GetFrameStatistics(&frameStatistics));

        // If the statistics have changed since our last read, then return the new information
        // to the caller.
        bool hasNewStats = false;
        if (frameStatistics.PresentCount > m_lastPresentCount)
        {
            m_lastPresentCount = frameStatistics.PresentCount;
            hasNewStats = true;
            *pPresentCount = frameStatistics.PresentCount;
            *pSyncQPCTime = frameStatistics.SyncQPCTime.QuadPart;
        }

        return hasNewStats;
    }

private:
    com_ptr_failfast<IDXGISwapChain4> m_swapchain;
    UINT m_lastPresentCount = 0;
};

// An implementation of PresentationProvider using the composition swapchain API to provide
// presentation functionality.
class PresentationAPIProvider :
    public PresentationProvider
{
public:
    PresentationAPIProvider(
        _In_ UINT width,
        _In_ UINT height,
        _In_ UINT bufferCount)
    {
        // Create the presentation manager and presentation surface using the function defined in a
        // previous example.
        MakePresentationManagerAndPresentationSurface(
            &m_d3dDevice,
            &m_presentationManager,
            &m_presentationSurface,
            m_presentationSurfaceHandle);

        // Register for present statistics.
        FAIL_FAST_IF_FAILED(m_presentationManager->EnablePresentStatisticsKind(
            PresentStatisticsKind_CompositionFrame,
            true));

        // Get the statistics event from the presentation manager.
        FAIL_FAST_IF_FAILED(m_presentationManager->GetPresentStatisticsAvailableEvent(
            &m_statisticsAvailableEvent));

        // Create and register the specified number of presentation buffers.
        for (UINT i = 0; i < bufferCount; i++)
        {
            com_ptr_failfast<ID3D11Texture2D> texture;
            com_ptr_failfast<IPresentationBuffer> presentationBuffer;
            AddNewPresentationBuffer(
                m_d3dDevice.get(),
                m_presentationManager.get(),
                width,
                height,
                &texture,
                &presentationBuffer);

            // Add the new presentation buffer and texture to our array.
            m_textures.push_back(texture);
            m_presentationBuffers.push_back(presentationBuffer);

            // Store the available event for the presentation buffer.
            unique_event availableEvent;
            FAIL_FAST_IF_FAILED(presentationBuffer->GetAvailableEvent(&availableEvent));
            m_bufferAvailableEvents.emplace_back(std::move(availableEvent));
        }
    }

    void GetBackBuffer(
        _Out_ ID3D11Texture2D** ppBackBuffer) override
    {
        // Query an available backbuffer using our available events.
        DWORD waitIndex = WaitForMultipleObjects(
            static_cast<UINT>(m_bufferAvailableEvents.size()),
            reinterpret_cast<HANDLE*>(m_bufferAvailableEvents.data()),
            FALSE,
            INFINITE);
        UINT bufferIndex = waitIndex - WAIT_OBJECT_0;

        // Set the backbuffer to be the next presentation buffer.
        FAIL_FAST_IF_FAILED(m_presentationSurface->SetBuffer(m_presentationBuffers[bufferIndex].get()));

        // Return the backbuffer to the caller.
        m_textures[bufferIndex].query_to(ppBackBuffer);
    }

    void Present(
        _In_ SystemInterruptTime presentationTime) override
    {
        // Present at the targeted time.
        m_presentationManager->SetTargetTime(presentationTime);
        HRESULT hrPresent = m_presentationManager->Present();
        if (hrPresent == PRESENTATION_ERROR_LOST)
        {
            // Our presentation manager has been lost. See previous examples regarding how to handle this.
            return;
        }
        else
        {
            FAIL_FAST_IF_FAILED(hrPresent);
        }
    }

    bool ReadStatistics(
        _Out_ UINT* pPresentCount,
        _Out_ UINT64* pSyncQPCTime) override
    {
        // Zero our out parameters initially.
        *pPresentCount = 0;
        *pSyncQPCTime = 0;

        bool hasNewStats = false;

        // Peek at the statistics available event state to see if we've got new statistics.
        while (WaitForSingleObject(m_statisticsAvailableEvent.get(), 0) == WAIT_OBJECT_0)
        {
            // Pop a statistics item to process.
            com_ptr_failfast<IPresentStatistics> statisticsItem;
            FAIL_FAST_IF_FAILED(m_presentationManager->GetNextPresentStatistics(
                &statisticsItem));

            // If this is a composition frame stat, process it.
            if (statisticsItem->GetKind() == PresentStatisticsKind_CompositionFrame)
            {
                // We've got new stats to report.
                hasNewStats = true;

                // Convert to composition frame statistic item.
                auto frameStatisticsItem = statisticsItem.query<ICompositionFramePresentStatistics>();

                // Query DirectComposition's target statistics API to determine the completed time.
                COMPOSITION_FRAME_TARGET_STATS frameTargetStats;
                COMPOSITION_TARGET_STATS targetStats[4];
                frameTargetStats.targetCount = ARRAYSIZE(targetStats);
                frameTargetStats.targetStats = targetStats;
                // Specify the frameId we got from stats in order to pass to the call
                // below and retrieve timing information about that frame.
                frameTargetStats.frameId = frameStatisticsItem->GetCompositionFrameId();
                FAIL_FAST_IF_FAILED(DCompositionGetTargetStatistics(1, &frameTargetStats));

                // Return the statistics information for the first target.
                *pPresentCount = static_cast<UINT>(frameStatisticsItem->GetPresentId());
                *pSyncQPCTime = frameTargetStats.targetStats[0].completedStats.time;

                // Note that there's a much richer variety of statistics information in the new
                // API that can be used to infer much more than is possible with DXGI frame
                // statistics.
            }
        }

        return hasNewStats;
    }

    static bool IsSupportedOnSystem()
    {
        // Direct3D device creation flags. The composition swapchain API requires that applications disable internal
        // driver threading optimizations, as these optimizations break synchronization of the API.
        // If this flag isn't present, then the API will fail the call to create the presentation factory.
        UINT deviceCreationFlags =
            D3D11_CREATE_DEVICE_BGRA_SUPPORT |
            D3D11_CREATE_DEVICE_SINGLETHREADED |
            D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;

        // Create the Direct3D device.
        com_ptr_failfast<ID3D11Device> d3dDevice;
        com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;
        FAIL_FAST_IF_FAILED(D3D11CreateDevice(
            nullptr,                   // No adapter
            D3D_DRIVER_TYPE_HARDWARE,  // Hardware device
            nullptr,                   // No module
            deviceCreationFlags,       // Device creation flags
            nullptr, 0,                // Highest available feature level
            D3D11_SDK_VERSION,         // API version
            &d3dDevice,                // Resulting interface pointer
            nullptr,                   // Actual feature level
            &d3dDeviceContext));       // Device context

        // Call the composition swapchain API export to create the presentation factory.
        com_ptr_failfast<IPresentationFactory> presentationFactory;
        FAIL_FAST_IF_FAILED(CreatePresentationFactory(
            d3dDevice.get(),
            IID_PPV_ARGS(&presentationFactory)));

        // Now determine whether the system is capable of supporting the composition swapchain API based
        // on the capability that's reported by the presentation factory.
        return presentationFactory->IsPresentationSupported();
    }

private:
    com_ptr_failfast<IPresentationManager> m_presentationManager;
    com_ptr_failfast<IPresentationSurface> m_presentationSurface;
    vector<com_ptr_failfast<ID3D11Texture2D>> m_textures;
    vector<com_ptr_failfast<IPresentationBuffer>> m_presentationBuffers;
    vector<unique_event> m_bufferAvailableEvents;
    unique_handle m_presentationSurfaceHandle;
    unique_event m_statisticsAvailableEvent;
};

void DXGIOrPresentationAPIExample()
{
    // Get the current system time. We'll base our 'PresentAt' time on this result.
    SystemInterruptTime currentTime;
    QueryInterruptTimePrecise(&currentTime.value);

    // Track a time we'll be presenting at below. Default to the current time, then increment by
    // 1/10th of a second every present.
    auto presentTime = currentTime;

    // Allocate a presentation provider using the composition swapchain API if it is supported;
    // otherwise fall back to DXGI.
    unique_ptr<PresentationProvider> presentationProvider;
    if (PresentationAPIProvider::IsSupportedOnSystem())
    {
        presentationProvider = std::make_unique<PresentationAPIProvider>(
            500, // Buffer width
            500, // Buffer height
            6);  // Number of buffers
    }
    else
    {
        // System doesn't support the composition swapchain API. Fall back to DXGI.
        presentationProvider = std::make_unique<DXGIProvider>(
            500, // Buffer width
            500, // Buffer height
            6);  // Number of buffers
    }

    // Present 50 times.
    constexpr UINT numPresents = 50;
    for (UINT i = 0; i < 50; i++)
    {
        // Advance our present time 1/10th of a second in the future.
        presentTime.value += 1'000'000;

        // Call the presentation provider to get a backbuffer to render to.
        com_ptr_failfast<ID3D11Texture2D> backBuffer;
        presentationProvider->GetBackBuffer(&backBuffer);

        // Render to the backbuffer.
        DrawColorToSurface(
            presentationProvider->GetD3D11DeviceNoRef(),
            backBuffer,
            1.0f, // red
            0.0f, // green
            0.0f); // blue

        // Present the backbuffer.
        presentationProvider->Present(presentTime);

        // Process statistics.
        bool hasNewStats;
        UINT64 presentTime;
        UINT presentCount;
        hasNewStats = presentationProvider->ReadStatistics(
            &presentCount,
            &presentTime);
        if (hasNewStats)
        {
            // Process these statistics however your application wishes.
        }
    }
}