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

Windows 8 отключает стандартную модель windows 2000 Display Driver Model (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
1024x768 альбомная 1024x768 поворот на 0 градусов 1024x768[newline] удаленный рабочий стол безrotated
1024x768 книжная Поворот 768x1024 на 90 градусов 1024x768[newline] повернут на 90 градусов удаленный рабочий стол
1024x768 альбомная (перевернутая) Поворот 1024x768 на 180 градусов 1024x768[newline] повернут на 180 градусов удаленный рабочий стол
1024x768 портрет (перевернутый) Поворот 768x1024 270 градусов 1024x768[newline] повернут на 270 градусов удаленный рабочий стол

 

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

Примечание

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

 

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

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

Чтобы нарисовать указатель мыши на рабочем столе, используйте элемент PointerPositionэлемента DXGI_OUTDUPL_FRAME_INFO из параметра pFrameInfoacquireNextFrame , чтобы определить, где находится левый верхний угол указателя мыши на изображении рабочего стола. При рисовании первого кадра необходимо использовать метод 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