다음을 통해 공유


컴퍼지션 스왑 체인 코드 예제

이러한 코드 예제에서는 WIL(Windows 구현 라이브러리)을 사용합니다. WIL을 설치하는 편리한 방법은 Visual Studio로 이동하여 프로젝트>NuGet 패키지 관리...>를 클릭하는 것입니다. 검색 상자에 Microsoft.Windows.ImplementationLibrary찾아보거나 입력하거나 붙여넣고 검색 결과에서 항목을 선택한 다음 설치를 클릭하여 해당 프로젝트에 대한 패키지를 설치합니다.

예제 1 - 컴퍼지션 스왑 체인 API를 지원하는 시스템에서 프레젠테이션 관리자 만들기

언급했듯이 컴퍼지션 스왑체인 API를 사용하려면 지원되는 드라이버가 작동해야 합니다. 다음 예제에서는 시스템에서 API를 지원하는 경우 애플리케이션에서 프레젠테이션 관리자를 만드는 방법을 보여 줍니다. 이는 API가 TryCreate지원되는 경우에만 프레젠테이션 관리자를 반환하는 스타일 함수로 설명됩니다. 이 함수는 프레젠테이션 관리자를 백업하기 위해 Direct3D 디바이스를 올바르게 만드는 방법도 보여 줍니다.

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

예제 2 - 프레젠테이션 관리자 및 프레젠테이션 화면 만들기

다음 예제에서는 애플리케이션에서 시각적 트리의 시각적 개체에 바인딩할 프레젠테이션 관리자 및 프레젠테이션 화면을 만드는 방법을 보여 줍니다. 다음 예제에서는 프레젠테이션 화면을 DirectCompositionWindows.UI.Composition 시각적 트리에 바인딩하는 방법을 보여 줍니다.

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

예제 3 - 프레젠테이션 화면을 Windows.UI.Composition Surface 브러시에 바인딩

다음 예제에서는 위의 예제에서 만든 대로 애플리케이션이 프레젠테이션 화면에 바인딩된 컴퍼지션 표면 핸들을 Windows.UI.Composition (WinComp) 표면 브러시에 바인딩한 다음 애플리케이션의 시각적 트리에서 스프라이트 시각적 개체에 바인딩할 수 있는 방법을 보여 줍니다.

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

예제 4 - 프레젠테이션 화면을 DirectComposition 시각적 개체에 바인딩

다음 예제에서는 애플리케이션이 시각적 트리의 DirectComposition(DComp) 시각적 개체에 프레젠테이션 화면을 바인딩하는 방법을 보여 줍니다.

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

예제 5 - 프레젠테이션 화면에서 알파 모드 및 색 공간 설정

다음 예제에서는 애플리케이션이 프레젠테이션 화면에서 알파 모드 및 색 공간을 설정하는 방법을 보여 줍니다. 알파 모드는 텍스처의 알파 채널을 해석해야 하는지 여부와 방법을 설명합니다. 색 공간은 텍스처 픽셀이 참조하는 색 공간을 설명합니다.

이와 같은 모든 속성 업데이트는 애플리케이션의 다음 현재 항목의 일부로 적용되며 해당 항목의 일부인 버퍼 업데이트와 함께 원자적으로 적용됩니다. 또한 현재는 애플리케이션이 원하는 경우 버퍼를 전혀 업데이트하지 않고 대신 속성 업데이트로만 구성됩니다. 특정 현재에서 버퍼가 업데이트되지 않은 프레젠테이션 화면은 해당 버퍼 이전에 바인딩된 버퍼에 바인딩된 상태로 유지됩니다.

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

예제 6 - 프레젠테이션 화면에서 레터박싱 여백 설정

다음 예제에서는 애플리케이션이 프레젠테이션 화면에서 레터박싱 여백을 지정하는 방법을 보여 줍니다. 레터박싱은 표시되는 콘텐츠와 표시 장치 간의 가로 세로 비율이 다른 것을 고려하여 표면 콘텐츠 자체 외부의 지정된 영역을 채우는 데 사용됩니다. 이 것의 좋은 예는 PC에서 와이드 스크린 극장 형식의 영화를 볼 때 종종 콘텐츠 위와 아래에 표시되는 검은 색 막대 입니다. 컴퍼지션 스왑 체인 API를 사용하면 이러한 경우 레터박싱을 렌더링할 여백을 지정할 수 있습니다.

