Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1463

[VB6] DirectX 11 Desktop Duplication

$
0
0
This is a work in progress of a remote control utility. This is the screen capturing part using DirectX 11 (DXGI).

Code:

Option Explicit
DefObj A-Z

'--- DIB Section constants
Private Const DIB_RGB_COLORS                As Long = 0 '  color table in RGBs

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDst As Any, lpSrc As Any, ByVal ByteLength As Long)
Private Declare Function CreateDIBSection Lib "gdi32" (ByVal hDC As Long, lpBitsInfo As BITMAPINFOHEADER, ByVal wUsage As Long, lpBits As Long, ByVal Handle As Long, ByVal dw As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "oleaut32" (lpPictDesc As PICTDESC, riid As Any, ByVal fPictureOwnsHandle As Long, ipic As IPicture) As Long
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long

Private Type BITMAPINFOHEADER
    biSize              As Long
    biWidth            As Long
    biHeight            As Long
    biPlanes            As Integer
    biBitCount          As Integer
    biCompression      As Long
    biSizeImage        As Long
    biXPelsPerMeter    As Long
    biYPelsPerMeter    As Long
    biClrUsed          As Long
    biClrImportant      As Long
End Type

Private Type PICTDESC
    lSize              As Long
    lType              As Long
    hBmp                As Long
    hPal                As Long
End Type

Private Type UcsDuplicationContext
    DeviceName          As String
    Width              As Long
    Height              As Long
    Timeout            As Long
    Context            As ID3D11DeviceContext
    StageTexture        As ID3D11Texture2D
    Duplication        As IDXGIOutputDuplication
    DesktopResource    As ID3D11Resource
    InSystemMemory      As Boolean
    FrameInfo          As DXGI_OUTDUPL_FRAME_INFO
    MoveRects()        As DXGI_OUTDUPL_MOVE_RECT
    NumMove            As Long
    DirtyRects()        As D3D11_RECT
    NumDirty            As Long
    PointerShape()      As Byte
    PointerInfo        As DXGI_OUTDUPL_POINTER_SHAPE_INFO
End Type

Private m_uCtx                  As UcsDuplicationContext

Private Sub PrintError(sFuncName As String)
    Debug.Print Err.Description & " in " & sFuncName
    If MsgBox(Err.Description, vbCritical Or vbOKCancel, sFuncName) = vbCancel Then
        Unload Me
    End If
End Sub

Private Function pvEnumOutputDeviceNames() As Collection
    Dim aGUID(0 To 3)  As Long
    Dim pFactory        As IDXGIFactory1
    Dim lIdx            As Long
    Dim lJdx            As Long
    Dim pAdapter        As IDXGIAdapter1
    Dim pOutput        As IDXGIOutput1
    Dim uAdapterDesc    As DXGI_ADAPTER_DESC
    Dim uOutputDesc    As DXGI_OUTPUT_DESC
   
    Set pvEnumOutputDeviceNames = New Collection
    Call IIDFromString(szIID_DXGIFactory1, aGUID(0))
    Set pFactory = CreateDXGIFactory1(aGUID(0))
    For lIdx = 0 To 100
        Set pAdapter = Nothing
        If pFactory.EnumAdapters1(lIdx, pAdapter) <> 0 Then
            Exit For
        End If
        pAdapter.GetDesc uAdapterDesc
'        Debug.Print Replace(uAdapterDesc.Description, vbNullChar, vbNullString)
        For lJdx = 0 To 100
            Set pOutput = Nothing
            If pAdapter.EnumOutputs(lJdx, pOutput) <> 0 Then
                Exit For
            End If
            pOutput.GetDesc uOutputDesc
            pvEnumOutputDeviceNames.Add Array(Replace(uOutputDesc.DeviceName, vbNullChar, vbNullString), _
                Replace(uAdapterDesc.Description, vbNullChar, vbNullString))
        Next
    Next
End Function

Private Function pvInitCapture(uCtx As UcsDuplicationContext, ByVal sDeviceName As String, ByVal lTimeout As Long) As Boolean
    Const FUNC_NAME    As String = "pvInitCapture"
    Dim aGUID(0 To 3)  As Long
    Dim pFactory        As IDXGIFactory1
    Dim lIdx            As Long
    Dim lJdx            As Long
    Dim pAdapter        As IDXGIAdapter1
    Dim pOutput        As IDXGIOutput1
    Dim uOutputDesc    As DXGI_OUTPUT_DESC
    Dim hResult        As Long
    Dim pDevice        As ID3D11Device
    Dim uTextureDesc    As D3D11_TEXTURE2D_DESC
    Dim uDuplDesc      As DXGI_OUTDUPL_DESC
   
    On Error GoTo EH
    With uCtx
        .DeviceName = vbNullString
        Set .DesktopResource = Nothing
        Set .Duplication = Nothing
        Set .StageTexture = Nothing
        Set .Context = Nothing
        Call IIDFromString(szIID_DXGIFactory1, aGUID(0))
        Set pFactory = CreateDXGIFactory1(aGUID(0))
        For lIdx = 0 To 100
            Set pAdapter = Nothing
            If pFactory.EnumAdapters1(lIdx, pAdapter) <> 0 Then
                Exit For
            End If
            For lJdx = 0 To 100
                Set pOutput = Nothing
                If pAdapter.EnumOutputs(lJdx, pOutput) <> 0 Then
                    Exit For
                End If
                pOutput.GetDesc uOutputDesc
                If Replace(uOutputDesc.DeviceName, vbNullChar, vbNullString) Like sDeviceName Or LenB(sDeviceName) = 0 Then
                    lIdx = 100
                    Exit For
                End If
            Next
        Next
        If pOutput Is Nothing Then
            GoTo QH
        End If
        .DeviceName = Replace(uOutputDesc.DeviceName, vbNullChar, vbNullString)
        .Width = uOutputDesc.DesktopCoordinates.Right - uOutputDesc.DesktopCoordinates.Left
        .Height = uOutputDesc.DesktopCoordinates.Bottom - uOutputDesc.DesktopCoordinates.Top
        .Timeout = lTimeout
        hResult = D3D11CreateDevice(pAdapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, ByVal 0, 0, D3D11_SDK_VERSION, pDevice, 0, .Context)
        If hResult < 0 Then
            Err.Raise hResult
        End If
        With uTextureDesc
            .Width = uCtx.Width
            .Height = uCtx.Height
            .MipLevels = 1
            .ArraySize = 1
            .Format = DXGI_FORMAT_B8G8R8A8_UNORM
            .SampleDesc.Count = 1
            .SampleDesc.Quality = 0
            .Usage = D3D11_USAGE_STAGING
            .BindFlags = 0
            .CPUAccessFlags = D3D11_CPU_ACCESS_READ
            .MiscFlags = 0
        End With
        Set .StageTexture = pDevice.CreateTexture2D(uTextureDesc)
        Set .Duplication = pOutput.DuplicateOutput(pDevice)
        .Duplication.GetDesc uDuplDesc
        .InSystemMemory = (uDuplDesc.DesktopImageInSystemMemory <> 0)
        ReDim .MoveRects(0 To 0) As DXGI_OUTDUPL_MOVE_RECT
        ReDim .DirtyRects(0 To 0) As D3D11_RECT
        ReDim .PointerShape(0 To 0) As Byte
    End With
    '--- success
    pvInitCapture = True
QH:
    Exit Function
EH:
    PrintError FUNC_NAME
End Function

Private Function pvCaptureScreen(uCtx As UcsDuplicationContext, oPicDesktop As StdPicture, oPicPointer As StdPicture) As Boolean
    Const FUNC_NAME    As String = "pvCaptureScreen"
    Const SIZE_OUTDUPL_MOVE_RECT As Long = 24
    Const SIZE_RECT    As Long = 16
    Const BORDER_COLOR  As Long = &HFFFF0000
    Dim hResult        As Long
    Dim lIdx            As Long
    Dim uResource      As D3D11_MAPPED_SUBRESOURCE
    Dim hMemDC          As Long
    Dim hPointerDib    As Long
    Dim hDib            As Long
    Dim lpBits          As Long
    Dim uMapRect        As DXGI_MAPPED_RECT
    Dim lSize          As Long
    Dim dblTimerEx      As Double
    Dim lX              As Long
    Dim lY              As Long
    Dim uPointerInfo    As DXGI_OUTDUPL_POINTER_SHAPE_INFO
   
    On Error GoTo EH
    dblTimerEx = TimerEx
    With uCtx
        If .Duplication Is Nothing Then
            GoTo QH
        End If
        If Not .DesktopResource Is Nothing Then
            .Duplication.ReleaseFrame
            Set .DesktopResource = Nothing
        End If
        hResult = .Duplication.AcquireNextFrame(.Timeout / 2, .FrameInfo, .DesktopResource)
        If hResult = DXGI_ERROR_WAIT_TIMEOUT Then
            '--- success
            pvCaptureScreen = True
            GoTo QH
        End If
        If hResult < 0 Then
            GoTo QH
        End If
        hResult = .Duplication.GetFrameMoveRects((UBound(.MoveRects) + 1) * SIZE_OUTDUPL_MOVE_RECT, .MoveRects(0), lSize)
        If hResult = DXGI_ERROR_MORE_DATA Then
            ReDim .MoveRects(0 To lSize \ SIZE_OUTDUPL_MOVE_RECT - 1) As DXGI_OUTDUPL_MOVE_RECT
            hResult = .Duplication.GetFrameMoveRects((UBound(.MoveRects) + 1) * SIZE_OUTDUPL_MOVE_RECT, .MoveRects(0), lSize)
        End If
        If hResult < 0 Then
            GoTo QH
        End If
        .NumMove = lSize / SIZE_OUTDUPL_MOVE_RECT
        hResult = .Duplication.GetFrameDirtyRects((UBound(.DirtyRects) + 1) * SIZE_RECT, .DirtyRects(0), lSize)
        If hResult = DXGI_ERROR_MORE_DATA Then
            ReDim .DirtyRects(0 To lSize \ SIZE_RECT - 1) As D3D11_RECT
            hResult = .Duplication.GetFrameDirtyRects((UBound(.DirtyRects) + 1) * SIZE_RECT, .DirtyRects(0), lSize)
        End If
        If hResult < 0 Then
            GoTo QH
        End If
        .NumDirty = lSize / SIZE_RECT
        '--- init mem dc
        hMemDC = CreateCompatibleDC(0)
        If hMemDC = 0 Then
            GoTo QH
        End If
        '--- copy Pointer
        hResult = .Duplication.GetFramePointerShape((UBound(.PointerShape) + 1), .PointerShape(0), lSize, uPointerInfo)
        If hResult = DXGI_ERROR_MORE_DATA Then
            ReDim .PointerShape(0 To lSize - 1) As Byte
            hResult = .Duplication.GetFramePointerShape((UBound(.PointerShape) + 1), .PointerShape(0), lSize, uPointerInfo)
        End If
        If hResult < 0 Then
            GoTo QH
        End If
        If lSize > 0 Then
            .PointerInfo = uPointerInfo
            Select Case .PointerInfo.Type
            Case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR
                If Not pvCreateDib(hMemDC, .PointerInfo.Width, .PointerInfo.Height, hPointerDib, lpBits) Then
                    GoTo QH
                End If
                For lIdx = 0 To .PointerInfo.Height - 1
                    Call CopyMemory(ByVal lpBits + lIdx * .PointerInfo.Width * 4, .PointerShape(lIdx * .PointerInfo.Pitch), .PointerInfo.Width * 4)
                Next
                If Not pvCreateStdPicture(hPointerDib, oPicPointer) Then
                    GoTo QH
                End If
                hPointerDib = 0
            Case Else
'                Debug.Print ".PointerInfo.Type=" & Hex(.PointerInfo.Type) & ", ";
            End Select
        End If
        '--- copy to DIB
        If Not pvCreateDib(hMemDC, .Width, .Height, hDib, lpBits) Then
            GoTo QH
        End If
        If .InSystemMemory Then
            .Duplication.MapDesktopSurface uMapRect
            For lIdx = 0 To .Height - 1
                Call CopyMemory(ByVal lpBits + lIdx * .Width * 4, ByVal uMapRect.pBitsPtr + lIdx * uMapRect.Pitch, .Width * 4)
            Next
            .Duplication.UnMapDesktopSurface
        Else
            .Context.CopyResource .StageTexture, .DesktopResource
            .Context.Map .StageTexture, 0, D3D11_MAP_READ, 0, uResource
            For lIdx = 0 To .Height - 1
                Call CopyMemory(ByVal lpBits + lIdx * .Width * 4, ByVal uResource.pDataPtr + lIdx * uResource.RowPitch, .Width * 4)
            Next
            For lIdx = 0 To .NumDirty - 1
                lY = .DirtyRects(lIdx).Top
                For lX = .DirtyRects(lIdx).Left To .DirtyRects(lIdx).Right - 1
                    Call CopyMemory(ByVal lpBits + (.DirtyRects(lIdx).Top * .Width + lX) * 4, BORDER_COLOR, 4)
                    Call CopyMemory(ByVal lpBits + ((.DirtyRects(lIdx).Bottom - 1) * .Width + lX) * 4, BORDER_COLOR, 4)
                Next
                lX = .DirtyRects(lIdx).Left
                For lY = .DirtyRects(lIdx).Top To .DirtyRects(lIdx).Bottom - 1
                    Call CopyMemory(ByVal lpBits + (lY * .Width + .DirtyRects(lIdx).Left) * 4, BORDER_COLOR, 4)
                    Call CopyMemory(ByVal lpBits + (lY * .Width + .DirtyRects(lIdx).Right - 1) * 4, BORDER_COLOR, 4)
                Next
            Next
'            For lIdx = 0 To .NumDirty - 1
'                Debug.Print "(" & .DirtyRects(lIdx).Left & ";" & .DirtyRects(lIdx).Left & "-" & .DirtyRects(lIdx).Right & ";" & .DirtyRects(lIdx).Bottom & "), ";
'                lX = .DirtyRects(lIdx).Left
'                For lY = .DirtyRects(lIdx).Top To .DirtyRects(lIdx).Bottom - 1
'                    Call CopyMemory(ByVal lpBits + (lY * .Width + lX) * 4, ByVal uResource.pDataPtr + lY * uResource.RowPitch + lX * 4, (.DirtyRects(lIdx).Right - lX) * 4)
'                Next
'            Next
            .Context.Unmap .StageTexture, 0
        End If
    End With
    If Not pvCreateStdPicture(hDib, oPicDesktop) Then
        GoTo QH
    End If
    hDib = 0
    '--- success
    pvCaptureScreen = True
QH:
    If hPointerDib <> 0 Then
        Call DeleteObject(hPointerDib)
        hPointerDib = 0
    End If
    If hDib <> 0 Then
        Call DeleteObject(hDib)
        hDib = 0
    End If
    If hMemDC <> 0 Then
        Call DeleteDC(hMemDC)
        hMemDC = 0
    End If
    If uResource.pDataPtr <> 0 Then
        uCtx.Context.Unmap uCtx.StageTexture, 0
    End If
'    If Not oPicDesktop Is Nothing Then
'        Debug.Print "Elapsed=" & Format(TimerEx - dblTimerEx, "0.000")
'    End If
    Exit Function
EH:
    PrintError FUNC_NAME
    Resume QH
End Function

Private Function pvCreateDib(ByVal hMemDC As Long, ByVal lWidth As Long, ByVal lHeight As Long, hDib As Long, Optional lpBits As Long) As Boolean
    Const FUNC_NAME    As String = "pvCreateDib"
    Dim uHdr            As BITMAPINFOHEADER
   
    On Error GoTo EH
    With uHdr
        .biSize = Len(uHdr)
        .biPlanes = 1
        .biBitCount = 32
        .biWidth = lWidth
        .biHeight = -lHeight
        .biSizeImage = 4 * lWidth * lHeight
    End With
    hDib = CreateDIBSection(hMemDC, uHdr, DIB_RGB_COLORS, lpBits, 0, 0)
    If hDib = 0 Then
        GoTo QH
    End If
    '--- success
    pvCreateDib = True
QH:
    Exit Function
EH:
    PrintError FUNC_NAME
    Resume QH
End Function

Private Function pvCreateStdPicture(hDib As Long, oPic As StdPicture) As Boolean
    Const FUNC_NAME    As String = "pvCreateStdPicture"
    Dim uDesc          As PICTDESC
    Dim aGUID(0 To 3)  As Long
   
    On Error GoTo EH
    With uDesc
        .lSize = Len(uDesc)
        .lType = vbPicTypeBitmap
        .hBmp = hDib
    End With
    '--- IID_IPicture
    aGUID(0) = &H7BF80980
    aGUID(1) = &H101ABF32
    aGUID(2) = &HAA00BB8B
    aGUID(3) = &HAB0C3000
    If OleCreatePictureIndirect(uDesc, aGUID(0), 1, oPic) <> 0 Then
        GoTo QH
    End If
    '--- success
    pvCreateStdPicture = True
QH:
    Exit Function
EH:
    PrintError FUNC_NAME
End Function

Public Property Get TimerEx() As Double
    Dim cFreq          As Currency
    Dim cValue          As Currency
   
    Call QueryPerformanceFrequency(cFreq)
    Call QueryPerformanceCounter(cValue)
    TimerEx = cValue / cFreq
End Property

'=========================================================================
' Control events
'=========================================================================

Private Sub Form_Load()
    Dim vElem          As Variant
   
    For Each vElem In pvEnumOutputDeviceNames
        Combo1.AddItem vElem(0)
    Next
    Combo1.ListIndex = 0
End Sub

Private Sub Form_Resize()
    Dim dblTop          As Double
   
    If WindowState <> vbMinimized Then
        dblTop = Combo1.Top + Combo1.Height + Combo1.Top
        imgDesktop.Move 0, dblTop, ScaleWidth, ScaleHeight - dblTop
    End If
End Sub

Private Sub Combo1_Click()
    If Combo1.ListIndex >= 0 Then
        If Not pvInitCapture(m_uCtx, Combo1.Text, Timer1.Interval) Then
            Timer1.Enabled = False
        Else
            Timer1.Enabled = True
            Timer1_Timer
        End If
    End If
End Sub

Private Sub imgDesktop_Click()
    Timer1.Enabled = Not Timer1.Enabled
End Sub

Private Sub Timer1_Timer()
    Dim oPicDesktop    As StdPicture
    Dim oPicPointer    As StdPicture
   
    If pvCaptureScreen(m_uCtx, oPicDesktop, oPicPointer) Then
        If Not oPicDesktop Is Nothing Then
            Set imgDesktop.Picture = oPicDesktop
        End If
        If Not oPicPointer Is Nothing Then
            Set imgPointer.Picture = oPicPointer
        End If
        With m_uCtx.FrameInfo.PointerPosition
            imgPointer.Visible = .Visible
            If .Visible Then
                imgPointer.Move _
                    imgDesktop.Left + ScaleX(.Position.X, vbPixels) * imgDesktop.Width \ ScaleX(m_uCtx.Width, vbPixels), _
                    imgDesktop.Top + ScaleY(.Position.Y, vbPixels) * imgDesktop.Height \ ScaleX(m_uCtx.Height, vbPixels), _
                    ScaleX(m_uCtx.PointerInfo.Width, vbPixels) * imgDesktop.Width \ ScaleX(m_uCtx.Width, vbPixels), _
                    ScaleY(m_uCtx.PointerInfo.Height, vbPixels) * imgDesktop.Height \ ScaleX(m_uCtx.Height, vbPixels)
            End If
        End With
    ElseIf Not pvInitCapture(m_uCtx, m_uCtx.DeviceName, Timer1.Interval) Then
        Timer1.Enabled = False
    End If
End Sub

Private Sub Command1_Click()
    Dim dblTargetHeight As Double
   
    dblTargetHeight = imgDesktop.Width * m_uCtx.Height \ m_uCtx.Width
    Height = imgDesktop.Top + dblTargetHeight + (Height - ScaleHeight)
End Sub

There is a custom DirectX 11 type library (both .idl and .tlb in the archive) with just enough interfaces to instantiate IDXGIOutputDuplication and capture a texture which is then converted to a DIB which is then converted to a StdPicture and placed in a stretching Image control so the scale quality is poor.

The idea is for a remote screen sharing implementation to transport only screen diffs using GetFrameDirtyRects, GetFrameMoveRects and GetFramePointerShape methods (instead of current full screen capture) with some fast LZ4 compression on top and some Foreward Error Correction implementation over UDP, including UDP hole punching for serverless peer-to-peer connections when both parties happen to be behind NAT or alternative is using STUN/TURN infrastructure as currently provided by google for WebRTC.

cheers,
</wqw>
Attached Files

Viewing all articles
Browse latest Browse all 1463

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>