API de duplicação da Área de Trabalho

Windows 8 desabilita o XDDM (Modelo de Driver de Vídeo) padrão do Windows 2000 espelho drivers e oferece a API de duplicação da área de trabalho. A API de duplicação da área de trabalho fornece acesso remoto a uma imagem da área de trabalho para cenários de colaboração. Os aplicativos podem usar a API de duplicação da área de trabalho para acessar atualizações quadro a quadro para a área de trabalho. Como os aplicativos recebem atualizações para a imagem da área de trabalho em uma superfície DXGI, os aplicativos podem usar toda a potência da GPU para processar as atualizações de imagem.

Atualizando os dados de imagem da área de trabalho

O DXGI fornece uma superfície que contém uma imagem da área de trabalho atual por meio do novo método IDXGIOutputDuplication::AcquireNextFrame . O formato da imagem da área de trabalho é sempre DXGI_FORMAT_B8G8R8A8_UNORM não importa qual seja o modo de exibição atual. Juntamente com essa superfície, esses métodos IDXGIOutputDuplication retornam os tipos de informações indicados que ajudam a determinar quais pixels na superfície você precisa processar:

  • IDXGIOutputDuplication::GetFrameDirtyRects retorna sujo regiões, que são retângulos não sobrepostos que indicam as áreas da imagem da área de trabalho que o sistema operacional atualizou desde que você processou a imagem da área de trabalho anterior.
  • IDXGIOutputDuplication::GetFrameMoveRects retorna regiões de movimentação, que são retângulos de pixels na imagem da área de trabalho que o sistema operacional moveu para outro local dentro da mesma imagem. Cada região de movimentação consiste em um retângulo de destino e um ponto de origem. O ponto de origem especifica o local de onde o sistema operacional copiou a região e o retângulo de destino especifica para onde o sistema operacional moveu essa região. As regiões de movimentação são sempre regiões não ampliadas, portanto, a origem sempre tem o mesmo tamanho que o destino.

Suponha que a imagem da área de trabalho tenha sido transmitida por uma conexão lenta com seu aplicativo cliente remoto. A quantidade de dados enviados pela conexão é reduzida recebendo apenas dados sobre como seu aplicativo cliente deve mover regiões de pixels em vez de dados reais de pixel. Para processar as movimentações, seu aplicativo cliente deve ter armazenado a última imagem completa.

Embora o sistema operacional acumule atualizações de imagem da área de trabalho não processadas, ele pode ficar sem espaço para armazenar com precisão as regiões de atualização. Nessa situação, o sistema operacional começa a acumular as atualizações unindo-as com regiões de atualização existentes para cobrir todas as novas atualizações. Como resultado, o sistema operacional abrange pixels que ele ainda não atualizou nesse quadro. Mas essa situação não produz problemas visuais em seu aplicativo cliente porque você recebe toda a imagem da área de trabalho e não apenas os pixels atualizados.

Para reconstruir a imagem correta da área de trabalho, seu aplicativo cliente deve primeiro processar todas as regiões de movimentação e, em seguida, processar todas as regiões sujo. Qualquer uma dessas listas de sujo e regiões de movimentação pode estar completamente vazia. O código de exemplo do Exemplo de Duplicação da Área de Trabalho mostra como processar as sujo e mover regiões em um único quadro:

//
// Get next frame and write it into Data
//
HRESULT DUPLICATIONMANAGER::GetFrame(_Out_ FRAME_DATA* Data)
{
    HRESULT hr = S_OK;

    IDXGIResource* DesktopResource = NULL;
    DXGI_OUTDUPL_FRAME_INFO FrameInfo;

    //Get new frame
    hr = DeskDupl->AcquireNextFrame(500, &FrameInfo, &DesktopResource);
    if (FAILED(hr))
    {
        if ((hr != DXGI_ERROR_ACCESS_LOST) && (hr != DXGI_ERROR_WAIT_TIMEOUT))
        {
            DisplayErr(L"Failed to acquire next frame in DUPLICATIONMANAGER", L"Error", hr);
        }
        return hr;
    }

    // If still holding old frame, destroy it
    if (AcquiredDesktopImage)
    {
        AcquiredDesktopImage->Release();
        AcquiredDesktopImage = NULL;
    }

    // QI for IDXGIResource
    hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&AcquiredDesktopImage));
    DesktopResource->Release();
    DesktopResource = NULL;
    if (FAILED(hr))
    {
        DisplayErr(L"Failed to QI for ID3D11Texture2D from acquired IDXGIResource in DUPLICATIONMANAGER", L"Error", hr);
        return hr;
    }

    // Get metadata
    if (FrameInfo.TotalMetadataBufferSize)
    {
        // Old buffer too small
        if (FrameInfo.TotalMetadataBufferSize > MetaDataSize)
        {
            if (MetaDataBuffer)
            {
                delete [] MetaDataBuffer;
                MetaDataBuffer = NULL;
            }
            MetaDataBuffer = new (std::nothrow) BYTE[FrameInfo.TotalMetadataBufferSize];
            if (!MetaDataBuffer)
            {
                DisplayErr(L"Failed to allocate memory for metadata in DUPLICATIONMANAGER", L"Error", E_OUTOFMEMORY);
                MetaDataSize = 0;
                Data->MoveCount = 0;
                Data->DirtyCount = 0;
                return E_OUTOFMEMORY;
            }
            MetaDataSize = FrameInfo.TotalMetadataBufferSize;
        }

        UINT BufSize = FrameInfo.TotalMetadataBufferSize;

        // Get move rectangles
        hr = DeskDupl->GetFrameMoveRects(BufSize, reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(MetaDataBuffer), &BufSize);
        if (FAILED(hr))
        {
            if (hr != DXGI_ERROR_ACCESS_LOST)
            {
                DisplayErr(L"Failed to get frame move rects in DUPLICATIONMANAGER", L"Error", hr);
            }
            Data->MoveCount = 0;
            Data->DirtyCount = 0;
            return hr;
        }
        Data->MoveCount = BufSize / sizeof(DXGI_OUTDUPL_MOVE_RECT);

        BYTE* DirtyRects = MetaDataBuffer + BufSize;
        BufSize = FrameInfo.TotalMetadataBufferSize - BufSize;

        // Get dirty rectangles
        hr = DeskDupl->GetFrameDirtyRects(BufSize, reinterpret_cast<RECT*>(DirtyRects), &BufSize);
        if (FAILED(hr))
        {
            if (hr != DXGI_ERROR_ACCESS_LOST)
            {
                DisplayErr(L"Failed to get frame dirty rects in DUPLICATIONMANAGER", L"Error", hr);
            }
            Data->MoveCount = 0;
            Data->DirtyCount = 0;
            return hr;
        }
        Data->DirtyCount = BufSize / sizeof(RECT);

        Data->MetaData = MetaDataBuffer;
    }

    Data->Frame = AcquiredDesktopImage;
    Data->FrameInfo = FrameInfo;

    return hr;
}

//
// Release frame
//
HRESULT DUPLICATIONMANAGER::DoneWithFrame()
{
    HRESULT hr = S_OK;

    hr = DeskDupl->ReleaseFrame();
    if (FAILED(hr))
    {
        DisplayErr(L"Failed to release frame in DUPLICATIONMANAGER", L"Error", hr);
        return hr;
    }

    if (AcquiredDesktopImage)
    {
        AcquiredDesktopImage->Release();
        AcquiredDesktopImage = NULL;
    }

    return hr;
}

Girar a imagem da área de trabalho

Você deve adicionar código explícito ao aplicativo cliente de duplicação da área de trabalho para dar suporte a modos girados. Em um modo girado, a superfície que você recebe de IDXGIOutputDuplication::AcquireNextFrame está sempre na orientação não girada e a imagem da área de trabalho é girada dentro da superfície. Por exemplo, se a área de trabalho estiver definida como 768x1024 em rotação de 90 graus, AcquireNextFrame retornará uma superfície 1024x768 com a imagem da área de trabalho girada dentro dela. Aqui estão alguns exemplos de rotação.

Modo de exibição definido no painel de controle de exibição Modo de exibição retornado por GDI ou DXGI Surface retornado de AcquireNextFrame
Paisagem 1024x768 Rotação de 1024 x 768 0 graus 1024x768[newline] área de trabalho remota não executada
Retrato 1024x768 Rotação de 768 x 1024 90 graus 1024x768[newline] girou a área de trabalho remota de 90 graus
Paisagem 1024x768 (invertida) Rotação de 1024 x 768 180 graus 1024x768[newline] girou a área de trabalho remota de 180 graus
Retrato 1024x768 (invertido) Rotação de 768 x 1024 270 graus 1024x768[newline] girou a área de trabalho remota de 270 graus

 

O código no aplicativo cliente de duplicação da área de trabalho deve girar a imagem da área de trabalho adequadamente antes de exibir a imagem da área de trabalho.

Observação

Em cenários de vários monitores, você pode girar a imagem da área de trabalho para cada monitor de forma independente.

 

Atualizando o ponteiro da área de trabalho

Você precisa usar a API de duplicação da área de trabalho para determinar se o aplicativo cliente deve desenhar a forma do ponteiro do mouse na imagem da área de trabalho. O ponteiro do mouse já está desenhado na imagem da área de trabalho que IDXGIOutputDuplication::AcquireNextFrame fornece ou o ponteiro do mouse é separado da imagem da área de trabalho. Se o ponteiro do mouse for desenhado na imagem da área de trabalho, os dados de posição do ponteiro relatados por AcquireNextFrame (no membro PointerPosition de DXGI_OUTDUPL_FRAME_INFO para o qual o parâmetro pFrameInfo aponta) indicarão que um ponteiro separado não está visível. Se o adaptador gráfico sobrepor o ponteiro do mouse sobre a imagem da área de trabalho, AcquireNextFrame relatará que um ponteiro separado está visível. Portanto, seu aplicativo cliente deve desenhar a forma do ponteiro do mouse na imagem da área de trabalho para representar com precisão o que o usuário atual verá em seu monitor.

