ScreenBuffer - DirectX screen capture

Post your working scripts, libraries and tools.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

ScreenBuffer - DirectX screen capture

Post by iseahound » 13 Jul 2021, 13:16

BETA

Last year, @malcev sent me some DirectX screen capture code, but it was too complex to work with in AutoHotkey v1. I finally got around to playing around with it, and will be developing it further.

Script

Code: Select all

class ScreenBuffer {

   static GDI() => this(1)
   static DIRECTX9() => this(9)
   static DIRECTX11() => this(11)

   __New(engine := 1) {
      this.engine := engine

      ; Get true virtual screen coordinates.
      dpi := DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")
      sx := DllCall("GetSystemMetrics", "int", 76, "int")
      sy := DllCall("GetSystemMetrics", "int", 77, "int")
      sw := DllCall("GetSystemMetrics", "int", 78, "int")
      sh := DllCall("GetSystemMetrics", "int", 79, "int")
      DllCall("SetThreadDpiAwarenessContext", "ptr", dpi, "ptr")

      this.sx := sx
      this.sy := sy
      this.sw := sw
      this.sh := sh

      if (engine = 1)
         this.Init_GDI(sw, sh)

      if (engine = 9)
         this.Init_DIRECTX9()

      if (engine = 11)
         this.Init_DIRECTX11()

      ; Allocate a buffer and set the pointer and size.
      this.Update()

      ; Startup gdiplus.
      DllCall("LoadLibrary", "str", "gdiplus")
      si := Buffer(A_PtrSize = 4 ? 16:24, 0) ; sizeof(GdiplusStartupInput) = 16, 24
         NumPut("uint", 0x1, si)
      DllCall("gdiplus\GdiplusStartup", "ptr*", &pToken:=0, "ptr", si, "ptr", 0)

      ; Create a Bitmap with 32-bit pre-multiplied ARGB. (Owned by this object!)
      DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", sw, "int", sh, "uint", this.size / sh, "uint", 0xE200B, "ptr", this.ptr, "ptr*", &pBitmap:=0)
      DllCall("gdiplus\GdipGetImageGraphicsContext", "ptr", pBitmap, "ptr*", &Graphics:=0)
      DllCall("gdiplus\GdipTranslateWorldTransform", "ptr", Graphics, "float", -sx, "float", -sy, "int", 0)

      this.pToken := pToken
      this.pBitmap := pBitmap
      this.Graphics := Graphics

      return this
   }

   __Delete() {
      DllCall("gdiplus\GdipDeleteGraphics", "ptr", this.Graphics)
      DllCall("gdiplus\GdipDisposeImage", "ptr", this.pBitmap)
      DllCall("gdiplus\GdiplusShutdown", "ptr", this.pToken)
      DllCall("FreeLibrary", "ptr", DllCall("GetModuleHandle", "str", "gdiplus", "ptr"))

      this.Cleanup()
   }

   __Item[x, y] {
      get => Format("0x{:X}", NumGet(this.ptr + 4*(y*this.sw + x), "uint"))
   }

   Call(p*) => this.Update(p*)

   Init_GDI(sw, sh) {
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      bi := Buffer(40, 0)                    ; sizeof(bi) = 40
         NumPut(  "uint",        40, bi,  0) ; Size
         NumPut(   "int",        sw, bi,  4) ; Width
         NumPut(   "int",       -sh, bi,  8) ; Height - Negative so (0, 0) is top-left.
         NumPut("ushort",         1, bi, 12) ; Planes
         NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")

      this.ptr := pBits
      this.size := 4 * sw * sh

      Update() {
         ; Retrieve a shared device context for the screen.
         static sdc := DllCall("GetDC", "ptr", 0, "ptr")

         ; Copies a portion of the screen to a new device context.
         DllCall("gdi32\BitBlt"
                  , "ptr", hdc, "int", 0, "int", 0, "int", sw, "int", sh
                  , "ptr", sdc, "int", 0, "int", 0, "uint", 0x00CC0020 | 0x40000000) ; SRCCOPY | CAPTUREBLT

         ; Remember to enable method chaining.
         return this
      }

      Cleanup() {
         DllCall("SelectObject", "ptr", hdc, "ptr", obm)
         DllCall("DeleteObject", "ptr", hbm)
         DllCall("DeleteDC",     "ptr", hdc)
      }

      this.Update := (*) => Update()
      this.Cleanup := (*) => Cleanup()
   }

   Init_DIRECTX9() {

      assert d3d := Direct3DCreate9(D3D_SDK_VERSION := 32), "Direct3DCreate9 failed."

      ComCall(IDirect3D9_GetAdapterDisplayMode := 8, d3d, "uint", D3DADAPTER_DEFAULT := 0, "ptr", D3DDISPLAYMODE := Buffer(16, 0))
      Windowed := true
      BackBufferCount := 1
      BackBufferHeight := NumGet(D3DDISPLAYMODE, 4, "uint")
      BackBufferWidth := NumGet(D3DDISPLAYMODE, 0, "uint")
      SwapEffect := 1 ; D3DSWAPEFFECT_DISCARD
      hDeviceWindow := WinExist("A")

      ; create device & capture surface
      D3DPRESENT_PARAMETERS := Buffer(48+2*A_PtrSize, 0)
         NumPut("uint",  BackBufferWidth, D3DPRESENT_PARAMETERS, 0)
         NumPut("uint", BackBufferHeight, D3DPRESENT_PARAMETERS, 4)
         NumPut("uint",  BackBufferCount, D3DPRESENT_PARAMETERS, 12)
         NumPut("uint",       SwapEffect, D3DPRESENT_PARAMETERS, 24)
         NumPut( "ptr",    hDeviceWindow, D3DPRESENT_PARAMETERS, 24+A_PtrSize)
         NumPut( "int",         Windowed, D3DPRESENT_PARAMETERS, 24+2*A_PtrSize)
      ComCall(IDirect3D9_CreateDevice := 16, d3d
               ,   "uint", D3DADAPTER_DEFAULT := 0
               ,   "uint", D3DDEVTYPE_HAL := 1
               ,    "ptr", 0 ; hFocusWindow
               ,   "uint", D3DCREATE_SOFTWARE_VERTEXPROCESSING := 0x00000020
               ,    "ptr", D3DPRESENT_PARAMETERS
               ,   "ptr*", &device:=0)
      ComCall(IDirect3DDevice9_CreateOffscreenPlainSurface := 36, device
               ,   "uint", BackBufferWidth
               ,   "uint", BackBufferHeight
               ,   "uint", D3DFMT_A8R8G8B8 := 21
               ,   "uint", D3DPOOL_SYSTEMMEM := 2
               ,   "ptr*", &surface:=0
               ,    "ptr", 0)

      Update(this) {
         ; get the data
         ComCall(IDirect3DDevice9_GetFrontBufferData := 33, device, "uint", 0, "ptr", surface)

         ; copy it into our buffers
         ComCall(IDirect3DSurface9_LockRect := 13, surface, "ptr", D3DLOCKED_RECT := Buffer(A_PtrSize*2), "ptr", 0, "uint", 0)
         pitch := NumGet(D3DLOCKED_RECT, 0, "int")
         pBits := NumGet(D3DLOCKED_RECT, A_PtrSize, "ptr")
         ComCall(IDirect3DSurface9_UnlockRect := 14, surface)

         this.ptr := pBits
         this.size := pitch * BackBufferHeight

         ; Remember to enable method chaining.
         return this
      }

      Cleanup(this) {
         ObjRelease(surface)
         ObjRelease(device)
         ObjRelease(d3d)
      }

      Direct3DCreate9(SDKVersion) {
         if !DllCall("GetModuleHandle","str","d3d9")
            DllCall("LoadLibrary","str","d3d9")
         return DllCall("d3d9\Direct3DCreate9", "uint", SDKVersion, "ptr")
      }

      assert(statement, message) {
         if !statement
            throw ValueError(message, -1, statement)
      }

      this.Update := Update
      this.Cleanup := Cleanup
   }

