Udostępnij za pośrednictwem


Interfejs API duplikowania pulpitu

System Windows 8 wyłącza standardowe sterowniki dublowania systemu Windows 2000 Display Driver Model (XDDM) i oferuje zamiast tego duplikowany interfejs API pulpitu. Interfejs API duplikowania pulpitu zapewnia zdalny dostęp do obrazu pulpitu na potrzeby scenariuszy współpracy. Aplikacje mogą używać interfejsu API duplikowania pulpitu w celu uzyskania dostępu do aktualizacji ramek po ramce na pulpicie. Ponieważ aplikacje otrzymują aktualizacje obrazu pulpitu na powierzchni DXGI, aplikacje mogą używać pełnej mocy procesora GPU do przetwarzania aktualizacji obrazu.

Aktualizowanie danych obrazu pulpitu

DXGI udostępnia powierzchnię zawierającą bieżący obraz pulpitu za pośrednictwem nowej metody IDXGIOutputDuplication::AcquireNextFrame. Format obrazu pulpitu jest zawsze DXGI_FORMAT_B8G8R8A8_UNORM niezależnie od bieżącego trybu wyświetlania. Wraz z tą powierzchnią te IDXGIOutputDuplication metody zwracają wskazane typy informacji, które ułatwiają określenie pikseli na powierzchni, które należy przetworzyć:

  • IDXGIOutputDuplication::GetFrameDirtyRects zwraca zanieczyszczone regiony, które nie nakładają się prostokąty wskazujące obszary obrazu pulpitu zaktualizowanego przez system operacyjny od czasu przetworzenia poprzedniego obrazu pulpitu.
  • IDXGIOutputDuplication::GetFrameMoveRects zwraca regiony przenoszenia, które są prostokątami pikseli na obrazie pulpitu, który system operacyjny przeniósł się do innej lokalizacji na tym samym obrazie. Każdy region przenoszenia składa się z prostokąta docelowego i punktu źródłowego. Punkt źródłowy określa lokalizację, z której system operacyjny skopiował region, a docelowy prostokąt określa, gdzie system operacyjny przeniósł ten region. Regiony przenoszenia są zawsze regionami bez rozciągnięcia, więc źródło jest zawsze takim samym rozmiarem jak miejsce docelowe.

Załóżmy, że obraz pulpitu został przesłany przez powolne połączenie z zdalną aplikacją kliencką. Ilość danych wysyłanych przez połączenie jest zmniejszana przez odbieranie tylko danych dotyczących sposobu, w jaki aplikacja kliencka musi przenosić regiony pikseli, a nie rzeczywiste dane pikseli. Aby przetworzyć przenoszenie, aplikacja kliencka musi przechowywać pełny obraz.

Podczas gdy system operacyjny gromadzi nieprzetworzone aktualizacje obrazów pulpitu, może zabrakło miejsca na dokładne przechowywanie regionów aktualizacji. W takiej sytuacji system operacyjny zaczyna gromadzić aktualizacje, łącząc je z istniejącymi regionami aktualizacji w celu pokrycia wszystkich nowych aktualizacji. W rezultacie system operacyjny obejmuje piksele, które nie zostały jeszcze zaktualizowane w tej ramce. Jednak ta sytuacja nie powoduje problemów wizualnych w aplikacji klienckiej, ponieważ otrzymujesz cały obraz pulpitu, a nie tylko zaktualizowane piksele.

Aby odtworzyć prawidłowy obraz pulpitu, aplikacja kliencka musi najpierw przetworzyć wszystkie regiony przenoszenia, a następnie przetworzyć wszystkie zanieczyszczone regiony. Każda z tych list zanieczyszczonych i przeniesionych regionów może być całkowicie pusta. Przykładowy kod z przykładu duplikowania pulpitu pokazuje, jak przetwarzać zarówno zanieczyszczone, jak i przenoszone regiony w jednej ramce:

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

Obracanie obrazu pulpitu