현재 레터박싱 영역은 항상 불투명한 검은색으로 채워져 있습니다.

시각적 트리 콘텐츠인 프레젠테이션 화면은 호스트 시각적 개체의 좌표 공간에 존재합니다. 레터박싱이 있는 경우 시각적 개체 좌표 공간의 원점은 레터박싱의 왼쪽 위에 해당합니다. 즉, 버퍼 자체가 시각적 개체의 좌표 공간에 오프셋되어 있습니다. 범위는 버퍼의 크기와 레터박싱의 크기로 계산됩니다.

다음은 시각적 좌표 공간에 버퍼 및 레터박싱이 있는 위치와 경계를 계산하는 방법을 보여 줍니다.

레터박싱 여백

마지막으로 프레젠테이션 화면에 적용된 변환에 오프셋이 포함된 경우 버퍼 또는 레터박싱이 적용되지 않는 영역은 콘텐츠 범위 외부에서 고려되며 다른 시각적 트리 콘텐츠가 콘텐츠 범위 외부에서 처리되는 방식과 유사하게 투명하게 처리됩니다.

레터박싱 및 변환 상호 작용

일부 콘텐츠 영역을 채우기 위해 애플리케이션이 버퍼에 적용하는 규모에 관계없이 일관된 레터박싱 여백 크기를 제공하기 위해 DWM은 눈금에 관계없이 일관된 크기로 여백을 렌더링하려고 시도하지만 프레젠테이션 화면에 적용된 변환의 결과를 고려합니다.

즉, 프레젠테이션 화면의 변환이 적용되기 전에 레터박싱 여백이 기술적으로 적용되지만 해당 변환의 일부일 수 있는 모든 배율을 보정합니다. 즉, 프레젠테이션 화면의 변환은 크기가 조정되는 변환의 일부와 변환의 나머지 부분인 두 구성 요소로 분해됩니다.

레터박싱 및 변환 상호 작용 1

예를 들어 100px 레터박싱 여백이 있고 프레젠테이션 화면에 변형이 적용되지 않은 경우 결과 버퍼는 배율 없이 렌더링되고 레터박싱 여백은 100px 너비가 됩니다.

레터박싱 및 변환 상호 작용 2

또 다른 예로, 100px 레터박싱 여백과 프레젠테이션 화면에 2배 배율 변환이 적용된 경우 결과 버퍼는 2배 배율로 렌더링되고 화면에 표시되는 레터박싱 여백은 모든 크기에서 100px가 됩니다.

레터박싱 및 변환 상호 작용 3

또 다른 예로 45도 회전 변환을 사용하면 결과 레터박싱 여백이 100px로 표시되고 레터박싱 여백이 버퍼와 함께 회전됩니다.

레터박싱 및 변환 상호 작용 4

또 다른 예로, 배율 2x 및 회전 45도 변환을 사용하면 이미지가 회전 및 크기 조정되고 레터박싱 여백도 너비가 100px이고 버퍼로 회전됩니다.

레터박싱 및 변환 상호 작용 5

결과 X 또는 Y 눈금이 0인 경우와 같이 애플리케이션이 프레젠테이션 화면에 적용하는 변환에서 눈금 변환을 명확하게 추출할 수 없는 경우, 레터박싱 여백은 전체 변환이 적용된 상태로 렌더링되며 배율을 보정하려고 시도하지 않습니다.

레터박싱 및 변환 상호 작용 6

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

예제 7 - 프레젠테이션 화면에서 콘텐츠 제한 설정

다음 예제에서는 애플리케이션이 보호된 콘텐츠를 위해 다른 애플리케이션이 프레젠테이션 화면의 내용(PrintScreen, DDA, 캡처 API 등과 같은 기술)을 다시 읽지 못하게 하는 방법을 보여 줍니다. 또한 프레젠테이션 화면 표시를 단일 DXGI 출력으로만 제한하는 방법을 보여 줍니다.

콘텐츠 읽기 저장을 사용하지 않도록 설정한 경우 애플리케이션이 다시 읽으려고 하면 캡처된 이미지에 프레젠테이션 화면 대신 불투명한 검은색이 포함됩니다. 프레젠테이션 화면이 IDXGIOutput으로 제한되면 다른 모든 출력에서 불투명 검은색으로 표시됩니다.

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