   Init_DIRECTX11() {

      assert IDXGIFactory := CreateDXGIFactory(), "Create IDXGIFactory failed."

      loop {
         ComCall(IDXGIFactory_EnumAdapters := 7, IDXGIFactory, "uint", A_Index-1, "ptr*", &IDXGIAdapter:=0)

         loop {
            try ComCall(IDXGIAdapter_EnumOutputs := 7, IDXGIAdapter, "uint", A_Index-1, "ptr*", &IDXGIOutput:=0)
            catch OSError as e
               if e.number = 0x887A0002 ; DXGI_ERROR_NOT_FOUND
                  break
               else throw

            ComCall(IDXGIOutput_GetDesc := 7, IDXGIOutput, "ptr", DXGI_OUTPUT_DESC := Buffer(88+A_PtrSize, 0))
            Width             := NumGet(DXGI_OUTPUT_DESC, 72, "int")
            Height            := NumGet(DXGI_OUTPUT_DESC, 76, "int")
            AttachedToDesktop := NumGet(DXGI_OUTPUT_DESC, 80, "int")
            if (AttachedToDesktop = 1)
               break 2
         }
      }

      assert AttachedToDesktop, "No adapter attached to desktop."

      DllCall("D3D11\D3D11CreateDevice"
               ,    "ptr", IDXGIAdapter                 ; pAdapter
               ,    "int", D3D_DRIVER_TYPE_UNKNOWN := 0 ; DriverType
               ,    "ptr", 0                            ; Software
               ,   "uint", 0                            ; Flags
               ,    "ptr", 0                            ; pFeatureLevels
               ,   "uint", 0                            ; FeatureLevels
               ,   "uint", D3D11_SDK_VERSION := 7       ; SDKVersion
               ,   "ptr*", &d3d_device:=0               ; ppDevice
               ,   "ptr*", 0                            ; pFeatureLevel
               ,   "ptr*", &d3d_context:=0              ; ppImmediateContext
               ,"HRESULT")

      IDXGIOutput1 := ComObjQuery(IDXGIOutput, "{00cddea8-939b-4b83-a340-a685226666cc}")
      ComCall(IDXGIOutput1_DuplicateOutput := 22, IDXGIOutput1, "ptr", d3d_device, "ptr*", &Duplication:=0)
      ComCall(IDXGIOutputDuplication_GetDesc := 7, Duplication, "ptr", DXGI_OUTDUPL_DESC := Buffer(36, 0))
      DesktopImageInSystemMemory := NumGet(DXGI_OUTDUPL_DESC, 32, "uint")
      Sleep 50   ; As I understand - need some sleep for successful connecting to IDXGIOutputDuplication interface

      D3D11_TEXTURE2D_DESC := Buffer(44, 0)
         NumPut("uint",                            width, D3D11_TEXTURE2D_DESC,  0)   ; Width
         NumPut("uint",                           height, D3D11_TEXTURE2D_DESC,  4)   ; Height
         NumPut("uint",                                1, D3D11_TEXTURE2D_DESC,  8)   ; MipLevels
         NumPut("uint",                                1, D3D11_TEXTURE2D_DESC, 12)   ; ArraySize
         NumPut("uint", DXGI_FORMAT_B8G8R8A8_UNORM := 87, D3D11_TEXTURE2D_DESC, 16)   ; Format
         NumPut("uint",                                1, D3D11_TEXTURE2D_DESC, 20)   ; SampleDescCount
         NumPut("uint",                                0, D3D11_TEXTURE2D_DESC, 24)   ; SampleDescQuality
         NumPut("uint",         D3D11_USAGE_STAGING := 3, D3D11_TEXTURE2D_DESC, 28)   ; Usage
         NumPut("uint",                                0, D3D11_TEXTURE2D_DESC, 32)   ; BindFlags
         NumPut("uint", D3D11_CPU_ACCESS_READ := 0x20000, D3D11_TEXTURE2D_DESC, 36)   ; CPUAccessFlags
         NumPut("uint",                                0, D3D11_TEXTURE2D_DESC, 40)   ; MiscFlags
      ComCall(ID3D11Device_CreateTexture2D := 5, d3d_device, "ptr", D3D11_TEXTURE2D_DESC, "ptr", 0, "ptr*", &staging_tex:=0)


      ; Persist the concept of a desktop_resource as a closure???
      local desktop_resource

      Update(this, timeout := unset) {
         ; Unbind resources.
         Unbind()

         ; Allocate a shared buffer for all calls of AcquireNextFrame.
         static DXGI_OUTDUPL_FRAME_INFO := Buffer(48, 0)

         if !IsSet(timeout) {
            ; The following loop structure repeatedly checks for a new frame.
            loop {
               ; Ask if there is a new frame available immediately.
               try ComCall(IDXGIOutputDuplication_AcquireNextFrame := 8, Duplication, "uint", 0, "ptr", DXGI_OUTDUPL_FRAME_INFO, "ptr*", &desktop_resource:=0)
               catch OSError as e
                  if e.number = 0x887A0027 ; DXGI_ERROR_WAIT_TIMEOUT
                     continue
                  else throw

               ; Exclude mouse movement events by ensuring LastPresentTime is greater than zero.
               if NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64") > 0
                  break

               ; Continue the loop by releasing resources.
               ObjRelease(desktop_resource)
               ComCall(IDXGIOutputDuplication_ReleaseFrame := 14, Duplication)
            }
         } else {
            try ComCall(IDXGIOutputDuplication_AcquireNextFrame := 8, Duplication, "uint", timeout, "ptr", DXGI_OUTDUPL_FRAME_INFO, "ptr*", &desktop_resource:=0)
            catch OSError as e
               if e.number = 0x887A0027 ; DXGI_ERROR_WAIT_TIMEOUT
                  return this ; Remember to enable method chaining.
               else throw

            if NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64") = 0
               return this ; Remember to enable method chaining.
         }

         ; map new resources.
         if (DesktopImageInSystemMemory = 1) {
            static DXGI_MAPPED_RECT := Buffer(A_PtrSize*2, 0)
            ComCall(IDXGIOutputDuplication_MapDesktopSurface := 12, Duplication, "ptr", DXGI_MAPPED_RECT)
            pitch := NumGet(DXGI_MAPPED_RECT, 0, "int")
            pBits := NumGet(DXGI_MAPPED_RECT, A_PtrSize, "ptr")
         }
         else {
            tex := ComObjQuery(desktop_resource, "{6f15aaf2-d208-4e89-9ab4-489535d34f9c}") ; ID3D11Texture2D
            ComCall(ID3D11DeviceContext_CopyResource := 47, d3d_context, "ptr", staging_tex, "ptr", tex)
            static D3D11_MAPPED_SUBRESOURCE := Buffer(8+A_PtrSize, 0)
            ComCall(ID3D11DeviceContext_Map := 14, d3d_context, "ptr", staging_tex, "uint", 0, "uint", D3D11_MAP_READ := 1, "uint", 0, "ptr", D3D11_MAPPED_SUBRESOURCE)
            pBits := NumGet(D3D11_MAPPED_SUBRESOURCE, 0, "ptr")
            pitch := NumGet(D3D11_MAPPED_SUBRESOURCE, A_PtrSize, "uint")
         }

         this.ptr := pBits
         this.size := pitch * height

         ; Remember to enable method chaining.
         return this
      }

      Unbind() {
         if IsSet(desktop_resource) && desktop_resource != 0 {
            if (DesktopImageInSystemMemory = 1)
               ComCall(IDXGIOutputDuplication_UnMapDesktopSurface := 13, Duplication)
            else
               ComCall(ID3D11DeviceContext_Unmap := 15, d3d_context, "ptr", staging_tex, "uint", 0)

            ObjRelease(desktop_resource)
            ComCall(IDXGIOutputDuplication_ReleaseFrame := 14, Duplication)
         }
      }

      Cleanup(this) {
         Unbind()
         ObjRelease(staging_tex)
         ObjRelease(duplication)
         ObjRelease(d3d_context)
         ObjRelease(d3d_device)
         IDXGIOutput1 := ""
         ObjRelease(IDXGIOutput)
         ObjRelease(IDXGIAdapter)
         ObjRelease(IDXGIFactory)
      }

      CreateDXGIFactory() {
         if !DllCall("GetModuleHandle", "str", "DXGI")
            DllCall("LoadLibrary", "str", "DXGI")
         if !DllCall("GetModuleHandle", "str", "D3D11")
            DllCall("LoadLibrary", "str", "D3D11")
         DllCall("ole32\CLSIDFromString", "wstr", "{7b7166ec-21c7-44ae-b21a-c9ae321ae369}", "ptr", riid := Buffer(16, 0), "HRESULT")
         DllCall("DXGI\CreateDXGIFactory1", "ptr", riid, "ptr*", &ppFactory:=0, "HRESULT")
         return ppFactory
      }

      assert(statement, message) {
         if !statement
            throw ValueError(message, -1, statement)
      }

      this.Update := Update
      this.Cleanup := Cleanup
   }

   Save(filepath) {
      return this.put_file(this.pBitmap, filepath)
   }

   SaveRaw(filepath) {

      static bm := CreateBitmapHeader()

      CreateBitmapHeader() {
         bm := Buffer(54)

         StrPut("BM", bm, "CP0")                ; identifier
         NumPut(  "uint", 54+this.size, bm,  2) ; file size
         NumPut(  "uint",            0, bm,  6) ; reserved
         NumPut(  "uint",           54, bm, 10) ; bitmap data offset

         ; BITMAPINFOHEADER struct
         NumPut(  "uint",           40, bm, 14) ; Size
         NumPut(  "uint",      this.sw, bm, 18) ; Width
         NumPut(   "int",     -this.sh, bm, 22) ; Height - Negative so (0, 0) is top-left.
         NumPut("ushort",            1, bm, 26) ; Planes
         NumPut("ushort",           32, bm, 28) ; BitCount / BitsPerPixel

         NumPut(  "uint",            0, bm, 30) ; biCompression
         NumPut(  "uint",    this.size, bm, 34) ; biSizeImage
         NumPut(   "int",            0, bm, 38) ; biXPelsPerMeter
         NumPut(   "int",            0, bm, 42) ; biYPelsPerMeter
         NumPut(  "uint",            0, bm, 46) ; biClrUsed
         NumPut(  "uint",            0, bm, 50) ; biClrImportant

         return bm
      }

      loop
         try
            if file := FileOpen(filepath, "w")
               break
            else throw
         catch
            if A_Index < 6
               Sleep (2**(A_Index-1) * 30)
            else throw

      file.RawWrite(bm)   ; Writes 54 bytes of bitmap file header.
      file.RawWrite(this) ; Writes raw 32-bit ARGB pixel data.
      file.Close()

      return filepath
   }