Aby obsługiwać tryby rotacji, należy dodać jawny kod do aplikacji klienckiej duplikowania pulpitu. W trybie obróconym powierzchnia otrzymana z IDXGIOutputDuplication::AcquireNextFrame jest zawsze w orientacji nieobróconej, a obraz pulpitu jest obracany w obrębie powierzchni. Jeśli na przykład pulpit jest ustawiony na 768x1024 przy 90 stopni obrotu, AcquireNextFrame zwraca powierzchnię 1024x768 z obrazem pulpitu obracanym w nim. Oto kilka przykładów rotacji.

Tryb wyświetlania ustawiony z panelu sterowania wyświetlania Tryb wyświetlania zwracany przez GDI lub DXGI Urządzenie Surface zwrócone z AcquireNextFrame
Poziom 1024x768 Obrót 1024x768 0 stopni 1024x768[newline] nierotowanego pulpitu zdalnego
Portret 1024x768 Obrót 768x1024 90 stopni 1024x768[newline] obrócony 90 stopni pulpitu zdalnego
1024x768 krajobraz (odwrócony) Obrót 1024x768 180 stopni 1024x768[newline] obrócony 180 stopni pulpitu zdalnego
Portret 1024x768 (odwrócony) Obrót 768x1024 270 stopni 1024x768[newline] obrócony 270 stopni pulpitu zdalnego

 

Kod w aplikacji klienckiej duplikowania pulpitu musi odpowiednio obrócić obraz pulpitu przed wyświetleniem obrazu pulpitu.

Nuta

W scenariuszach z wieloma monitorami można niezależnie obracać obraz pulpitu dla każdego monitora.

 

Aktualizowanie wskaźnika pulpitu

Aby określić, czy aplikacja kliencka musi narysować kształt wskaźnika myszy na obrazie pulpitu, należy użyć interfejsu API duplikowania pulpitu. Wskaźnik myszy jest już rysowany na obrazie pulpitu, który IDXGIOutputDuplication::AcquireNextFrame zapewnia lub wskaźnik myszy jest oddzielony od obrazu pulpitu. Jeśli wskaźnik myszy zostanie narysowany na obrazie pulpitu, dane położenia wskaźnika zgłoszone przez AcquireNextFrame (w wskaźnikpozycji elementu członkowskiego DXGI_OUTDUPL_FRAME_INFO, że pFrameInfo wskazuje, że oddzielny wskaźnik nie jest widoczny. Jeśli karta graficzna nakłada wskaźnik myszy na wierzchu obrazu pulpitu, AcquireNextFrame raportuje, że oddzielny wskaźnik jest widoczny. Dlatego aplikacja kliencka musi narysować kształt wskaźnika myszy na obraz pulpitu, aby dokładnie reprezentować to, co bieżący użytkownik zobaczy na swoim monitorze.

Aby narysować wskaźnik myszy pulpitu, użyj elementu członkowskiego PointerPositionDXGI_OUTDUPL_FRAME_INFO z parametru pFrameInfo parametru AcquireNextFrame, aby określić, gdzie zlokalizować lewy górny róg wskaźnika myszy na obrazie pulpitu. Podczas rysowania pierwszej ramki należy użyć metody IDXGIOutputDuplication::GetFramePointerShape, aby uzyskać informacje o kształcie wskaźnika myszy. Każde wywołanie AcquireNextFrame, aby uzyskać następną ramkę, zapewnia również bieżącą pozycję wskaźnika dla tej ramki. Z drugiej strony należy ponownie użyć GetFramePointerShape tylko wtedy, gdy kształt ulegnie zmianie. Dlatego zachowaj kopię ostatniego obrazu wskaźnika i użyj go do rysowania na pulpicie, chyba że kształt wskaźnika myszy ulegnie zmianie.

Nuta

Wraz z obrazem kształtu wskaźnika GetFramePointerShape zapewnia rozmiar lokalizacji punktu gorącego. Hot spot jest podawany tylko w celach informacyjnych. Lokalizacja miejsca narysowania obrazu wskaźnika jest niezależna od hotspotu.

 

Ten przykładowy kod z przykładu duplikowania pulpitu pokazuje, jak uzyskać kształt wskaźnika myszy:

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

ulepszenia DXGI 1.2