예제 8 - 프레젠테이션 관리자에 프레젠테이션 버퍼 추가

다음 예제에서는 애플리케이션이 Direct3D 텍스처 집합을 할당하고 프레젠테이션 관리자에 추가하여 프레젠테이션 화면에 표시할 수 있는 프레젠테이션 버퍼로 사용하는 방법을 보여 줍니다.

언급했듯이 텍스처를 등록할 수 있는 프레젠테이션 관리자 수에는 제한이 없습니다. 그러나 대부분의 일반적인 사용 사례에서는 텍스처가 단일 프레젠테이션 관리자에만 등록됩니다.

또한 API는 프레젠테이션 관리자에서 텍스처를 프레젠테이션 버퍼로 등록할 때 공유 버퍼 핸들을 통화로 수락하고 DXGI는 단일 텍스처2D에 대해 여러 공유 버퍼를 만들 수 있으므로 기술적으로 애플리케이션에서 단일 텍스처에 대해 여러 공유 버퍼 핸들을 만들고 프레젠테이션 관리자에 등록할 수 있습니다. 기본적으로 동일한 버퍼를 두 번 이상 추가하는 효과가 있습니다. 고유하게 추적된 두 개의 프레젠테이션 버퍼가 실제로 동일한 텍스처에 해당하므로 프레젠테이션 관리자가 제공하는 동기화 메커니즘을 중단하므로 권장되지 않습니다. 이 API는 고급 API이므로 구현 시 실제로 이 사례를 검색하기가 매우 어렵기 때문에 API는 이 시나리오의 유효성을 검사하지 않습니다.

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

예제 9 - 프레젠테이션 관리자에서 프레젠테이션 버퍼 제거 및 개체 수명

프레젠테이션 관리자에서 프레젠테이션 버퍼를 제거하는 것은 IPresentationBuffer 개체를 0 refcount로 해제하는 것만큼 간단합니다. 그러나 이 경우 반드시 프레젠테이션 버퍼를 해제할 수 있는 것은 아닙니다. 프레젠테이션 버퍼가 화면에 표시되거나 버퍼를 참조하는 미해결 프레젠테이션이 있는 경우와 같이 버퍼가 계속 사용 중일 수 있는 경우가 있을 수 있습니다.

즉, 프레젠테이션 관리자는 애플리케이션에서 직접 및 간접적으로 모든 버퍼를 사용하는 방법을 추적합니다. 표시되는 버퍼, 미해결 버퍼 및 참조 버퍼를 추적하며, 버퍼가 실제로 더 이상 사용되지 않을 때까지 버퍼가 실제로 릴리스/등록 취소되지 않도록 이 모든 상태를 내부적으로 추적합니다.

버퍼 수명에 대해 생각하는 좋은 방법은 애플리케이션이 IPresentationBuffer를 릴리스하는 경우 향후 호출에서 더 이상 해당 버퍼를 사용하지 않을 것임을 API에 알리는 것입니다. 컴퍼지션 스왑 체인 API는 버퍼가 사용되는 다른 방법뿐만 아니라 이를 추적하며, 버퍼가 완전히 안전한 경우에만 버퍼를 완전히 해제합니다.

요컨대, 애플리케이션은 IPresentationBuffer가 완료되면 IPresentationBuffer 를 해제해야 하며, 다른 모든 사용도 완료되었을 때 컴퍼지션 스왑체인 API가 버퍼를 완전히 해제한다는 것을 알고 있어야 합니다.

다음은 위의 개념에 대한 그림입니다.

프레젠테이션 버퍼 제거

이 예제에는 두 개의 프레젠테이션 버퍼가 있는 프레젠테이션 관리자가 있습니다. 버퍼 1에는 활성 상태로 유지하는 세 가지 참조가 있습니다.

  • 애플리케이션에는 참조하는 IPresentationBuffer 가 있습니다.
  • API 내부는 애플리케이션이 바인딩된 마지막 버퍼이므로 프레젠테이션 화면의 현재 바인딩된 버퍼로 저장되고 있습니다.
  • 프레젠테이션 화면에 버퍼 1을 표시하려는 현재 큐에 미해결된 프레젠테이션도 있습니다.