Para desenhar o ponteiro do mouse da área de trabalho, use o membro PointerPosition de DXGI_OUTDUPL_FRAME_INFO do parâmetro pFrameInfo de AcquireNextFrame para determinar onde localizar o canto superior esquerdo do ponteiro do mouse na imagem da área de trabalho. Ao desenhar o primeiro quadro, você deve usar o método IDXGIOutputDuplication::GetFramePointerShape para obter informações sobre a forma do ponteiro do mouse. Cada chamada para AcquireNextFrame para obter o próximo quadro também fornece a posição do ponteiro atual para esse quadro. Por outro lado, você precisará usar GetFramePointerShape novamente somente se a forma for alterada. Portanto, mantenha uma cópia da última imagem de ponteiro e use-a para desenhar na área de trabalho, a menos que a forma do ponteiro do mouse seja alterada.

Observação

Junto com a imagem da forma do ponteiro, GetFramePointerShape fornece o tamanho do local do ponto de acesso. O ponto de acesso é dado apenas para fins informativos. O local de onde desenhar a imagem do ponteiro é independente do ponto de acesso.

 

Este código de exemplo do Exemplo de Duplicação da Área de Trabalho mostra como obter a forma do ponteiro do mouse:

//
// Retrieves mouse info and write it into PtrInfo
//
HRESULT DUPLICATIONMANAGER::GetMouse(_Out_ PTR_INFO* PtrInfo, _In_ DXGI_OUTDUPL_FRAME_INFO* FrameInfo, INT OffsetX, INT OffsetY)
{
    HRESULT hr = S_OK;

    // A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change
    if (FrameInfo->LastMouseUpdateTime.QuadPart == 0)
    {
        return hr;
    }

    bool UpdatePosition = true;

    // Make sure we don't update pointer position wrongly
    // If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer
    // was visible, if so, don't set it to invisible or update.
    if (!FrameInfo->PointerPosition.Visible && (PtrInfo->WhoUpdatedPositionLast != OutputNumber))
    {
        UpdatePosition = false;
    }

    // If two outputs both say they have a visible, only update if new update has newer timestamp
    if (FrameInfo->PointerPosition.Visible && PtrInfo->Visible && (PtrInfo->WhoUpdatedPositionLast != OutputNumber) && (PtrInfo->LastTimeStamp.QuadPart > FrameInfo->LastMouseUpdateTime.QuadPart))
    {
        UpdatePosition = false;
    }

    // Update position
    if (UpdatePosition)
    {
        PtrInfo->Position.x = FrameInfo->PointerPosition.Position.x + OutputDesc.DesktopCoordinates.left - OffsetX;
        PtrInfo->Position.y = FrameInfo->PointerPosition.Position.y + OutputDesc.DesktopCoordinates.top - OffsetY;
        PtrInfo->WhoUpdatedPositionLast = OutputNumber;
        PtrInfo->LastTimeStamp = FrameInfo->LastMouseUpdateTime;
        PtrInfo->Visible = FrameInfo->PointerPosition.Visible != 0;
    }

    // No new shape
    if (FrameInfo->PointerShapeBufferSize == 0)
    {
        return hr;
    }

    // Old buffer too small
    if (FrameInfo->PointerShapeBufferSize > PtrInfo->BufferSize)
    {
        if (PtrInfo->PtrShapeBuffer)
        {
            delete [] PtrInfo->PtrShapeBuffer;
            PtrInfo->PtrShapeBuffer = NULL;
        }
        PtrInfo->PtrShapeBuffer = new (std::nothrow) BYTE[FrameInfo->PointerShapeBufferSize];
        if (!PtrInfo->PtrShapeBuffer)
        {
            DisplayErr(L"Failed to allocate memory for pointer shape in DUPLICATIONMANAGER", L"Error", E_OUTOFMEMORY);
            PtrInfo->BufferSize = 0;
            return E_OUTOFMEMORY;
        }

        // Update buffer size
        PtrInfo->BufferSize = FrameInfo->PointerShapeBufferSize;
    }

    UINT BufferSizeRequired;
    // Get shape
    hr = DeskDupl->GetFramePointerShape(FrameInfo->PointerShapeBufferSize, reinterpret_cast<VOID*>(PtrInfo->PtrShapeBuffer), &BufferSizeRequired, &(PtrInfo->ShapeInfo));
    if (FAILED(hr))
    {
        if (hr != DXGI_ERROR_ACCESS_LOST)
        {
            DisplayErr(L"Failed to get frame pointer shape in DUPLICATIONMANAGER", L"Error", hr);
        }
        delete [] PtrInfo->PtrShapeBuffer;
        PtrInfo->PtrShapeBuffer = NULL;
        PtrInfo->BufferSize = 0;
        return hr;
    }

    return hr;
}

Melhorias do DXGI 1.2