   Search() {
   }

   ; ScreenCoordinates(ByRef sx, ByRef sy, ByRef sw, ByRef sh) {
   put_file(pBitmap, filepath := "", quality := "") {
      ; Thanks tic - https://www.autohotkey.com/boards/viewtopic.php?t=6517

      ; Remove whitespace. Seperate the filepath. Adjust for directories.
      filepath := Trim(filepath)
      SplitPath filepath,, &directory, &extension, &filename
      if DirExist(filepath)
         directory .= "\" filename, filename := ""
      if (directory != "" && !DirExist(directory))
         DirCreate(directory)
      directory := (directory != "") ? directory : "."

      ; Validate filepath, defaulting to PNG. https://stackoverflow.com/a/6804755
      if !(extension ~= "^(?i:bmp|dib|rle|jpg|jpeg|jpe|jfif|gif|tif|tiff|png)$") {
         if (extension != "")
            filename .= "." extension
         extension := "png"
      }
      filename := RegExReplace(filename, "S)(?i:^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:|?*\x00-\x1F\x22\/\\])")
      if (filename == "")
         filename := FormatTime(, "yyyy-MM-dd HH꞉mm꞉ss")
      filepath := directory "\" filename "." extension

      ; Fill a buffer with the available encoders.
      DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &count:=0, "uint*", &size:=0)
      ci := Buffer(size)
      DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", ci)
      if !(count && size)
         throw Error("Could not get a list of image codec encoders on this system.")

      ; Search for an encoder with a matching extension.
      Loop count
         EncoderExtensions := StrGet(NumGet(ci, (idx:=(48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "uptr"), "UTF-16")
      until InStr(EncoderExtensions, "*." extension)

      ; Get the pointer to the index/offset of the matching encoder.
      if !(pCodec := ci.ptr + idx)
         throw Error("Could not find a matching encoder for the specified file format.")

      ; JPEG is a lossy image format that requires a quality value from 0-100. Default quality is 75.
      if (extension ~= "^(?i:jpg|jpeg|jpe|jfif)$"
      && IsInteger(quality) && 0 <= quality && quality <= 100 && quality != 75) {
         DllCall("gdiplus\GdipGetEncoderParameterListSize", "ptr", pBitmap, "ptr", pCodec, "uint*", &size:=0)
         EncoderParameters := Buffer(size, 0)
         DllCall("gdiplus\GdipGetEncoderParameterList", "ptr", pBitmap, "ptr", pCodec, "uint", size, "ptr", EncoderParameters)

         ; Search for an encoder parameter with 1 value of type 6.
         Loop NumGet(EncoderParameters, "uint")
            elem := (24+A_PtrSize)*(A_Index-1) + A_PtrSize
         until (NumGet(EncoderParameters, elem+16, "uint") = 1) && (NumGet(EncoderParameters, elem+20, "uint") = 6)

         ; struct EncoderParameter - http://www.jose.it-berater.org/gdiplus/reference/structures/encoderparameter.htm
         ep := EncoderParameters.ptr + elem - A_PtrSize                ; sizeof(EncoderParameter) = 28, 32
            NumPut(  "uptr",       1, ep)                              ; Must be 1.
            NumPut(  "uint",       4, ep, 20+A_PtrSize)                ; Type
            NumPut(  "uint", quality, NumGet(ep+24+A_PtrSize, "uptr")) ; Value (pointer)
      }

      ; Write the file to disk using the specified encoder and encoding parameters.
      Loop 6 ; Try this 6 times.
         if (A_Index > 1)
            Sleep (2**(A_Index-2) * 30)
      until (result := !DllCall("gdiplus\GdipSaveImageToFile", "ptr", pBitmap, "wstr", filepath, "ptr", pCodec, "uint", IsSet(ep) ? ep : 0))
      if !(result)
         throw Error("Could not save file to disk.")

      return filepath
   }
}
Benchmark

Code: Select all

#include ScreenBuffer.ahk

sb1 := ScreenBuffer.GDI()
sb2 := ScreenBuffer.DIRECTX9()
sb3 := ScreenBuffer.DIRECTX11()

MsgBox "Please watch a 60fps video or play a game during testing.`nPress OK to begin."
Sleep 2000 ; Minimize variance on start.

; Number of Frames per test
f := 60


; ------------------------ |
; Screen Capture Framerate |
; ------------------------ |

DllCall("QueryPerformanceFrequency", "int64*", &frequency:=0)
DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb1()
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
a := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb2()
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
b := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3()
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
c := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3(0)
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
d := f / ((end - start) / frequency)

MsgBox "GDI:`t`t"    Round(a, 2)   " fps"
   . "`nDX9:`t`t"    Round(b, 2)   " fps"
   . "`nDX11:`t`t"   Round(c, 2)   " fps"
   . "`nDX11 (uncapped):`t"   Round(d, 2)   " fps"
   . "`n`nTotal number of frames per test: " f
   , "(Test 1 of 3) Screen Capture Framerate"


; -------------------------------- |
; Save To Bitmap Capture Framerate |
; -------------------------------- |

DllCall("QueryPerformanceFrequency", "int64*", &frequency:=0)
DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb1().SaveRaw("image.bmp")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
a := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb2().SaveRaw("image.bmp")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
b := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3().SaveRaw("image.bmp")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
c := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3(0).SaveRaw("image.bmp")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
d := f / ((end - start) / frequency)

MsgBox "GDI:`t`t"    Round(a, 2)   " fps"
   . "`nDX9:`t`t"    Round(b, 2)   " fps"
   . "`nDX11:`t`t"   Round(c, 2)   " fps"
   . "`nDX11 (uncapped):`t"   Round(d, 2)   " fps"
   . "`n`nTotal number of frames per test: " f
   , "(Test 2 of 3) Save To Bitmap Capture Framerate"


; ---------------------- |
; JPEG Capture Framerate |
; ---------------------- |

DllCall("QueryPerformanceFrequency", "int64*", &frequency:=0)
DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb1().Save("image.jpg")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
a := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb2().Save("image.jpg")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
b := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3().Save("image.jpg")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
c := f / ((end - start) / frequency)

DllCall("QueryPerformanceCounter", "int64*", &start:=0)
loop f
   sb3(0).Save("image.jpg")
DllCall("QueryPerformanceCounter", "int64*", &end:=0)
d := f / ((end - start) / frequency)

MsgBox "GDI:`t`t"    Round(a, 2)   " fps"
   . "`nDX9:`t`t"    Round(b, 2)   " fps"
   . "`nDX11:`t`t"   Round(c, 2)   " fps"
   . "`nDX11 (uncapped):`t"   Round(d, 2)   " fps"
   . "`n`nTotal number of frames per test: " f
   , "(Test 3 of 3) JPEG Capture Framerate"
A few notes:
sb() and sb.update() are the same thing.
When using DirectX11, AcquireNextFrame will only return a new frame if and only if the screen changes. In other words, updating may hang until the user's screen changes.
Access indivdual pixels like pixel := sb[x,y]
By default this is a buffer object with .ptr and .size properties.
Interoperate with GDI+ via the exposed .pBitmap and .Graphics pointers.
To save a file to disk use sb.save(filepath)
To save a bitmap to disk use sb.SaveRaw(filepath) (faster, bigger file)

Things to do:
✅ Increase the speed of saving a file to disk. (This is not optimized at all currently.)
✅ Implement DirectX9
Support for masking / cropping
Add PixelSearch.

Known issues:
✅ If your screen width is not divisible by 4 weird things may happen with DirectX11.
The desktop duplication API is limited to one per process.

This entire post will be updated and rewritten at some point in the future. Please quote the relevant parts.
https://github.com/iseahound/ScreenBuffer
Last edited by iseahound on 23 Aug 2021, 20:31, edited 9 times in total.

arcticir
Posts: 694
Joined: 17 Nov 2013, 11:32

Re: ScreenBuffer - DirectX screen capture

Post by arcticir » 14 Jul 2021, 11:44

Cool, thanks for sharing.

swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: ScreenBuffer - DirectX screen capture

Post by swagfag » 14 Jul 2021, 18:17

Code: Select all

DllCall("gdiplus\GdipGetImageGraphicsContext", "ptr", pBitmap, "ptr*", Graphics:=0)
Graphics will always be a 0-ptr, since u arent capturing anything byref(missing &)

Code: Select all

	...
	; Remember to enable method chaining.
	return this
}

this.Update := (*) => Update()
the problem with this is, if u capture this in the Closure(which u do) and then u assign the Closure(which already contains a captured this inside of it) back to any of this's properties, uve just created a circular reference. essentially:

Code: Select all

this.SomeProperty := thingThatHasCapturedThis
so if whatever object u had instantiated ever goes out of scope and u didnt clear the circular reference manually(eg sb1.Update := '') before it went out of scope, the object wont have its destructor called. since u seem to be doing some kinda resource cleanup in ur destructor, ure leaking memory and the resources probably too. to fix, change it to:

Code: Select all

update(this) {
	...
	return this
}

this.Update := update

Code: Select all

__Item[x, y] {
      get => Format("0x{:X}", NumGet(this.ptr + 4*(y*this.sw + x), "uint"))
   }
this is probably going to be used in loops or for comparisons where formatting isnt relevant but speed is. u can make a separate property that returns it hex formatted. u could also bind the this. values, if nothing's gonna mutate them but im not sure how much of an improvement that would be. ud have to bench

Code: Select all

__Item[x, y] {
      get => Format("0x{:X}", NumGet(this.ptr + 4*(y*this.sw + x), "uint"))
   }
this will probably crash for some specific x/y values on some specific multimonitor, multigpu(eg GPU + iGPU) system configurations for DIRECTX11()(and probably DIRECTX9() as well). this.sw is the entire combined width of the virtual desktop, while here:

Code: Select all

Init_DIRECTX11() {

		assert IDXGIFactory := CreateDXGIFactory(), "Create IDXGIFactory failed."

		loop {
			ComCall(IDXGIFactory_EnumAdapters := 7, IDXGIFactory, "uint", A_Index - 1, "ptr*", &IDXGIAdapter := 0)

			loop {
				try ComCall(IDXGIAdapter_EnumOutputs := 7, IDXGIAdapter, "uint", A_Index - 1, "ptr*", &IDXGIOutput := 0)
				catch OSError as e
					if e.number = 0x887A0002	; DXGI_ERROR_NOT_FOUND
						break
					else throw

						ComCall(IDXGIOutput_GetDesc := 7, IDXGIOutput, "ptr", DXGI_OUTPUT_DESC := Buffer(88 + A_PtrSize, 0))
				Width := NumGet(DXGI_OUTPUT_DESC, 72, "int")
				Height := NumGet(DXGI_OUTPUT_DESC, 76, "int")
				AttachedToDesktop := NumGet(DXGI_OUTPUT_DESC, 80, "int")
				if (AttachedToDesktop = 1)
					break 2
			}
		}
what ure doing is u enumerate the devices on the system and break as soon as u detect the first available one. this effectively gets u a single monitor, and since the buffer(and its size, and the memory ure allowed to touch) will later be defined in terms of Width and Height, it is given that the monitor's either width or height(or both depending on where its placed) will be < than the total combined virtual desktop width and height. so using the virtual desktop's width in the calculation may have u accessing out of bounds memory, which will crash the script
The desktop duplication API is limited to one per process.
then the class should probably prevent me from trying to do that to begin with. in a roundabout way it probably does(i assume one of the DX calls throws or something), but thats no indication whether it was by design or due to buggy code
overall nice script, but very weird class design. what is this for?

Code: Select all

   static GDI := (this) => ObjBindMethod(this, "call", 1)()
   static DIRECTX9 := (this) => ObjBindMethod(this, "call", 9)()
   static DIRECTX11 := (this) => ObjBindMethod(this, "call", 11)()
why not:

Code: Select all

static GDI() => this(1)
static DIRECTX9() => this(9)
static DIRECTX11() => this(11)
or better yet, why not separate classes altogether? ure switching on integers in the constructor. the constructors do different things, the properties do different things(or at least they should), the methods do different things. why is all this crammed into a single class lol?

Code: Select all

CreateDXGIFactory() {
			if !DllCall("GetModuleHandle", "str", "DXGI")
				DllCall("LoadLibrary", "str", "DXGI")
			if !DllCall("GetModuleHandle", "str", "D3D11")
				DllCall("LoadLibrary", "str", "D3D11")
u arent freeing those, u arent checking if they succeeded(though its probably very unlikely that they wouldnt), might as well #DllLoad them

Code: Select all

"ptr*", &staging_tex := 0)
all of these could have been wrapped in IUnknown ComValues() with probably minimal impact to performance and get rid of the clean up code, which would automatically force proper cleanup. though i havent checked if everything is being released properly. if it is, leave it. if not, then its something to consider

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by iseahound » 14 Jul 2021, 20:38

a) Oops botched copy paste, good catch.
b) The explanations regarding closures and circular references make sense. Haven't tested destruction yet.
c) PixelSearch will likely return an enumerator of x,y pixel values. NumPut is too slow, and it's faster to check in ASM/C. Binding this.ptr is ill advised, I have no assurances that the virtual pointer returned by DirectX is a constant.
d) Multi monitor is slated for a future release. Doing so depends on support for clipping regions/masks/cropping which has not been implemented yet because it likely depends on writing pixel search machine code to support it.
e) The Duplication object can be shared across multiple instances.
f) Because it's a glorified buffer object with a ptr and size property, and shared methods. The only thing that's different are the implementation details, and those are encapsulated/abstracted away such that the rest of the class does not care. In the future there will be pixelsearch etc.
g) Those are just reference counters, they will be automatically freed when the process exits.
h) I don't see a benefit to wrapping the pointers given the extra effort. But rest assured, all calls to ObjRelease() return 0, indicating the object has been freed in the proper order.