버퍼 1은 해당 Direct3D 텍스처 할당에 대한 참조를 추가로 보유합니다. 애플리케이션에는 해당 할당을 참조하는 ID3D11Texture2D 도 있습니다.

버퍼 2에는 애플리케이션 쪽에서 더 이상 미해결 참조가 없으며 참조하는 텍스처 할당도 없지만 버퍼 2는 화면의 프레젠테이션 화면에 현재 표시되는 내용이므로 활성 상태로 유지됩니다. Present 1이 처리되고 버퍼 1이 화면에 대신 표시되면 버퍼 2에는 더 이상 참조가 없으며, 그러면 Direct3D 텍스처2D 할당에 대한 참조가 해제됩니다.

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

예제 10 - 프레젠테이션 관리자에서 버퍼 크기 조정

다음 예제에서는 애플리케이션에서 프레젠테이션 버퍼의 크기 조정 을 수행하는 방법을 보여 줍니다. 예를 들어 영화 스트리밍 서비스가 480p를 스트리밍하지만 높은 네트워크 대역폭의 가용성으로 인해 형식을 1080p로 전환하기로 결정한 경우 버퍼를 480p에서 1080p로 다시 할당하여 1080p 콘텐츠 표시를 시작할 수 있습니다.

DXGI에서 이를 크기 조정 작업이라고 합니다. DXGI는 모든 버퍼를 원자적으로 재할당하며, 비용이 많이 들고 화면에서 결함을 생성할 수 있습니다. 이 예제에서는 컴퍼지션 스왑체인 API에서 DXGI와 동등한 원자 크기 조정을 달성하는 방법을 설명합니다. 이후 예제에서는 원자성 스왑 체인 크기 조정보다 더 나은 성능을 달성, 여러 선물에 걸쳐 재할당을 간격, 엇갈 린 방식으로 크기를 조정하는 방법을 보여줍니다.

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

예제 11 - 버퍼 사용 가능한 이벤트를 사용하여 프레젠테이션 동기화 및 프레젠테이션 관리자 손실 이벤트 처리

애플리케이션이 이전에 사용된 프레젠테이션 버퍼와 관련된 프레젠테이션을 발행하려는 경우 프레젠테이션 버퍼를 렌더링하고 프레젠테이션 버퍼를 다시 표시하기 위해 이전 프레젠테이션이 완료되었는지 확인해야 합니다. 컴퍼지션 스왑 체인 API는 이 동기화를 용이하게 하는 몇 가지 다른 메커니즘을 제공합니다. 가장 쉬운 것은 프레젠테이션 버퍼의 사용 가능한 이벤트로, 버퍼를 사용할 수 있을 때 신호를 받고(즉, 이전의 모든 선물이 사용 중지 또는 사용 중지 됨 상태로) 서명되지 않은 경우 신호를 수신하는 NT 이벤트 개체입니다. 이 예제에서는 버퍼 사용 가능한 이벤트를 사용하여 표시할 사용 가능한 버퍼를 선택하는 방법을 보여 줍니다. 또한 DXGI 디바이스 손실과 도덕적으로 동등한 디바이스 분실 오류를 수신 대기하고 프레젠테이션 관리자를 다시 만드는 방법을 보여 줍니다.

프레젠테이션 관리자에서 오류가 발생했습니다.

내부적으로 복구할 수 없다는 오류가 발생하면 프레젠테이션 관리자가 손실될 수 있습니다. 애플리케이션은 손실된 프레젠테이션 관리자를 삭제하고 대신 사용할 새 프레젠테이션 관리자를 만들어야 합니다. 프레젠테이션 관리자가 손실되면 추가 프레젠테이션 수락이 중지됩니다. 손실된 프레젠테이션 관리자에서 Present 를 호출하려고 하면 PRESENTATION_ERROR_LOST 반환됩니다. 그러나 다른 모든 메서드는 정상적으로 작동합니다. 이는 애플리케이션이 현재 호출 시 PRESENTATION_ERROR_LOST 대한 검사 필요하고 모든 API 호출에서 손실된 오류를 예상/처리할 필요가 없도록 하기 위한 것입니다. 애플리케이션이 현재 호출 외부에서 손실된 오류를 검사 또는 알림을 받으려는 경우 IPresentationManager::GetLostEvent에서 반환된 손실된 이벤트를 사용할 수 있습니다.

큐 및 타이밍 표시

