桌面複製 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 收到的表面一律處於未旋轉的方向,且桌面影像會在表面內旋轉。 例如,如果桌面設定為 768x1024 且旋轉 90 度, AcquireNextFrame 會傳回 1024x768 表面,其中旋轉桌面影像。 以下是一些旋轉範例。
從顯示控制台設定的顯示模式 | GDI 或 DXGI 傳回的顯示模式 | 從AcquireNextFrame傳回的 Surface |
---|---|---|
1024x768 橫向 | 1024x768 0 度旋轉 | 1024x768[newline] |
1024x768 直向 | 768x1024 90 度旋轉 | 1024x768[newline] |
1024x768 橫向 (翻轉) | 1024x768 180 度旋轉 | 1024x768[newline] |
1024x768 直向 (翻轉) | 768x1024 270 度旋轉 | 1024x768[newline] |
桌面重複用戶端應用程式中的程式碼必須適當地旋轉桌面映射,才能顯示桌面影像。
注意
在多監視器案例中,您可以個別旋轉每個監視器的桌面映射。
更新桌面指標
您必須使用桌面重複 API 來判斷用戶端應用程式是否必須在桌面映射上繪製滑鼠指標圖形。 滑鼠指標已經繪製到 IDXGIOutputDuplication::AcquireNextFrame 提供的桌面映射上,或是滑鼠指標與桌面映射分開。 如果滑鼠指標繪製到桌面影像上,則 AcquireNextFrame所報告的指標位置資料會在pFrameInfo參數指向的PointerPositionDXGI_OUTDUPL_FRAME_INFO成員中 (,指出 pFrameInfo 參數指向) 表示看不到個別指標。 如果圖形介面卡在桌面影像頂端重迭滑鼠指標, AcquireNextFrame 會報告個別指標是可見的。 因此,您的用戶端應用程式必須將滑鼠指標圖形繪製到桌面映射上,才能精確地代表目前使用者在其監視器上看到的內容。
若要繪製桌面的滑鼠指標,請使用從 AcquireNextFrame的pFrameInfo參數DXGI_OUTDUPL_FRAME_INFOPointerPosition成員,判斷在桌面映射上找出滑鼠指標左上角的位置。 繪製第一個框架時,您必須使用 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;
}
相關主題