Thanks for the insightful feedback. Much of the logic was done by @malcev I've updated the main post with DirectX 9, has anyone experienced DX9 being faster than GDI?

User avatar
thqby
Posts: 406
Joined: 16 Apr 2021, 11:18
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by thqby » 17 Aug 2021, 05:59

I tested directx11 and found that the screenshots were crooked.
image.png
image.png (111.18 KiB) Viewed 4903 times

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: ScreenBuffer - DirectX screen capture

Post by kczx3 » 17 Aug 2021, 08:08

@thqby seems to work fine for me.

@iseahound forgive my ignorance but how exactly do the DX9 and DX11 methods write to the pBitmap property? I don't see anything in either Update method that explicitly writes to pBitmap or Graphics and want to understand that better.

User avatar
thqby
Posts: 406
Joined: 16 Apr 2021, 11:18
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by thqby » 17 Aug 2021, 10:08

CreateBitmap by GdipCreateBitmapFromScan0, and data from this.ptr. With update(), the data in this buffer will be updated.
@kczx3

Tested on another computer, it works fine.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: ScreenBuffer - DirectX screen capture

Post by kczx3 » 17 Aug 2021, 11:24

Ok, so by setting this.ptr at the end of the Update() call that updates the memory at that location and thus affects both pBitmap and Graphics?

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by iseahound » 18 Aug 2021, 09:01

I believe your screen dimensions are not a multiple of 16. i.e. 1920/16 = 120.

@kczx3 yes

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: ScreenBuffer - DirectX screen capture

Post by kczx3 » 18 Aug 2021, 10:55

Very cool.

User avatar
thqby
Posts: 406
Joined: 16 Apr 2021, 11:18
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by thqby » 20 Aug 2021, 08:13

Yeah, the screen resolution is 1366*768, Why is that?
@iseahound

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by iseahound » 23 Aug 2021, 15:53

That's a good question. I suppose the answer, "that's just the way it is" doesn't really answer your question satisfactorily, so I went ahead and did some research. Turns out, in the video memory (VRAM) DirectX uses something called High Level Shader Language (HLSL) which necessitates a 16 byte boundary. That means the screen width should be divisible by 4, because 1 pixel (ARGB) has 4 channels, and each color channel is 1 byte. So I made a mistake earlier, when I said it should be divisible by 16, the screen width needs to be divisible by 4.

As for why? It's probably faster to compute in blocks of 128-bits, (16 bytes), because of how the video card is designed. Now keep in mind, when you're doing DirectX screen capture, what's really happening behind the scenes is a memory transfer from the video card (VRAM) to your computer’s memory (RAM). And the really clever part about DirectX11 is that it doesn't copy the memory until you ask for it. That's why DX11 is so fast compared to BitBlt.

TL;DR Alignment on memory = faster.

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: ScreenBuffer - DirectX screen capture

Post by malcev » 23 Aug 2021, 16:17

thqby, try this code on ahk v1 and tell does it work ok and what You see in message box.
For me it works with Your resolution.
It should save screenshot as "1.png" in working directory.

Code: Select all

setbatchlines -1
; Load GDI+
DllCall("LoadLibrary", "str", "gdiplus")
VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0) ; sizeof(GdiplusStartupInput) = 16, 24
NumPut(0x1, si, "uint")
DllCall("gdiplus\GdiplusStartup", "ptr*", pToken:=0, "ptr", &si, "ptr", 0)
extension := "png"
DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", count:=0, "uint*", size:=0)
VarSetCapacity(ci, size)
DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", &ci)
if !(count && size)
   throw Exception("Could not get a list of image codec encoders on this system.")