프레젠테이션 관리자가 발행한 프레젠테이션은 특정 대상 시간을 지정할 수 있습니다. 이 대상 시간은 시스템이 현재를 표시하려고 시도하는 이상적인 시간입니다. 화면이 유한한 주기로 업데이트되므로 현재는 지정된 시간에 정확하게 표시되지 않을 수 있지만 가능한 한 시간에 가깝게 표시됩니다.

이 대상 시간은 시스템 상대 시간 또는 시스템이 켜진 이후 실행된 시간(수백 나노초 단위)입니다. 현재 시간을 계산하는 쉬운 방법은 현재 QPC(QueryPerformanceCounter ) 값을 쿼리하고, 시스템의 QPC 빈도로 나눈 다음, 10,000,000을 곱하는 것입니다. 반올림 및 정밀도 제한은 제쳐두고 적용 가능한 단위로 현재 시간을 계산합니다. 또한 시스템은 MfGetSystemTime 또는 QueryInterruptTimePrecise를 호출하여 현재 시간을 가져올 수도 있습니다.

현재 시간이 알려지면 애플리케이션은 일반적으로 현재 시간에서 증가하는 오프셋에서 프레젠테이션을 내보낸다.

대상 시간에 관계없이 선물은 항상 큐 순서로 처리됩니다. 현재가 이전 현재보다 이전 시간을 대상으로 하는 경우에도 이전 존재가 처리될 때까지 처리되지 않습니다. 이것은 본질적으로 이전의 현재를 재정의하기 전에 보다 나중에 대상으로 하지 않는 모든 현재를 의미합니다. 또한 애플리케이션이 가능한 한 빨리 프레젠테이션을 발행하려는 경우 대상 시간을 설정하거나(이 경우 대상 시간이 마지막 현재에 대해 그대로 유지됨) 대상 시간을 0으로 설정할 수 없습니다. 둘 다 같은 효과를 가질 것입니다. 애플리케이션이 새 선물이 열릴 때까지 이전 선물이 완료될 때까지 기다리지 않으려면 이전 선물을 취소해야 합니다. 이후 예제에서는 이 작업을 수행하는 방법을 설명합니다.

큐 및 타이밍 표시

T=3s: 마지막 현재인 1, 2, 3 모두 준비 및 3이 승리합니다. T=4s: 마지막 현재인 4, 5, 6 모두 준비 및 6이 승리합니다.

위의 다이어그램은 현재 큐에 있는 미해결 프레젠테이션의 예를 보여 줍니다. 현재 1은 시간 3을 대상으로 합니다. 따라서 3s까지는 선물이 열리지 않습니다. 그러나 현재 2는 실제로 이전 시간, 2s를 대상으로 하며 현재 3개의 대상 3도 제공합니다. 따라서 시간 3에서는 1, 2 및 3이 모두 완료되고 현재 3이 실제로 적용되는 현재가 됩니다. 그런 다음, 다음 현재인 4는 4초에 충족되지만 0을 대상으로 하는 Present 5와 대상 시간 집합이 없는 Present 6으로 즉시 재정의됩니다. 5와 6 모두 가능한 한 빨리 적용됩니다.

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

예제 12- 고급 동기화 - 현재 동기화 펜스를 사용하여 워크플로를 미해결 현재 큐의 크기로 제한하고 프레젠테이션 관리자 손실 이벤트 처리

다음 예제에서는 애플리케이션이 나중에 많은 수의 선물을 제출한 다음 미해결 상태인 선물 수가 특정 수량으로 떨어질 때까지 절전 모드로 작동하는 방법을 보여 줍니다. Windows Media Foundation과 같은 프레임워크는 이러한 방식으로 제한하여 발생하는 CPU 절전 모드 해제 수를 최소화하는 동시에 현재 큐가 고갈되지 않도록 합니다(원활한 재생을 방지하고 결함을 발생). 프레젠테이션 워크플로 중에 CPU 전원 사용량을 최소화하는 효과가 있습니다. 애플리케이션은 할당한 프레젠테이션 버퍼 수에 따라 최대 선물 수를 큐에 대기한 다음 현재 큐가 고갈되기 직전까지 절전 모드로 이동하여 큐를 다시 채우게 됩니다.

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

예제 13 - VSync 인터럽트 및 하드웨어 대칭 이동 큐 지원

