Exemplos de código de cadeia de permuta de composição
Esses exemplos de código usam as Bibliotecas de Implementação do Windows (WIL). Uma maneira conveniente de instalar o WIL é acessar o Visual Studio, clicar em Projeto>Gerenciar Pacotes NuGet...>Procurar, digitar ou colar Microsoft.WIndows.ImplementationLibrary na caixa de pesquisa, selecionar o item nos resultados da pesquisa e clicar em Instalar para instalar o pacote para esse projeto.
Exemplo 1 — Criando um gerenciador de apresentações em sistemas que oferecem suporte à API de cadeia de permuta de composição
Como mencionado, a API de cadeia de permuta de composição requer drivers suportados para funcionar. O exemplo a seguir ilustra como seu aplicativo pode criar um gerenciador de apresentações se o sistema oferecer suporte à API. Isso é demonstrado como uma TryCreate
função -style que retorna o gerenciador de apresentações somente se a API for suportada. Essa função também demonstra como criar corretamente um dispositivo Direct3D para fazer backup de um gerenciador de apresentações.
Exemplo de 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;
}
Exemplo 2 — Criando um gerenciador de apresentações e uma superfície de apresentação
O exemplo a seguir ilustra como seu aplicativo criaria um gerenciador de apresentações e uma superfície de apresentação para vincular a um visual em uma árvore visual. Exemplos subsequentes mostrarão como vincular a superfície de apresentação às árvores visuais DirectComposition e Windows.UI.Composition.
Exemplo de 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;
}
Exemplo 3 — Vinculando uma superfície de apresentação a um pincel de superfície Windows.UI.Composition
O exemplo a seguir ilustra como seu aplicativo vincularia uma alça de superfície de composição vinculada a uma superfície de apresentação, conforme criado no exemplo acima, a um pincel de superfície Windows.UI.Composition (WinComp), que pode ser vinculado a um visual de sprite na árvore visual do aplicativo.
Exemplo de 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()));
}
Exemplo 4 — Vinculando uma superfície de apresentação a um visual DirectComposition
O exemplo a seguir ilustra como seu aplicativo vincularia uma superfície de apresentação a um visual DirectComposition (DComp) em uma árvore visual.
Exemplo de 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()));
}
Exemplo 5 — Definindo o modo alfa e o espaço de cores em uma superfície de apresentação
O exemplo a seguir ilustra como seu aplicativo definiria o modo alfa e o espaço de cores em uma superfície de apresentação. O modo alfa descreve se e como, o canal alfa na textura deve ou não ser interpretado. O espaço de cores descreve o espaço de cores ao qual os pixels de textura se referem.
Todas as atualizações de propriedade como essas entrarão em vigor como parte do próximo presente do aplicativo e entrarão em vigor atomicamente junto com quaisquer atualizações de buffer que façam parte desse presente. Um presente também pode, se seu aplicativo desejar, não atualizar nenhum buffer e, em vez disso, consistir apenas em atualizações de propriedade. Quaisquer superfícies de apresentação cujos buffers não são atualizados em um determinado presente permanecem vinculadas a qualquer buffer ao qual estavam vinculadas antes desse presente.
Exemplo de 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));
}
Exemplo 6 — Definindo margens de letterboxing em uma superfície de apresentação
O exemplo a seguir ilustra como seu aplicativo especificaria margens de letterboxing em uma superfície de apresentação. O letterboxing é usado para preencher uma área especificada fora do próprio conteúdo da superfície, para levar em conta as diferentes proporções entre o conteúdo e o dispositivo de exibição no qual ele está sendo mostrado. Um bom exemplo disso são as barras pretas que geralmente são visíveis acima e abaixo do conteúdo ao assistir em um PC um filme formatado para cinemas widescreen. A API de cadeia de permuta de composição permite que você especifique as margens dentro das quais renderizar letterboxing nesses casos.
Atualmente, as áreas de letterboxing são sempre preenchidas com preto opaco.
Como conteúdo de árvore visual, a superfície de apresentação existe no espaço de coordenadas de seu visual de host. Com letterboxing presente, a origem do espaço de coordenadas do visual corresponde ao canto superior esquerdo do letterboxing. Ou seja, o buffer em si existe deslocado para o espaço de coordenadas do visual. Os limites são calculados como o tamanho do buffer mais o tamanho do letterboxing.
O seguinte ilustra onde o buffer e o letterboxing existem no espaço de coordenadas visuais e como os limites são calculados.
Finalmente, se a transformação aplicada a uma superfície de apresentação vier com um deslocamento, a área não coberta pelo buffer ou pelo letterboxing será considerada fora dos limites do conteúdo e será tratada como transparente — semelhante a como qualquer outro conteúdo de árvore visual é tratado fora dos limites de conteúdo.
Letterboxing e interação de transformação
Para fornecer tamanhos de margem de letterboxing consistentes, independentemente da escala que seu aplicativo aplica a um buffer para preencher alguma área de conteúdo, o DWM tentará renderizar as margens em um tamanho consistente, independentemente da escala, mas levando em conta o resultado da transformação que foi aplicada à superfície de apresentação.
Dito de outra forma, as margens de letterboxing são tecnicamente aplicadas antes da transformação da superfície de apresentação ser aplicada, mas compensam qualquer escala que possa fazer parte dessa transformação. Ou seja, a transformação da superfície de apresentação é desconstruída em dois componentes: a parte da transformação que escala e o restante da transformação.
Por exemplo, com margens de letterboxing de 100px e nenhuma transformação aplicada à superfície de apresentação, o buffer resultante será renderizado sem escala e as margens de letterboxing terão 100px de largura.
Outro exemplo, com margens de letterboxing de 100px e uma transformação de escala de 2x aplicada na superfície de apresentação, o buffer resultante será renderizado com uma escala de 2x, e as margens de letterboxing vistas na tela ainda serão de 100px em todos os tamanhos.
Outro exemplo, com uma transformação de rotação de 45 graus, as margens de letterboxing resultantes aparecerão 100px, e as margens de letterboxing serão giradas com o buffer.
Outro exemplo, com uma escala de 2x e rotação de 45 graus, a imagem será girada e dimensionada, e as margens de letterboxing também terão 100px de largura e serão giradas com o buffer.
Se uma transformação de escala não puder ser extraída inequivocamente da transformação que seu aplicativo aplica à superfície de apresentação, como se a escala X ou Y resultante for 0, as margens de letterboxing serão renderizadas com a transformação inteira aplicada e não tentaremos compensar a escala.
Exemplo de 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));
}
Exemplo 7 — Definindo restrições de conteúdo em uma superfície de apresentação
O exemplo a seguir ilustra como seu aplicativo impediria que outros aplicativos lessem o conteúdo da superfície de apresentação (de tecnologias como PrintScreen, DDA, Capture API e outros) para fins de conteúdo protegido. Ele também demonstra como limitar a exibição de uma superfície de apresentação a apenas uma única saída DXGI.
Se a leitura de conteúdo estiver desabilitada, quando o aplicativo tentar reler, a imagem capturada conterá preto opaco no lugar da superfície da apresentação. Se uma superfície de apresentação estiver limitada a um IDXGIOutput, ela aparecerá como preto opaco em todas as outras saídas.
Exemplo de 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));
}
Exemplo 8 — Adicionando buffers de apresentação a um gerenciador de apresentações
O exemplo a seguir ilustra como seu aplicativo alocaria um conjunto de texturas Direct3D e as adicionaria ao gerenciador de apresentações para usar como buffers de apresentação, que podem ser apresentados a superfícies de apresentação.
Como mencionado, não há restrições em relação ao número de gerenciadores de apresentação com os quais uma textura pode ser registrada. No entanto, na maioria dos casos de uso normais, uma textura será registrada apenas para um único gerenciador de apresentações.
Observe também que, como a API aceita identificadores de buffer compartilhados como moeda ao registrar texturas como buffers de apresentação no gerenciador de apresentações, e como o DXGI pode criar vários buffers compartilhados para uma única texture2D, é tecnicamente possível para seu aplicativo criar vários identificadores de buffer compartilhados para uma única textura e registrá-los com o gerenciador de apresentações, essencialmente tendo o efeito de adicionar o mesmo buffer mais de uma vez. Isso não é aconselhável, pois quebrará os mecanismos de sincronização fornecidos pelo gerenciador de apresentações, pois dois buffers de apresentação rastreados exclusivamente corresponderão à mesma textura. Como essa é uma API avançada, e como é realmente muito difícil na implementação detectar esse caso quando ele acontece, a API não tenta validar esse cenário.
Exemplo de 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();
}
Exemplo 9 — Removendo buffers de apresentação de um gerenciador de apresentações e o tempo de vida do objeto
Remover um buffer de apresentação do gerenciador de apresentações é tão simples quanto liberar o objeto IPresentationBuffer para uma refcount 0. No entanto, quando isso acontece, não significa necessariamente que o buffer de apresentação pode ser liberado. Pode haver casos em que um buffer ainda pode estar em uso, como se esse buffer de apresentação estiver visível na tela ou tiver presentes pendentes que o referenciam.
Em suma, o gerenciador de apresentações acompanhará como cada buffer está sendo usado pelo seu aplicativo direta e indiretamente. Ele manterá o controle de quais buffers são exibidos, quais apresentam buffers pendentes e de referência, e rastreará internamente todo esse estado para garantir que um buffer não seja realmente liberado/não registrado até que realmente não esteja mais em uso.
Uma boa maneira de pensar sobre o tempo de vida do buffer é que, se seu aplicativo liberar um IPresentationBuffer, ele está dizendo à API que ele não usará mais esse buffer em nenhuma chamada futura. A API de swapchain de composição estará rastreando isso, bem como quaisquer outras maneiras pelas quais o buffer está sendo usado, e liberará totalmente o buffer somente quando for completamente seguro fazê-lo.
Em resumo, seu aplicativo deve liberar um IPresentationBuffer quando terminar de usá-lo e saber que a API swapchain de composição liberará totalmente o buffer quando todos os outros usos também tiverem sido concluídos.
Aqui está uma ilustração dos conceitos acima.
Neste exemplo, temos um gerenciador de apresentações com dois buffers de apresentação. O buffer 1 tem três referências que o mantêm vivo.
- Seu aplicativo contém um IPresentationBuffer fazendo referência a ele.
- Interno à API, ele está sendo armazenado como o buffer vinculado atual da superfície de apresentação, pois foi o último buffer vinculado ao seu aplicativo, que ele então apresentou.
- Há também um presente pendente na fila atual com a intenção de exibir o buffer 1 na superfície de apresentação.
O buffer 1 também mantém uma referência em sua alocação de textura Direct3D correspondente. Seu aplicativo também tem um ID3D11Texture2D fazendo referência a essa alocação.
O buffer 2 não tem mais referências pendentes do lado do aplicativo, nem a alocação de textura que ele referencia, mas o buffer 2 está sendo mantido vivo porque é o que está sendo exibido atualmente pela superfície de apresentação na tela. Quando o Present 1 for processado e o buffer 1 for exibido na tela, o buffer 2 não terá mais referências e será lançado, liberando sua referência em sua alocação de textura Direct3D.
Exemplo de 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();
}
Exemplo 10 — Redimensionando buffers em um gerenciador de apresentações
O exemplo a seguir ilustra como seu aplicativo executaria um redimensionamento dos buffers de apresentação. Por exemplo, se um serviço de streaming de filmes está transmitindo 480p, mas decide mudar de formato para 1080p devido à disponibilidade de alta largura de banda de rede, ele gostaria de realocar seus buffers de 480p para 1080p para que pudesse começar a apresentar conteúdo 1080p.
No DXGI, isso é conhecido como uma operação de redimensionamento . DXGI realoca todos os buffers atomicamente, o que é caro, e pode produzir brilho na tela. O exemplo aqui descreve como obter o redimensionamento atômico no mesmo nível do DXGI na API swapchain de composição. Um exemplo posterior mostrará como fazer um redimensionamento de forma escalonada , espaçando a realocação em vários presentes, alcançando melhor desempenho do que um redimensionamento de cadeia de permuta atômica.
Exemplo de 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);
}
Exemplo 11 — Sincronizando a apresentação usando eventos disponíveis do buffer e manipulando eventos perdidos do gerenciador de apresentações
Se seu aplicativo vai emitir um presente envolvendo um buffer de apresentação que foi usado em um presente anterior, você deve garantir que o presente anterior esteja completo para renderizar e apresentar o buffer de apresentação novamente. A API swapchain de composição fornece alguns mecanismos diferentes para facilitar essa sincronização. O mais fácil é o evento disponível de um buffer de apresentação, que é um objeto de evento NT que é sinalizado quando um buffer está disponível para uso (ou seja, todos os presentes anteriores entraram no estado de retirada ou retirada) e não sinalizado de outra forma. Este exemplo ilustra como usar eventos disponíveis de buffer para selecionar buffers disponíveis para apresentar. Ele também ilustra como ouvir erros de perda de dispositivo que são moralmente equivalentes à perda de dispositivo DXGI, e exigem uma recriação do gerenciador de apresentações.
Erros perdidos do gerenciador de apresentações
Um gerenciador de apresentações pode se perder se encontrar um erro interno do qual não possa se recuperar. Seu aplicativo deve destruir um gerenciador de apresentações perdido e criar um novo para usar. Quando um gerenciador de apresentações se perde, ele deixa de aceitar mais presentes. Qualquer tentativa de chamar Present em um gerenciador de apresentações perdido retornará PRESENTATION_ERROR_LOST. Todos os outros métodos, no entanto, funcionarão normalmente. Isso é para garantir que seu aplicativo precise apenas verificar PRESENTATION_ERROR_LOST em chamadas presentes e não precise antecipar/lidar com erros perdidos em cada chamada de API. Se o seu aplicativo deseja verificar ou ser notificado sobre erros perdidos fora de uma chamada presente, ele pode usar o evento perdido retornado por IPresentationManager::GetLostEvent.
Apresentar fila e tempo
Os presentes emitidos pelo gerenciador de apresentações podem especificar um horário específico para o destino. Esse tempo-alvo é o tempo ideal que o sistema tentará mostrar o presente. Como as telas são atualizadas em uma cadência finita, é improvável que o presente apareça precisamente no horário especificado, mas será mostrado o mais próximo possível do tempo.
Esse tempo-alvo é um tempo relativo ao sistema, ou o tempo que o sistema está em execução desde que foi ligado, em centenas de unidades de nanossegundos. Uma maneira fácil de calcular a hora atual é consultar o valor atual do QueryPerformanceCounter (QPC), dividi-lo pela frequência QPC do sistema e multiplicá-lo por 10.000.000. Arredondamento e limites de precisão à parte, isso calculará o tempo atual nas unidades aplicáveis. O sistema também pode obter a hora atual chamando MfGetSystemTime ou QueryInterruptTimePrecise.
Uma vez que a hora atual é conhecida, seu aplicativo normalmente emitirá presentes em deslocamentos crescentes da hora atual.
Independentemente do horário de destino, os presentes são sempre processados em ordem de fila. Mesmo que um presente tenha como alvo um tempo anterior ao presente anterior, ele não será processado até que o presente anterior seja processado. Isso significa, essencialmente, que qualquer presente que não tenha como alvo um tempo posterior ao anterior substituirá esse presente anterior. Isso também significa que, se o seu aplicativo quiser emitir um presente o mais cedo possível, ele simplesmente não pode definir um tempo alvo (caso em que o tempo alvo permaneceria o que foi para o último presente), ou definir um tempo alvo de 0. Ambos teriam o mesmo efeito. Se a sua inscrição não quiser esperar que os presentes anteriores sejam concluídos para que um novo presente ocorra, será necessário cancelar os presentes anteriores. Um exemplo futuro descreve como fazer isso.
T=3s: Presente 1, 2, 3 tudo pronto, e 3, sendo o último presente, vence. T=4s: Presente 4, 5, 6 todos prontos, e 6, sendo o último presente, vence.
O diagrama acima ilustra um exemplo de presentes pendentes na fila atual. Apresentar 1 alvos um tempo de 3s. Portanto, nenhum presente acontecerá até as 3s. No entanto, o presente 2 realmente visa um tempo anterior, 2s, e apresenta 3 alvos 3s também. Assim, no tempo 3s, os presentes 1, 2 e 3 serão todos completos, e o presente 3 será o presente para realmente entrar em vigor. Depois disso, o próximo presente, 4, será satisfeito em 4s, mas imediatamente será substituído pelo Presente 5, que visa 0s, e pelo Presente 6, que não tem tempo de meta definido. Tanto o 5 quanto o 6 efetivos entram em vigor o mais cedo possível.
Exemplo de 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);
}
Exemplo 12 — Sincronização avançada — limitando o fluxo de trabalho para o tamanho da fila presente pendente usando cercas de sincronização presentes e manipulando eventos perdidos do gerenciador de apresentações
O exemplo a seguir ilustra como seu aplicativo pode enviar um grande número de presentes para o futuro e, em seguida, suspender até que o número de presentes que ainda estão pendentes caia para uma quantidade específica. Estruturas como o Windows Media Foundation aceleram dessa maneira para minimizar o número de ativações de CPU que ocorrem, ao mesmo tempo em que garantem que a fila atual não se esgote (o que impediria uma reprodução suave e causaria uma falha). Isso tem o efeito de minimizar o uso de energia da CPU durante o fluxo de trabalho da apresentação. Seu aplicativo enfileirará o número máximo de presentes (com base no número de buffers de apresentação alocados) e, em seguida, suspenderá até que a fila atual seja esgotada para reabastecer a fila.
Exemplo de 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;
}
Exemplo 13 — Interrupções do VSync e suporte a filas de inversão de hardware
Uma nova forma de gerenciamento de fila de inversão chamada fila de inversão de hardware foi introduzida junto com essa API, essencialmente permitindo que o hardware da GPU gerencie presentes de forma completamente independente da CPU. O principal benefício disso é a eficiência energética. Quando a CPU não precisa estar envolvida no processo de apresentação, menos energia é consumida.
A desvantagem de ter o identificador da GPU apresentado de forma independente é que o estado da CPU não pode mais refletir imediatamente quando os presentes são mostrados. Os conceitos de API de cadeia de permuta de composição, como eventos disponíveis de buffer, cercas de sincronização e estatísticas atuais, não serão atualizados imediatamente. Em vez disso, a GPU atualizará apenas periodicamente o estado da CPU quanto aos presentes que foram mostrados. Isso significa que o feedback aos aplicativos sobre o status atual virá com uma latência.
Seu aplicativo normalmente se preocupará quando alguns presentes forem mostrados, mas não se importará tanto com outros presentes. Por exemplo, se o aplicativo emite 10 presentes, ele pode decidir que deseja saber quando o 8º é mostrado, para que possa começar a reabastecer a fila atual novamente. Neste caso, o único presente que ele realmente quer feedback é o dia 8. Ele não planeja fazer nada quando 1-7 ou 9 são mostrados.
Se o estado da CPU de atualização atual depende se o hardware da GPU está configurado para gerar uma interrupção do VSync quando essa presença é mostrada. Essa interrupção do VSync desperta a CPU se ela ainda não estiver ativa, e a CPU então executa código especial no nível do kernel para se atualizar sobre os presentes que ocorreram na GPU desde a última vez que ela foi verificada, por sua vez atualizando mecanismos de feedback como eventos disponíveis de buffer, a cerca de retirada atual e estatísticas atuais.
Para permitir que seu aplicativo indique explicitamente quais apresentações devem emitir uma interrupção do VSync, o gerenciador de apresentações expõe um método IPresentationManager::ForceVSyncInterrupt , que especifica se os presentes subsequentes devem ou não emitir uma interrupção do VSync. Essa configuração se aplica a todos os presentes futuros até que seja alterada, assim como IPresentationManager::SetTargetTime e IPresentationManager::SetPreferredPresentDuration.
Se essa configuração estiver habilitada em um presente específico, o hardware notificará imediatamente a CPU quando essa presente for mostrada, usando mais energia, mas garantindo que a CPU seja imediatamente notificada quando ocorrer um presente, a fim de permitir que os aplicativos respondam o mais rápido possível. Se essa configuração estiver desabilitada em um presente específico, o sistema poderá adiar a atualização da CPU quando o presente for mostrado, economizando energia, mas adiando o feedback.
Seu aplicativo normalmente não forçará a interrupção do VSync para nenhum presente, exceto um presente com o qual ele deseja sincronizar. No exemplo acima, como seu aplicativo queria acordar quando o 8º presente fosse mostrado para reabastecer sua fila atual, ele solicitaria que o presente 8 sinalizasse uma interrupção do VSync, mas os presentes 1-7 e o presente 9 não.
Por padrão, se o aplicativo não definir essa configuração, o gerenciador de apresentações sempre sinalizará a interrupção do VSync quando todos os presentes forem exibidos. Os aplicativos que não estão preocupados com o uso de energia, ou não estão cientes do suporte à fila de inversão de hardware, simplesmente não podem chamar ForceVSyncInterrupt, e eles terão a garantia de sincronizar corretamente como resultado. Os aplicativos que reconhecem o suporte à fila de inversão de hardware podem controlar explicitamente essa configuração para melhorar a eficiência energética.
A seguir está um diagrama que descreve o comportamento da API em relação às configurações de interrupção do VSync.
Exemplo de 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;
}
Exemplo 14 — Cancelamento de presentes agendados para o futuro
Os aplicativos de mídia que enfileiram presentes para o futuro podem decidir cancelar os presentes que emitiram anteriormente. Isso pode acontecer, por exemplo, se seu aplicativo estiver reproduzindo um vídeo, tiver emitido um grande número de quadros para o futuro e o usuário decidir pausar a reprodução de vídeo. Nesse caso, seu aplicativo desejará manter o quadro atual e cancelar quaisquer quadros futuros que ainda não tenham sido enfileirados. Isso também pode acontecer se um aplicativo de mídia decidir mover a reprodução para um ponto diferente no vídeo. Nesse caso, seu aplicativo vai querer cancelar todos os presentes que ainda não entraram na fila para a posição antiga no vídeo, e substituí-los por presentes para a nova posição. Nesse caso, após cancelar presentes anteriores, seu aplicativo pode emitir novos presentes no futuro correspondentes ao novo ponto no vídeo.
Exemplo de 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.
}
Exemplo 15 — Operação de redimensionamento escalonado do buffer para melhorar o desempenho
Este exemplo ilustra como seu aplicativo pode escalonar redimensionamentos de buffer para melhorar o desempenho em relação ao DXGI. Lembre-se do nosso exemplo anterior de redimensionamento, em que um cliente de serviço de streaming de filmes deseja alterar a resolução de reprodução de 720p para 1080p. No DXGI, o aplicativo executaria uma operação de redimensionamento na cadeia de permuta DXGI, que descartaria atomicamente todos os buffers anteriores, e realocaria todos os novos buffers 1080p de uma só vez e os adicionaria à cadeia de permuta. Esse tipo de redimensionamento atômico de buffers é caro e tem o potencial de levar muito tempo e causar falhas. A nova API fornece controle mais preciso sobre buffers de apresentação individuais. Como tal, os buffers podem ser realocados e substituídos um a um em vários presentes para dividir a carga de trabalho ao longo do tempo. Isso tem menos impacto em qualquer um presente e é muito menos provável de causar glitching. Essencialmente, para um gerenciador de apresentações com n buffers de apresentação, para 'n' presentes, seu aplicativo pode remover um buffer de apresentação antigo no tamanho antigo, alocar um novo buffer de apresentação no novo tamanho e apresentá-lo. Após 'n' presentes, todos os buffers estarão no novo tamanho.
Exemplo de 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;
}
Exemplo 16 — Leitura e processamento de estatísticas atuais
Os relatórios da API apresentam estatísticas para cada presente enviado. Em um alto nível, as estatísticas atuais são um mecanismo de feedback que descreve como um determinado presente foi processado ou mostrado pelo sistema. Existem diferentes tipos de estatísticas que seu aplicativo pode registrar para receber, e a infraestrutura de estatísticas na própria API deve ser expansível, portanto, mais tipos de estatísticas podem ser adicionados no futuro. Essa API descreve como ler estatísticas e descreve os tipos de estatísticas definidos hoje e quais informações elas transmitem em alto nível.
Exemplo de 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;
}
}
Exemplo 17 — usando uma camada de abstração para apresentar usando a nova API de cadeia de permuta de composição ou DXGI do seu aplicativo
Dados os requisitos mais altos de sistema/driver da nova API de cadeia de permuta de composição, seu aplicativo pode desejar usar o DXGI nos casos em que a nova API não é suportada. Felizmente, é bastante fácil introduzir uma camada de abstração que utiliza qualquer API disponível para fazer apresentação. O exemplo a seguir ilustra como conseguir isso.
Exemplo de 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(¤tTime.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.
}
}
}