Loop % count
   EncoderExtensions := StrGet(NumGet(ci, (idx:=(48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "uptr"), "UTF-16")
      until InStr(EncoderExtensions, "*." extension)
if !(pCodec := &ci + idx)
   throw Exception("Could not find a matching encoder for the specified file format.")

IDXGIFactory := CreateDXGIFactory()
if !IDXGIFactory
{
   MsgBox, 16, Error, Create IDXGIFactory failed.
   ExitApp
}
loop
{
   IDXGIFactory_EnumAdapters(IDXGIFactory, A_Index-1, IDXGIAdapter)
   loop
   {
      hr := IDXGIAdapter_EnumOutputs(IDXGIAdapter, A_Index-1, IDXGIOutput)
      if (hr = "DXGI_ERROR_NOT_FOUND")
         break
      VarSetCapacity(DXGI_OUTPUT_DESC, 88+A_PtrSize, 0)
      IDXGIOutput_GetDesc(IDXGIOutput, &DXGI_OUTPUT_DESC)
      Width := NumGet(DXGI_OUTPUT_DESC, 72, "int")
      Height := NumGet(DXGI_OUTPUT_DESC, 76, "int")
      AttachedToDesktop := NumGet(DXGI_OUTPUT_DESC, 80, "int")
      if (AttachedToDesktop = 1)
         break 2         
   }
}
if (AttachedToDesktop != 1)
{
   MsgBox, 16, Error, No adapter attached to desktop
   ExitApp
}
D3D11CreateDevice(IDXGIAdapter, D3D_DRIVER_TYPE_UNKNOWN := 0, 0, 0, 0, 0, D3D11_SDK_VERSION := 7, d3d_device, 0, d3d_context)
IDXGIOutput1 := IDXGIOutput1_Query(IDXGIOutput)
IDXGIOutput1_DuplicateOutput(IDXGIOutput1, d3d_device, Duplication)
VarSetCapacity(DXGI_OUTDUPL_DESC, 36, 0)
IDXGIOutputDuplication_GetDesc(Duplication, &DXGI_OUTDUPL_DESC)
DesktopImageInSystemMemory := NumGet(DXGI_OUTDUPL_DESC, 32, "uint")
sleep 50   ; As I understand - need some sleep for successful connecting to IDXGIOutputDuplication interface

VarSetCapacity(D3D11_TEXTURE2D_DESC, 44, 0)
NumPut(width, D3D11_TEXTURE2D_DESC, 0, "uint")   ; Width
NumPut(height, D3D11_TEXTURE2D_DESC, 4, "uint")   ; Height
NumPut(1, D3D11_TEXTURE2D_DESC, 8, "uint")   ; MipLevels
NumPut(1, D3D11_TEXTURE2D_DESC, 12, "uint")   ; ArraySize
NumPut(DXGI_FORMAT_B8G8R8A8_UNORM := 87, D3D11_TEXTURE2D_DESC, 16, "uint")   ; Format
NumPut(1, D3D11_TEXTURE2D_DESC, 20, "uint")   ; SampleDescCount
NumPut(0, D3D11_TEXTURE2D_DESC, 24, "uint")   ; SampleDescQuality
NumPut(D3D11_USAGE_STAGING := 3, D3D11_TEXTURE2D_DESC, 28, "uint")   ; Usage
NumPut(0, D3D11_TEXTURE2D_DESC, 32, "uint")   ; BindFlags
NumPut(D3D11_CPU_ACCESS_READ := 0x20000, D3D11_TEXTURE2D_DESC, 36, "uint")   ; CPUAccessFlags
NumPut(0, D3D11_TEXTURE2D_DESC, 40, "uint")   ; MiscFlags
ID3D11Device_CreateTexture2D(d3d_device, &D3D11_TEXTURE2D_DESC, 0, staging_tex)

loop 1
{
   tooltip % A_Index
   VarSetCapacity(DXGI_OUTDUPL_FRAME_INFO, 48, 0)
   if (A_Index = 1)
   {
      loop
      {
         AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, -1, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
         LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
         if (LastPresentTime > 0)
            break
         ObjRelease(desktop_resource)
         IDXGIOutputDuplication_ReleaseFrame(duplication)
      }
   }
   else
   {
      AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, 0, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
      LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
   }
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
         {
            VarSetCapacity(DXGI_MAPPED_RECT, A_PtrSize*2, 0)
            IDXGIOutputDuplication_MapDesktopSurface(Duplication, &DXGI_MAPPED_RECT)
            pitch := NumGet(DXGI_MAPPED_RECT, 0, "int")
            pBits := NumGet(DXGI_MAPPED_RECT, A_PtrSize, "ptr")
         }
         else
         {
            tex := ID3D11Texture2D_Query(desktop_resource)
            ID3D11DeviceContext_CopyResource(d3d_context, staging_tex, tex)
            VarSetCapacity(D3D11_MAPPED_SUBRESOURCE, 8+A_PtrSize, 0)
            ID3D11DeviceContext_Map(d3d_context, staging_tex, 0, D3D11_MAP_READ := 1, 0, &D3D11_MAPPED_SUBRESOURCE)
            pBits := NumGet(D3D11_MAPPED_SUBRESOURCE, 0, "ptr")
            pitch := NumGet(D3D11_MAPPED_SUBRESOURCE, A_PtrSize, "uint")
         }
      }
   }
   DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", width, "int", height, "int", pitch, "int", 0xE200B, "ptr", pBits, "ptr*", pBitmap:=0)
   DllCall("gdiplus\GdipSaveImageToFile", "ptr", pBitmap, "wstr", A_Index ".png", "ptr", pCodec, "uint", 0)
   DllCall("gdiplus\GdipDisposeImage", "ptr", pBitmap)
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
            IDXGIOutputDuplication_UnMapDesktopSurface(Duplication)
         else
         {
            ID3D11DeviceContext_Unmap(d3d_context, staging_tex, 0)
            ObjRelease(tex)
         }
      }
      ObjRelease(desktop_resource)
      IDXGIOutputDuplication_ReleaseFrame(duplication)
   }
}
tooltip
Release(staging_tex)
Release(d3d_device)
Release(d3d_context)
Release(duplication)
Release(IDXGIAdapter)
Release(IDXGIOutput)
Release(IDXGIOutput1)
Release(IDXGIFactory)
msgbox % DesktopImageInSystemMemory
ExitApp




CreateDXGIFactory()
{
   if !DllCall("GetModuleHandle","str","DXGI")
      DllCall("LoadLibrary","str","DXGI")
   if !DllCall("GetModuleHandle","str","D3D11")
      DllCall("LoadLibrary","str","D3D11")
   GUID(riid, "{7b7166ec-21c7-44ae-b21a-c9ae321ae369}")
   hr := DllCall("DXGI\CreateDXGIFactory1", "ptr", &riid, "ptr*", ppFactory)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return ppFactory
}