하드웨어 플립 큐라는 새로운 형태의 플립 큐 관리가 이 API와 함께 도입되어 기본적으로 GPU 하드웨어가 CPU와 완전히 독립적으로 프레젠테이션을 관리할 수 있습니다. 이 기본 이점은 전력 효율성입니다. CPU가 프레젠테이션 프로세스에 참여할 필요가 없는 경우 전력이 줄어듭니다.

GPU 핸들이 독립적으로 표시되는 단점은 프레젠테이션이 표시되면 CPU 상태가 더 이상 즉시 반영되지 않는다는 것입니다. 버퍼 사용 가능한 이벤트, 동기화 펜스 및 현재 통계와 같은 컴퍼지션 스왑 체인 API 개념은 즉시 업데이트되지 않습니다. 대신 GPU는 표시된 내용에 대한 CPU 상태만 주기적으로 새로 고칩니다. 즉, 현재 상태 관련된 애플리케이션에 대한 피드백은 대기 시간이 발생합니다.

애플리케이션은 일반적으로 일부 선물이 표시될 때 신경을 쓰지만 다른 선물은 별로 신경 쓰지 않습니다. 예를 들어 애플리케이션에서 10이 표시되는 경우 8번째가 표시되는 시기를 알고 싶어 하므로 현재 큐를 다시 채우기 시작할 수 있습니다. 이 경우 피드백을 원하는 유일한 선물은 8번째입니다. 1-7 또는 9가 표시되면 아무 작업도 수행할 계획이 없습니다.

업데이트 CPU 상태가 표시되는지 여부는 GPU 하드웨어가 표시되면 VSync 인터럽트 생성을 구성했는지 여부에 따라 달라집니다. 이 VSync 인터럽트는 아직 활성화되지 않은 경우 CPU를 깨우고 CPU는 특수 커널 수준 코드를 실행하여 마지막으로 확인한 이후 GPU에서 발생한 선물에 대해 자신을 업데이트하고, 버퍼 사용 가능한 이벤트, 현재 사용 중지된 펜스 및 현재 통계와 같은 피드백 메커니즘을 업데이트합니다.

애플리케이션이 VSync 인터럽트를 실행해야 하는 프레젠테이션을 명시적으로 표시할 수 있도록 프레젠테이션 관리자는 후속 프레젠테이션이 VSync 인터럽트를 실행해야 하는지 여부를 지정하는 IPresentationManager::ForceVSyncInterrupt 메서드를 노출합니다. 이 설정은 IPresentationManager::SetTargetTime 및 IPresentationManager::SetPreferredPresentDuration과 마찬가지로 변경될 때까지 모든 이후 선물에 적용됩니다.

특정 현재 위치에서 이 설정을 사용하도록 설정하면 하드웨어는 더 많은 전원을 사용하여 표시되는 경우 CPU에 즉시 알리지만 애플리케이션이 가능한 한 빨리 응답할 수 있도록 하기 위해 프레젠테이션이 발생할 때 CPU가 즉시 알림을 받도록 합니다. 특정 현재 위치에서 이 설정을 사용하지 않도록 설정하면 시스템이 현재 표시될 때 CPU 업데이트를 연기하여 전원을 절약하지만 피드백을 지연할 수 있습니다.

애플리케이션은 일반적으로 동기화하려는 현재를 제외한 모든 선물에 대해 VSync 인터럽트 강제 적용을 하지 않습니다. 위의 예제에서 애플리케이션은 현재 큐를 다시 채우기 위해 8번째가 표시되었을 때 절전 모드를 해제하려고 했기 때문에 8이 VSync 인터럽트 신호를 표시하지만 1-7을 표시하고 현재 9는 그렇지 않음을 요청합니다.

기본적으로 애플리케이션이 이 설정을 구성하지 않으면 프레젠테이션 관리자는 모든 프레젠테이션이 표시될 때 항상 VSync 인터럽트 신호를 보냅니다. 전원 사용에 대해 걱정하지 않거나 하드웨어 대칭 이동 큐 지원을 인식하지 못하는 애플리케이션은 ForceVSyncInterrupt를 호출할 수 없으며 결과적으로 제대로 동기화되도록 보장됩니다. 하드웨어 대칭 이동 큐 지원을 알고 있는 애플리케이션은 향상된 전원 효율성을 위해 이 설정을 명시적으로 제어할 수 있습니다.

