API дублирования рабочего стола

Windows 8 отключает стандартные драйверы зеркального отображения Windows 2000 (XDDM) и предлагает API дублирования рабочего стола. API дублирования рабочего стола предоставляет удаленный доступ к образу рабочего стола для сценариев совместной работы. Приложения могут использовать API дублирования рабочего стола для доступа к обновлениям фрейма по кадрам на рабочем столе. Так как приложения получают обновления для образа рабочего стола в поверхности DXGI, приложения могут использовать полную мощность GPU для обработки обновлений образа.

Обновление данных образа рабочего стола

DXGI предоставляет поверхность, содержащую текущий образ рабочего стола с помощью нового метода IDXGIOutputDuplication::AcquireNextFrame . Формат изображения рабочего стола всегда DXGI_FORMAT_B8G8R8A8_UNORM независимо от текущего режима отображения. Наряду с этой поверхностью, эти методы IDXGIOutputDuplication возвращают указанные типы сведений, которые помогают определить, какие пиксели в поверхности необходимо обработать:

  • IDXGIOutputDuplication::GetFrameDirtyRects возвращает грязные области, которые являются неперекрывающимися прямоугольниками, указывающими области образа рабочего стола, обновленные операционной системой с момента обработки предыдущего образа рабочего стола.
  • IDXGIOutputDuplication::GetFrameMoveRects возвращает области перемещения, которые являются прямоугольниками пикселей на рабочем столе, которые операционная система переместила в другое расположение в том же изображении. Каждая область перемещения состоит из прямоугольника назначения и исходной точки. Исходная точка указывает расположение, из которого операционная система скопировала регион, а целевой прямоугольник указывает место перемещения операционной системы в этот регион. Области перемещения всегда не растянуты, поэтому источник всегда совпадает с размером назначения.

Предположим, что образ рабочего стола был передан через медленное подключение к удаленному клиентскому приложению. Объем данных, отправляемых через подключение, уменьшается за счет получения только данных о том, как клиентское приложение должно перемещать области пикселей, а не фактические данные пикселей. Чтобы обработать перемещение, клиентское приложение должно сохранить полное последнее изображение.

Хотя операционная система накапливает необработанные обновления образа рабочего стола, для точного хранения регионов обновления может потребоваться свободное место. В этом случае операционная система начинает накапливать обновления, объединяя их с существующими регионами обновления, чтобы охватывать все новые обновления. В результате операционная система охватывает пиксели, которые она еще не обновила в этом кадре. Но эта ситуация не создает визуальных проблем в клиентском приложении, так как вы получаете все изображение рабочего стола, а не только обновленные пиксели.

Чтобы восстановить правильный образ рабочего стола, клиентское приложение должно сначала обработать все регионы перемещения, а затем обработать все грязные области. Любой из этих списков грязных и перемещаемых регионов может быть полностью пустым. В примере кода из примера дублирования рабочего стола показано, как обрабатывать и грязные области, и перемещать их в одном кадре:

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

Смена образа рабочего стола

Для поддержки вращаемых режимов необходимо добавить явный код в клиентское приложение дублирования рабочего стола. В режиме поворота поверхность, получаемая от IDXGIOutputDuplication::AcquireNextFrame , всегда находится в невернутой ориентации, а изображение рабочего стола вращается внутри поверхности. Например, если для рабочего стола задано значение 768 x 1024 с поворотом на 90 градусов, AcquireNextFrame возвращает поверхность 1024x768 с изображением рабочего стола, вращаемое внутри него. Ниже приведены некоторые примеры поворота.

Режим отображения с панели управления отображением Режим отображения, возвращаемый GDI или DXGI Surface, возвращенный из AcquireNextFrame
Альбомная 1024 x 768 Поворот 1024 x 768 0 градусов 1024x768[newline] nonrotated remote desktop
1024x768 книжная Поворот 768 x 1024 90 градусов 1024x768[newline] rotated 90 degrees remote desktop
1024x768 альбомная (перевернутая) Поворот 1024 x 768 180 градусов 1024x768[newline] rotated 180 degrees remote desktop
1024x768 книжная (перевернутая) Поворот 768x1024 270 градусов 1024x768[newline] rotated 270 degrees remote desktop

 

Код в клиентском приложении дублирования рабочего стола должен соответствующим образом повернуть изображение рабочего стола, прежде чем отображать изображение рабочего стола.

Примечание

В сценариях с несколькими мониторами можно поворачивать образ рабочего стола для каждого монитора независимо друг от друга.

 

Обновление указателя рабочего стола

Необходимо использовать API дублирования рабочего стола, чтобы определить, должно ли клиентское приложение нарисовать фигуру указателя мыши на изображение рабочего стола. Указатель мыши уже нарисован на настольном изображении, которое предоставляет IDXGIOutputDuplication::AcquireNextFrame , или указатель мыши отделен от изображения рабочего стола. Если указатель мыши рисуется на настольном изображении, данные положения указателя, сообщаемые AcquireNextFrame (в элементе PointerPositionDXGI_OUTDUPL_FRAME_INFO указывает, что параметр pFrameInfo указывает на то, что отдельный указатель не отображается. Если графический адаптер накладывает указатель мыши на изображение рабочего стола, AcquireNextFrame сообщает, что отображается отдельный указатель. Таким образом, клиентское приложение должно нарисовать фигуру указателя мыши на настольном изображении, чтобы точно представить, что текущий пользователь будет видеть на своем мониторе.

Чтобы нарисовать указатель мыши на рабочем столе, используйте элемент PointerPositionDXGI_OUTDUPL_FRAME_INFO из параметра pFrameInfo AcquireNextFrame, чтобы определить, где найти верхний левый угол указателя мыши на изображении рабочего стола. При рисовании первого кадра необходимо использовать метод IDXGIOutputDuplication::GetFramePointerShape для получения сведений о форме указателя мыши. Каждый вызов AcquireNextFrame для получения следующего кадра также предоставляет текущее положение указателя для этого кадра. С другой стороны, вам нужно снова использовать GetFramePointerShape только в том случае, если фигура изменится. Таким образом, сохраните копию последнего изображения указателя и используйте его для рисования на рабочем столе, если только форма указателя мыши не изменится.

Примечание

Вместе с изображением фигуры указателя GetFramePointerShape предоставляет размер расположения горячей точки. Горячая точка предоставляется только для информационных целей. Расположение расположения, в котором следует нарисовать изображение указателя, не зависит от хот-спота.

 

В этом примере кода из примера дублирования рабочего стола показано, как получить фигуру указателя мыши:

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

Улучшения DXGI 1.2