IDXGIFactory_EnumAdapters(this, Adapter, ByRef ppAdapter)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Adapter, "ptr*", ppAdapter)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIAdapter_EnumOutputs(this, Output, ByRef ppOutput)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Output, "ptr*", ppOutput)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0002   ; DXGI_ERROR_NOT_FOUND
         return "DXGI_ERROR_NOT_FOUND"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIAdapter_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_GetDesc(this, pDesc)
{
   DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_AcquireNextFrame(this, TimeoutInMilliseconds, pFrameInfo, ByRef ppDesktopResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint", TimeoutInMilliseconds, "ptr", pFrameInfo, "ptr*", ppDesktopResource)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0027   ; DXGI_ERROR_WAIT_TIMEOUT
         return "DXGI_ERROR_WAIT_TIMEOUT"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

D3D11CreateDevice(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ByRef ppDevice, ByRef pFeatureLevel, ByRef ppImmediateContext)
{
   hr := DllCall("D3D11\D3D11CreateDevice", "ptr", pAdapter, "int", DriverType, "ptr", Software, "uint", Flags, "ptr", pFeatureLevels, "uint", FeatureLevels, "uint", SDKVersion, "ptr*", ppDevice, "ptr*", pFeatureLevel, "ptr*", ppImmediateContext)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11Device_CreateTexture2D(this, pDesc, pInitialData, ByRef ppTexture2D)
{
   hr := DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr", pDesc, "ptr", pInitialData, "ptr*", ppTexture2D)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_MapDesktopSurface(this, pLockedRect)
{
   hr := DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "ptr", pLockedRect)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0004   ; DXGI_ERROR_UNSUPPORTED
         return "DXGI_ERROR_UNSUPPORTED"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIOutputDuplication_UnMapDesktopSurface(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_ReleaseFrame(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_DuplicateOutput(this, pDevice, ByRef ppOutputDuplication)
{
   hr := DllCall(NumGet(NumGet(this+0)+22*A_PtrSize), "ptr", this, "ptr", pDevice, "ptr*", ppOutputDuplication)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_Query(IDXGIOutput)
{ 
   hr := ComObjQuery(IDXGIOutput, "{00cddea8-939b-4b83-a340-a685226666cc}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11Texture2D_Query(desktop_resource)
{ 
   hr := ComObjQuery(desktop_resource, "{6f15aaf2-d208-4e89-9ab4-489535d34f9c}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11DeviceContext_CopyResource(this, pDstResource, pSrcResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+47*A_PtrSize), "ptr", this, "ptr", pDstResource, "ptr", pSrcResource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_CopySubresourceRegion(this, pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox)
{
   hr := DllCall(NumGet(NumGet(this+0)+46*A_PtrSize), "ptr", this, "ptr", pDstResource, "uint", DstSubresource, "uint", DstX, "uint", DstY, "uint", DstZ, "ptr", pSrcResource, "uint", SrcSubresource, "ptr", pSrcBox)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Map(this, pResource, Subresource, MapType, MapFlags, pMappedResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource, "uint", MapType, "uint", MapFlags, "ptr", pMappedResource)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Unmap(this, pResource, Subresource)
{
   hr := DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

Release(this)
{
   DllCall(NumGet(NumGet(this+0)+2*A_PtrSize), "ptr", this)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

GUID(ByRef GUID, sGUID)
{
    VarSetCapacity(GUID, 16, 0)
    return DllCall("ole32\CLSIDFromString", "WStr", sGUID, "Ptr", &GUID) >= 0 ? &GUID : ""
}

_Error(val)
{
   msgbox % val
   ExitApp
}

burque505
Posts: 1732
Joined: 22 Jan 2017, 19:37

Re: ScreenBuffer - DirectX screen capture

Post by burque505 » 23 Aug 2021, 16:49

@malcev, I just tried it, Win7 64-bit, AHK 1.1.33.09

Code: Select all

---------------------------
IDXGIOutput1_DuplicateOutput error: -2005270524
ErrorLevel: 0
Works fine on Win10. Msgbox says "0"

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: ScreenBuffer - DirectX screen capture

Post by malcev » 23 Aug 2021, 16:53

This api (IDXGIOutput1::DuplicateOutput )does not work with win7.

burque505
Posts: 1732
Joined: 22 Jan 2017, 19:37

Re: ScreenBuffer - DirectX screen capture

Post by burque505 » 23 Aug 2021, 16:55

Thanks!

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: ScreenBuffer - DirectX screen capture

Post by malcev » 23 Aug 2021, 17:29

iseahound, I found where is error in Your code
This line is not right

Code: Select all

DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", sw, "int", sh, "uint", 4 * sw, "uint", 0xE200B, "ptr", this.ptr, "ptr*", &pBitmap:=0)
Stride (pitch) is not always equal width*4.
https://docs.microsoft.com/en-us/windows/win32/medfound/image-stride

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by iseahound » 23 Aug 2021, 19:06

@malcev Thanks, I think dividing the buffer size by the height should fix it.

Code: Select all

DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", sw, "int", sh, "uint", this.size / sh, "uint", 0xE200B, "ptr", this.ptr, "ptr*", &pBitmap:=0)
The main post has been updated.
  • Benchmark.ahk now tests saving to raw bitmaps and JPEGs.
  • ScreenBuffer.ahk has a new function .SaveRaw() which is faster than Save() but makes bigger files.

User avatar
thqby
Posts: 406
Joined: 16 Apr 2021, 11:18
Contact:

Re: ScreenBuffer - DirectX screen capture

Post by thqby » 27 Aug 2021, 01:43

malcev wrote:
23 Aug 2021, 16:17
thqby, try this code on ahk v1 and tell does it work ok and what You see in message box.
For me it works with Your resolution.
It should save screenshot as "1.png" in working directory.

Code: Select all

setbatchlines -1
; Load GDI+
DllCall("LoadLibrary", "str", "gdiplus")
VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0) ; sizeof(GdiplusStartupInput) = 16, 24
NumPut(0x1, si, "uint")
DllCall("gdiplus\GdiplusStartup", "ptr*", pToken:=0, "ptr", &si, "ptr", 0)
extension := "png"
DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", count:=0, "uint*", size:=0)
VarSetCapacity(ci, size)
DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", &ci)
if !(count && size)
   throw Exception("Could not get a list of image codec encoders on this system.")
Loop % count
   EncoderExtensions := StrGet(NumGet(ci, (idx:=(48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "uptr"), "UTF-16")
      until InStr(EncoderExtensions, "*." extension)
if !(pCodec := &ci + idx)
   throw Exception("Could not find a matching encoder for the specified file format.")

IDXGIFactory := CreateDXGIFactory()
if !IDXGIFactory
{
   MsgBox, 16, Error, Create IDXGIFactory failed.
   ExitApp
}
loop
{
   IDXGIFactory_EnumAdapters(IDXGIFactory, A_Index-1, IDXGIAdapter)
   loop
   {
      hr := IDXGIAdapter_EnumOutputs(IDXGIAdapter, A_Index-1, IDXGIOutput)
      if (hr = "DXGI_ERROR_NOT_FOUND")
         break
      VarSetCapacity(DXGI_OUTPUT_DESC, 88+A_PtrSize, 0)
      IDXGIOutput_GetDesc(IDXGIOutput, &DXGI_OUTPUT_DESC)
      Width := NumGet(DXGI_OUTPUT_DESC, 72, "int")
      Height := NumGet(DXGI_OUTPUT_DESC, 76, "int")
      AttachedToDesktop := NumGet(DXGI_OUTPUT_DESC, 80, "int")
      if (AttachedToDesktop = 1)
         break 2         
   }
}
if (AttachedToDesktop != 1)
{
   MsgBox, 16, Error, No adapter attached to desktop
   ExitApp
}
D3D11CreateDevice(IDXGIAdapter, D3D_DRIVER_TYPE_UNKNOWN := 0, 0, 0, 0, 0, D3D11_SDK_VERSION := 7, d3d_device, 0, d3d_context)
IDXGIOutput1 := IDXGIOutput1_Query(IDXGIOutput)
IDXGIOutput1_DuplicateOutput(IDXGIOutput1, d3d_device, Duplication)
VarSetCapacity(DXGI_OUTDUPL_DESC, 36, 0)
IDXGIOutputDuplication_GetDesc(Duplication, &DXGI_OUTDUPL_DESC)
DesktopImageInSystemMemory := NumGet(DXGI_OUTDUPL_DESC, 32, "uint")
sleep 50   ; As I understand - need some sleep for successful connecting to IDXGIOutputDuplication interface

VarSetCapacity(D3D11_TEXTURE2D_DESC, 44, 0)
NumPut(width, D3D11_TEXTURE2D_DESC, 0, "uint")   ; Width
NumPut(height, D3D11_TEXTURE2D_DESC, 4, "uint")   ; Height
NumPut(1, D3D11_TEXTURE2D_DESC, 8, "uint")   ; MipLevels
NumPut(1, D3D11_TEXTURE2D_DESC, 12, "uint")   ; ArraySize
NumPut(DXGI_FORMAT_B8G8R8A8_UNORM := 87, D3D11_TEXTURE2D_DESC, 16, "uint")   ; Format
NumPut(1, D3D11_TEXTURE2D_DESC, 20, "uint")   ; SampleDescCount
NumPut(0, D3D11_TEXTURE2D_DESC, 24, "uint")   ; SampleDescQuality
NumPut(D3D11_USAGE_STAGING := 3, D3D11_TEXTURE2D_DESC, 28, "uint")   ; Usage
NumPut(0, D3D11_TEXTURE2D_DESC, 32, "uint")   ; BindFlags
NumPut(D3D11_CPU_ACCESS_READ := 0x20000, D3D11_TEXTURE2D_DESC, 36, "uint")   ; CPUAccessFlags
NumPut(0, D3D11_TEXTURE2D_DESC, 40, "uint")   ; MiscFlags
ID3D11Device_CreateTexture2D(d3d_device, &D3D11_TEXTURE2D_DESC, 0, staging_tex)

loop 1
{
   tooltip % A_Index
   VarSetCapacity(DXGI_OUTDUPL_FRAME_INFO, 48, 0)
   if (A_Index = 1)
   {
      loop
      {
         AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, -1, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
         LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
         if (LastPresentTime > 0)
            break
         ObjRelease(desktop_resource)
         IDXGIOutputDuplication_ReleaseFrame(duplication)
      }
   }
   else
   {
      AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, 0, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
      LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
   }
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
         {
            VarSetCapacity(DXGI_MAPPED_RECT, A_PtrSize*2, 0)
            IDXGIOutputDuplication_MapDesktopSurface(Duplication, &DXGI_MAPPED_RECT)
            pitch := NumGet(DXGI_MAPPED_RECT, 0, "int")
            pBits := NumGet(DXGI_MAPPED_RECT, A_PtrSize, "ptr")
         }
         else
         {
            tex := ID3D11Texture2D_Query(desktop_resource)
            ID3D11DeviceContext_CopyResource(d3d_context, staging_tex, tex)
            VarSetCapacity(D3D11_MAPPED_SUBRESOURCE, 8+A_PtrSize, 0)
            ID3D11DeviceContext_Map(d3d_context, staging_tex, 0, D3D11_MAP_READ := 1, 0, &D3D11_MAPPED_SUBRESOURCE)
            pBits := NumGet(D3D11_MAPPED_SUBRESOURCE, 0, "ptr")
            pitch := NumGet(D3D11_MAPPED_SUBRESOURCE, A_PtrSize, "uint")
         }
      }
   }
   DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", width, "int", height, "int", pitch, "int", 0xE200B, "ptr", pBits, "ptr*", pBitmap:=0)
   DllCall("gdiplus\GdipSaveImageToFile", "ptr", pBitmap, "wstr", A_Index ".png", "ptr", pCodec, "uint", 0)
   DllCall("gdiplus\GdipDisposeImage", "ptr", pBitmap)
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
            IDXGIOutputDuplication_UnMapDesktopSurface(Duplication)
         else
         {
            ID3D11DeviceContext_Unmap(d3d_context, staging_tex, 0)
            ObjRelease(tex)
         }
      }
      ObjRelease(desktop_resource)
      IDXGIOutputDuplication_ReleaseFrame(duplication)
   }
}
tooltip
Release(staging_tex)
Release(d3d_device)
Release(d3d_context)
Release(duplication)
Release(IDXGIAdapter)
Release(IDXGIOutput)
Release(IDXGIOutput1)
Release(IDXGIFactory)
msgbox % DesktopImageInSystemMemory
ExitApp




CreateDXGIFactory()
{
   if !DllCall("GetModuleHandle","str","DXGI")
      DllCall("LoadLibrary","str","DXGI")
   if !DllCall("GetModuleHandle","str","D3D11")
      DllCall("LoadLibrary","str","D3D11")
   GUID(riid, "{7b7166ec-21c7-44ae-b21a-c9ae321ae369}")
   hr := DllCall("DXGI\CreateDXGIFactory1", "ptr", &riid, "ptr*", ppFactory)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return ppFactory
}

IDXGIFactory_EnumAdapters(this, Adapter, ByRef ppAdapter)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Adapter, "ptr*", ppAdapter)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIAdapter_EnumOutputs(this, Output, ByRef ppOutput)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Output, "ptr*", ppOutput)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0002   ; DXGI_ERROR_NOT_FOUND
         return "DXGI_ERROR_NOT_FOUND"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIAdapter_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_GetDesc(this, pDesc)
{
   DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_AcquireNextFrame(this, TimeoutInMilliseconds, pFrameInfo, ByRef ppDesktopResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint", TimeoutInMilliseconds, "ptr", pFrameInfo, "ptr*", ppDesktopResource)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0027   ; DXGI_ERROR_WAIT_TIMEOUT
         return "DXGI_ERROR_WAIT_TIMEOUT"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

D3D11CreateDevice(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ByRef ppDevice, ByRef pFeatureLevel, ByRef ppImmediateContext)
{
   hr := DllCall("D3D11\D3D11CreateDevice", "ptr", pAdapter, "int", DriverType, "ptr", Software, "uint", Flags, "ptr", pFeatureLevels, "uint", FeatureLevels, "uint", SDKVersion, "ptr*", ppDevice, "ptr*", pFeatureLevel, "ptr*", ppImmediateContext)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11Device_CreateTexture2D(this, pDesc, pInitialData, ByRef ppTexture2D)
{
   hr := DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr", pDesc, "ptr", pInitialData, "ptr*", ppTexture2D)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_MapDesktopSurface(this, pLockedRect)
{
   hr := DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "ptr", pLockedRect)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0004   ; DXGI_ERROR_UNSUPPORTED
         return "DXGI_ERROR_UNSUPPORTED"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIOutputDuplication_UnMapDesktopSurface(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_ReleaseFrame(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_DuplicateOutput(this, pDevice, ByRef ppOutputDuplication)
{
   hr := DllCall(NumGet(NumGet(this+0)+22*A_PtrSize), "ptr", this, "ptr", pDevice, "ptr*", ppOutputDuplication)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_Query(IDXGIOutput)
{ 
   hr := ComObjQuery(IDXGIOutput, "{00cddea8-939b-4b83-a340-a685226666cc}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11Texture2D_Query(desktop_resource)
{ 
   hr := ComObjQuery(desktop_resource, "{6f15aaf2-d208-4e89-9ab4-489535d34f9c}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11DeviceContext_CopyResource(this, pDstResource, pSrcResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+47*A_PtrSize), "ptr", this, "ptr", pDstResource, "ptr", pSrcResource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_CopySubresourceRegion(this, pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox)
{
   hr := DllCall(NumGet(NumGet(this+0)+46*A_PtrSize), "ptr", this, "ptr", pDstResource, "uint", DstSubresource, "uint", DstX, "uint", DstY, "uint", DstZ, "ptr", pSrcResource, "uint", SrcSubresource, "ptr", pSrcBox)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Map(this, pResource, Subresource, MapType, MapFlags, pMappedResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource, "uint", MapType, "uint", MapFlags, "ptr", pMappedResource)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Unmap(this, pResource, Subresource)
{
   hr := DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

Release(this)
{
   DllCall(NumGet(NumGet(this+0)+2*A_PtrSize), "ptr", this)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

GUID(ByRef GUID, sGUID)
{
    VarSetCapacity(GUID, 16, 0)
    return DllCall("ole32\CLSIDFromString", "WStr", sGUID, "Ptr", &GUID) >= 0 ? &GUID : ""
}

_Error(val)
{
   msgbox % val
   ExitApp
}
this works fine.

Turisas
Posts: 6
Joined: 04 Jul 2020, 15:21

Re: ScreenBuffer - DirectX screen capture

Post by Turisas » 16 May 2022, 03:33

@malcev

Thanks for the nice code, but is there any solution to resize the texture/image before saving it?

I know how it works with "Gdip_DrawImage" or "StretchBlt" but I would appreciate a D3D11 solution.

Code: Select all

setbatchlines -1
; Load GDI+
DllCall("LoadLibrary", "str", "gdiplus")
VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0) ; sizeof(GdiplusStartupInput) = 16, 24
NumPut(0x1, si, "uint")
DllCall("gdiplus\GdiplusStartup", "ptr*", pToken:=0, "ptr", &si, "ptr", 0)
extension := "png"
DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", count:=0, "uint*", size:=0)
VarSetCapacity(ci, size)
DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", &ci)
if !(count && size)
   throw Exception("Could not get a list of image codec encoders on this system.")
Loop % count
   EncoderExtensions := StrGet(NumGet(ci, (idx:=(48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize, "uptr"), "UTF-16")
      until InStr(EncoderExtensions, "*." extension)
if !(pCodec := &ci + idx)
   throw Exception("Could not find a matching encoder for the specified file format.")

IDXGIFactory := CreateDXGIFactory()
if !IDXGIFactory
{
   MsgBox, 16, Error, Create IDXGIFactory failed.
   ExitApp
}
loop
{
   IDXGIFactory_EnumAdapters(IDXGIFactory, A_Index-1, IDXGIAdapter)
   loop
   {
      hr := IDXGIAdapter_EnumOutputs(IDXGIAdapter, A_Index-1, IDXGIOutput)
      if (hr = "DXGI_ERROR_NOT_FOUND")
         break
      VarSetCapacity(DXGI_OUTPUT_DESC, 88+A_PtrSize, 0)
      IDXGIOutput_GetDesc(IDXGIOutput, &DXGI_OUTPUT_DESC)
      Width := NumGet(DXGI_OUTPUT_DESC, 72, "int")
      Height := NumGet(DXGI_OUTPUT_DESC, 76, "int")
      AttachedToDesktop := NumGet(DXGI_OUTPUT_DESC, 80, "int")
      if (AttachedToDesktop = 1)
         break 2         
   }
}
if (AttachedToDesktop != 1)
{
   MsgBox, 16, Error, No adapter attached to desktop
   ExitApp
}
D3D11CreateDevice(IDXGIAdapter, D3D_DRIVER_TYPE_UNKNOWN := 0, 0, 0, 0, 0, D3D11_SDK_VERSION := 7, d3d_device, 0, d3d_context)
IDXGIOutput1 := IDXGIOutput1_Query(IDXGIOutput)
IDXGIOutput1_DuplicateOutput(IDXGIOutput1, d3d_device, Duplication)
VarSetCapacity(DXGI_OUTDUPL_DESC, 36, 0)
IDXGIOutputDuplication_GetDesc(Duplication, &DXGI_OUTDUPL_DESC)
DesktopImageInSystemMemory := NumGet(DXGI_OUTDUPL_DESC, 32, "uint")
sleep 50   ; As I understand - need some sleep for successful connecting to IDXGIOutputDuplication interface

VarSetCapacity(D3D11_TEXTURE2D_DESC, 44, 0)
NumPut(width, D3D11_TEXTURE2D_DESC, 0, "uint")   ; Width
NumPut(height, D3D11_TEXTURE2D_DESC, 4, "uint")   ; Height
NumPut(1, D3D11_TEXTURE2D_DESC, 8, "uint")   ; MipLevels
NumPut(1, D3D11_TEXTURE2D_DESC, 12, "uint")   ; ArraySize
NumPut(DXGI_FORMAT_B8G8R8A8_UNORM := 87, D3D11_TEXTURE2D_DESC, 16, "uint")   ; Format
NumPut(1, D3D11_TEXTURE2D_DESC, 20, "uint")   ; SampleDescCount
NumPut(0, D3D11_TEXTURE2D_DESC, 24, "uint")   ; SampleDescQuality
NumPut(D3D11_USAGE_STAGING := 3, D3D11_TEXTURE2D_DESC, 28, "uint")   ; Usage
NumPut(0, D3D11_TEXTURE2D_DESC, 32, "uint")   ; BindFlags
NumPut(D3D11_CPU_ACCESS_READ := 0x20000, D3D11_TEXTURE2D_DESC, 36, "uint")   ; CPUAccessFlags
NumPut(0, D3D11_TEXTURE2D_DESC, 40, "uint")   ; MiscFlags
ID3D11Device_CreateTexture2D(d3d_device, &D3D11_TEXTURE2D_DESC, 0, staging_tex)

loop 1
{
   tooltip % A_Index
   VarSetCapacity(DXGI_OUTDUPL_FRAME_INFO, 48, 0)
   if (A_Index = 1)
   {
      loop
      {
         AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, -1, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
         LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
         if (LastPresentTime > 0)
            break
         ObjRelease(desktop_resource)
         IDXGIOutputDuplication_ReleaseFrame(duplication)
      }
   }
   else
   {
      AcquireNextFrame := IDXGIOutputDuplication_AcquireNextFrame(Duplication, 0, &DXGI_OUTDUPL_FRAME_INFO, desktop_resource)
      LastPresentTime := NumGet(DXGI_OUTDUPL_FRAME_INFO, 0, "int64")
   }
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
         {
            VarSetCapacity(DXGI_MAPPED_RECT, A_PtrSize*2, 0)
            IDXGIOutputDuplication_MapDesktopSurface(Duplication, &DXGI_MAPPED_RECT)
            pitch := NumGet(DXGI_MAPPED_RECT, 0, "int")
            pBits := NumGet(DXGI_MAPPED_RECT, A_PtrSize, "ptr")
         }
         else
         {
            tex := ID3D11Texture2D_Query(desktop_resource)
            ID3D11DeviceContext_CopyResource(d3d_context, staging_tex, tex)
            VarSetCapacity(D3D11_MAPPED_SUBRESOURCE, 8+A_PtrSize, 0)
            ID3D11DeviceContext_Map(d3d_context, staging_tex, 0, D3D11_MAP_READ := 1, 0, &D3D11_MAPPED_SUBRESOURCE)
            pBits := NumGet(D3D11_MAPPED_SUBRESOURCE, 0, "ptr")
            pitch := NumGet(D3D11_MAPPED_SUBRESOURCE, A_PtrSize, "uint")
         }
      }
   }
   DllCall("gdiplus\GdipCreateBitmapFromScan0", "int", width, "int", height, "int", pitch, "int", 0xE200B, "ptr", pBits, "ptr*", pBitmap:=0)
   DllCall("gdiplus\GdipSaveImageToFile", "ptr", pBitmap, "wstr", A_Index ".png", "ptr", pCodec, "uint", 0)
   DllCall("gdiplus\GdipDisposeImage", "ptr", pBitmap)
   if (AcquireNextFrame != "DXGI_ERROR_WAIT_TIMEOUT")
   {
      if (LastPresentTime > 0)
      {
         if (DesktopImageInSystemMemory = 1)
            IDXGIOutputDuplication_UnMapDesktopSurface(Duplication)
         else
         {
            ID3D11DeviceContext_Unmap(d3d_context, staging_tex, 0)
            ObjRelease(tex)
         }
      }
      ObjRelease(desktop_resource)
      IDXGIOutputDuplication_ReleaseFrame(duplication)
   }
}
tooltip
Release(staging_tex)
Release(d3d_device)
Release(d3d_context)
Release(duplication)
Release(IDXGIAdapter)
Release(IDXGIOutput)
Release(IDXGIOutput1)
Release(IDXGIFactory)
msgbox % DesktopImageInSystemMemory
ExitApp




CreateDXGIFactory()
{
   if !DllCall("GetModuleHandle","str","DXGI")
      DllCall("LoadLibrary","str","DXGI")
   if !DllCall("GetModuleHandle","str","D3D11")
      DllCall("LoadLibrary","str","D3D11")
   GUID(riid, "{7b7166ec-21c7-44ae-b21a-c9ae321ae369}")
   hr := DllCall("DXGI\CreateDXGIFactory1", "ptr", &riid, "ptr*", ppFactory)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return ppFactory
}

IDXGIFactory_EnumAdapters(this, Adapter, ByRef ppAdapter)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Adapter, "ptr*", ppAdapter)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIAdapter_EnumOutputs(this, Output, ByRef ppOutput)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Output, "ptr*", ppOutput)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0002   ; DXGI_ERROR_NOT_FOUND
         return "DXGI_ERROR_NOT_FOUND"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIAdapter_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput_GetDesc(this, pDesc)
{
   hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_GetDesc(this, pDesc)
{
   DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", pDesc)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_AcquireNextFrame(this, TimeoutInMilliseconds, pFrameInfo, ByRef ppDesktopResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint", TimeoutInMilliseconds, "ptr", pFrameInfo, "ptr*", ppDesktopResource)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0027   ; DXGI_ERROR_WAIT_TIMEOUT
         return "DXGI_ERROR_WAIT_TIMEOUT"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

D3D11CreateDevice(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ByRef ppDevice, ByRef pFeatureLevel, ByRef ppImmediateContext)
{
   hr := DllCall("D3D11\D3D11CreateDevice", "ptr", pAdapter, "int", DriverType, "ptr", Software, "uint", Flags, "ptr", pFeatureLevels, "uint", FeatureLevels, "uint", SDKVersion, "ptr*", ppDevice, "ptr*", pFeatureLevel, "ptr*", ppImmediateContext)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11Device_CreateTexture2D(this, pDesc, pInitialData, ByRef ppTexture2D)
{
   hr := DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr", pDesc, "ptr", pInitialData, "ptr*", ppTexture2D)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_MapDesktopSurface(this, pLockedRect)
{
   hr := DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "ptr", pLockedRect)
   if hr or ErrorLevel
   {
      if (hr&=0xFFFFFFFF) = 0x887A0004   ; DXGI_ERROR_UNSUPPORTED
         return "DXGI_ERROR_UNSUPPORTED"
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   }
}

IDXGIOutputDuplication_UnMapDesktopSurface(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutputDuplication_ReleaseFrame(this)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_DuplicateOutput(this, pDevice, ByRef ppOutputDuplication)
{
   hr := DllCall(NumGet(NumGet(this+0)+22*A_PtrSize), "ptr", this, "ptr", pDevice, "ptr*", ppOutputDuplication)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

IDXGIOutput1_Query(IDXGIOutput)
{ 
   hr := ComObjQuery(IDXGIOutput, "{00cddea8-939b-4b83-a340-a685226666cc}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11Texture2D_Query(desktop_resource)
{ 
   hr := ComObjQuery(desktop_resource, "{6f15aaf2-d208-4e89-9ab4-489535d34f9c}")
   if !hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
   return hr
}

ID3D11DeviceContext_CopyResource(this, pDstResource, pSrcResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+47*A_PtrSize), "ptr", this, "ptr", pDstResource, "ptr", pSrcResource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_CopySubresourceRegion(this, pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox)
{
   hr := DllCall(NumGet(NumGet(this+0)+46*A_PtrSize), "ptr", this, "ptr", pDstResource, "uint", DstSubresource, "uint", DstX, "uint", DstY, "uint", DstZ, "ptr", pSrcResource, "uint", SrcSubresource, "ptr", pSrcBox)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Map(this, pResource, Subresource, MapType, MapFlags, pMappedResource)
{
   hr := DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource, "uint", MapType, "uint", MapFlags, "ptr", pMappedResource)
   if hr or ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

ID3D11DeviceContext_Unmap(this, pResource, Subresource)
{
   hr := DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this, "ptr", pResource, "uint", Subresource)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

Release(this)
{
   DllCall(NumGet(NumGet(this+0)+2*A_PtrSize), "ptr", this)
   if ErrorLevel
      _Error(A_ThisFunc " error: " hr "`nErrorLevel: " ErrorLevel)
}

GUID(ByRef GUID, sGUID)
{
    VarSetCapacity(GUID, 16, 0)
    return DllCall("ole32\CLSIDFromString", "WStr", sGUID, "Ptr", &GUID) >= 0 ? &GUID : ""
}

_Error(val)
{
   msgbox % val
   ExitApp
}

Post Reply

Return to “Scripts and Functions (v2)”