다음은 VSync 인터럽트 설정과 관련하여 API의 동작을 설명하는 다이어그램입니다.

VSync 인터럽트

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

예제 14 - 향후 예정된 선물 취소

미래를 위해 심층 큐에 표시되는 미디어 애플리케이션은 이전에 발행한 선물을 취소하기로 결정할 수 있습니다. 예를 들어 애플리케이션이 비디오를 재생하고 있고, 향후 많은 수의 프레임을 실행했으며, 사용자가 비디오 재생을 일시 중지하기로 결정한 경우 이러한 문제가 발생할 수 있습니다. 이 경우 애플리케이션은 현재 프레임에 충실하고 아직 큐에 대기되지 않은 이후 프레임을 취소하려고 합니다. 미디어 애플리케이션이 비디오의 다른 지점으로 재생을 이동하기로 결정한 경우에도 이 문제가 발생할 수 있습니다. 이 경우 애플리케이션은 비디오의 이전 위치에 대해 아직 큐에 대기되지 않은 모든 프레젠테이션을 취소하고 새 위치에 대한 선물로 대체하려고 합니다. 이 경우 이전 선물을 취소한 후 애플리케이션은 나중에 비디오의 새 지점에 해당하는 새 선물을 발행할 수 있습니다.

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.
}

예제 15 - 성능 향상을 위해 버퍼 크기 조정 작업이 엇갈렸습니다.

이 예제에서는 애플리케이션이 DXGI를 통해 성능 향상을 위해 버퍼 크기를 조정하는 방법을 보여 줍니다. 영화 스트리밍 서비스 클라이언트가 재생 해상도를 720p에서 1080p로 변경하려는 이전 크기 조정 예제를 기억하세요. DXGI에서 애플리케이션은 DXGI 스왑 체인에서 크기 조정 작업을 수행하여 이전의 모든 버퍼를 원자적으로 버리고 모든 새 1080p 버퍼를 한 번에 다시 할당하고 스왑 체인에 추가합니다. 버퍼의 이 유형의 원자 크기 조정은 비용이 많이 들며 시간이 오래 걸리고 결함이 발생할 수 있습니다. 새 API는 개별 프레젠테이션 버퍼를 더 세밀하게 제어합니다. 따라서 버퍼를 여러 선물에서 하나씩 재할당하고 교체하여 시간이 지남에 따라 워크로드를 분할할 수 있습니다. 이것은 존재하는 하나에 미치는 영향이 적고 결함을 일으킬 가능성이 훨씬 적습니다. 기본적으로 n개의 프레젠테이션 버퍼가 있는 프레젠테이션 관리자의 경우 'n' 프레젠테이션의 경우 애플리케이션에서 이전 크기의 이전 프레젠테이션 버퍼를 제거하고 새 크기로 새 프레젠테이션 버퍼를 할당하고 프레젠테이션할 수 있습니다. 'n'이 표시되면 모든 버퍼가 새 크기가 됩니다.

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

예제 16 - 현재 통계 읽기 및 처리

API 보고서는 제출된 각 존재에 대한 통계를 제공합니다. 높은 수준에서 현재 통계는 특정 존재가 시스템에서 처리되거나 표시되는 방식을 설명하는 피드백 메커니즘입니다. 애플리케이션이 수신하도록 등록할 수 있는 다양한 유형의 통계가 있으며, API 자체의 통계 인프라는 확장 가능하므로 나중에 더 많은 유형의 통계를 추가할 수 있습니다. 이 API는 통계를 다시 읽는 방법을 설명하고 현재 정의된 통계 유형과 상위 수준에서 전달하는 정보를 설명합니다.

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

예제 17 - 추상화 계층을 사용하여 애플리케이션의 새 컴퍼지션 스왑 체인 API 또는 DXGI를 사용하여 표시

새 컴퍼지션 스왑 체인 API의 더 높은 시스템/드라이버 요구 사항을 감안할 때 애플리케이션은 새 API가 지원되지 않는 경우 DXGI를 사용할 수 있습니다. 다행히 프레젠테이션을 수행하는 데 사용할 수 있는 API를 활용하는 추상화 계층을 도입하는 것은 매우 쉽습니다. 다음 예제에서는 이를 달성하는 방법을 보여 줍니다.

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.
        }
    }
}