2016-06-29 25 views
10

Prima che qualcuno lo menzioni, ho fatto riferimento al collegamento this per scoprire come ho avuto bisogno di copiare il backbuffer in una bitmap.Cattura screenshot del programma DX11 a schermo intero utilizzando SharpDX e EasyHook

situazione attuale

  • sto iniettato al processo di destinazione
  • processo target FeatureLevel = Level_11_0
  • bersaglio SwapChain è stato fatto con bandiera DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.
  • SwapChain :: La funzione presente è agganciata.
  • Lo schermo risulta nero e il processo di destinazione si arresta in modo anomalo. senza processo screenshot funziona bene.

situazione desiderata

Fai lo screenshot correttamente e lasciare che il processo di destinazione continua con la sua normale esecuzione.

Codice

classe NOTA Hook è lo stesso che nel collegamento. Ho solo aggiunto una versione di UnmodifiableHook che fa quello che dice il suo nome. Ho omesso tutti i bit non importanti.

TestSwapChainHook.cs

using System; 
using System.Runtime.InteropServices; 

namespace Test 
{ 
    public sealed class TestSwapChainHook : IDisposable 
    { 
     private enum IDXGISwapChainVirtualTable 
     { 
      QueryInterface = 0, 
      AddRef = 1, 
      Release = 2, 
      SetPrivateData = 3, 
      SetPrivateDataInterface = 4, 
      GetPrivateData = 5, 
      GetParent = 6, 
      GetDevice = 7, 
      Present = 8, 
      GetBuffer = 9, 
      SetFullscreenState = 10, 
      GetFullscreenState = 11, 
      GetDesc = 12, 
      ResizeBuffers = 13, 
      ResizeTarget = 14, 
      GetContainingOutput = 15, 
      GetFrameStatistics = 16, 
      GetLastPresentCount = 17, 
     } 

     public static readonly int VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT = 18; 

     private static IntPtr[] SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES; 

     [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 
     public delegate int DXGISwapChainPresentDelegate(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags); 

     public delegate int DXGISwapChainPresentHookDelegate(UnmodifiableHook<DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags); 

     private DXGISwapChainPresentHookDelegate _present; 
     private Hook<DXGISwapChainPresentDelegate> presentHook; 

     static TestSwapChainHook() 
     { 
      SharpDX.DXGI.Rational rational = new SharpDX.DXGI.Rational(60, 1); 
      SharpDX.DXGI.ModeDescription modeDescription = new SharpDX.DXGI.ModeDescription(100, 100, rational, SharpDX.DXGI.Format.R8G8B8A8_UNorm); 
      SharpDX.DXGI.SampleDescription sampleDescription = new SharpDX.DXGI.SampleDescription(1, 0); 

      using (SharpDX.Windows.RenderForm renderForm = new SharpDX.Windows.RenderForm()) 
      { 
       SharpDX.DXGI.SwapChainDescription swapChainDescription = new SharpDX.DXGI.SwapChainDescription(); 
       swapChainDescription.BufferCount = 1; 
       swapChainDescription.Flags = SharpDX.DXGI.SwapChainFlags.None; 
       swapChainDescription.IsWindowed = true; 
       swapChainDescription.ModeDescription = modeDescription; 
       swapChainDescription.OutputHandle = renderForm.Handle; 
       swapChainDescription.SampleDescription = sampleDescription; 
       swapChainDescription.SwapEffect = SharpDX.DXGI.SwapEffect.Discard; 
       swapChainDescription.Usage = SharpDX.DXGI.Usage.RenderTargetOutput; 

       SharpDX.Direct3D11.Device device = null; 
       SharpDX.DXGI.SwapChain swapChain = null; 
       SharpDX.Direct3D11.Device.CreateWithSwapChain(SharpDX.Direct3D.DriverType.Hardware, SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, swapChainDescription, out device, out swapChain); 
       try 
       { 
        IntPtr swapChainVirtualTable = Marshal.ReadIntPtr(swapChain.NativePointer); 

        SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES = new IntPtr[VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT]; 
        for (int x = 0; x < VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT; x++) 
        { 
         SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[x] = Marshal.ReadIntPtr(swapChainVirtualTable, x * IntPtr.Size); 
        } 

        device.Dispose(); 
        swapChain.Dispose(); 
       } 
       catch (Exception) 
       { 
        if (device != null) 
        { 
         device.Dispose(); 
        } 

        if (swapChain != null) 
        { 
         swapChain.Dispose(); 
        } 

        throw; 
       } 
      } 
     } 

     public TestSwapChainHook() 
     { 
      this._present = null; 

      this.presentHook = new Hook<DXGISwapChainPresentDelegate>(
         SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[(int)IDXGISwapChainVirtualTable.Present], 
         new DXGISwapChainPresentDelegate(hookPresent), 
         this); 
     } 

     public void activate() 
     { 
      this.presentHook.activate(); 
     } 

     public void deactivate() 
     { 
      this.presentHook.deactivate(); 
     } 

     private int hookPresent(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags) 
     { 
      lock (this.presentHook) 
      { 
       if (this._present == null) 
       { 
        return this.presentHook.original(thisPtr, syncInterval, flags); 
       } 
       else 
       { 
        return this._present(new UnmodifiableHook<DXGISwapChainPresentDelegate>(this.presentHook), thisPtr, syncInterval, flags); 
       } 
      } 
     } 

     public DXGISwapChainPresentHookDelegate present 
     { 
      get 
      { 
       lock (this.presentHook) 
       { 
        return this._present; 
       } 
      } 
      set 
      { 
       lock (this.presentHook) 
       { 
        this._present = value; 
       } 
      } 
     } 
    } 
} 

Usando il codice

inizializzazione

private TestSwapChain swapChainHook; 
private bool capture = false; 
private object captureLock = new object(); 

this.swapChainHook = new TestSwapChainHook(); 
this.swapChainHook.present = presentHook; 
this.swapChainHook.activate(); 

EDIT

Ho utilizzato un metodo diverso per catturare uno screenshot descritto nel collegamento this. Tuttavia il mio screenshot risulta in questo modo:

Rainbow image

Ora, questo sembra essere un problema con le mie impostazioni di conversione o qualsiasi altra cosa, ma io sono in grado di scoprire che cosa esattamente devo fare per risolvere il problema. So che la superficie che sto convertendo in una bitmap utilizza il formato DXGI_FORMAT_R10G10B10A2_UNORM (32 bit, 10 bit per colore e 2 per alpha, penso?). Ma non sono sicuro di come funzioni anche nei loop for (saltando byte e roba). Ho semplicemente copiato incollato.

nuova funzione hook

private int presentHook(UnmodifiableHook<IDXGISwapChainHook.DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags) 
{ 
    try 
    { 
     lock (this.captureLock) 
     { 
      if (this.capture) 
      { 
       SharpDX.DXGI.SwapChain swapChain = (SharpDX.DXGI.SwapChain)thisPtr; 

       using (SharpDX.Direct3D11.Texture2D backBuffer = swapChain.GetBackBuffer<SharpDX.Direct3D11.Texture2D>(0)) 
       { 
        SharpDX.Direct3D11.Texture2DDescription texture2DDescription = backBuffer.Description; 
        texture2DDescription.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.Read; 
        texture2DDescription.Usage = SharpDX.Direct3D11.ResourceUsage.Staging; 
        texture2DDescription.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None; 
        texture2DDescription.BindFlags = SharpDX.Direct3D11.BindFlags.None; 

        using (SharpDX.Direct3D11.Texture2D texture = new SharpDX.Direct3D11.Texture2D(backBuffer.Device, texture2DDescription)) 
        { 
         //DXGI_FORMAT_R10G10B10A2_UNORM 
         backBuffer.Device.ImmediateContext.CopyResource(backBuffer, texture); 

         using (SharpDX.DXGI.Surface surface = texture.QueryInterface<SharpDX.DXGI.Surface>()) 
         { 
          SharpDX.DataStream dataStream; 
          SharpDX.DataRectangle map = surface.Map(SharpDX.DXGI.MapFlags.Read, out dataStream); 
          try 
          { 
           byte[] pixelData = new byte[surface.Description.Width * surface.Description.Height * 4]; 
           int lines = (int)(dataStream.Length/map.Pitch); 
           int dataCounter = 0; 
           int actualWidth = surface.Description.Width * 4; 

           for (int y = 0; y < lines; y++) 
           { 
            for (int x = 0; x < map.Pitch; x++) 
            { 
             if (x < actualWidth) 
             { 
              pixelData[dataCounter++] = dataStream.Read<byte>(); 
             } 
             else 
             { 
              dataStream.Read<byte>(); 
             } 
            } 
           } 

           GCHandle handle = GCHandle.Alloc(pixelData, GCHandleType.Pinned); 
           try 
           { 
            using (Bitmap bitmap = new Bitmap(surface.Description.Width, surface.Description.Height, map.Pitch, PixelFormat.Format32bppArgb, handle.AddrOfPinnedObject())) 
            { 
             bitmap.Save(@"C:\Users\SOMEUSERNAME\Desktop\test.bmp"); 
            } 
           } 
           finally 
           { 
            if (handle.IsAllocated) 
            { 
             handle.Free(); 
            } 
           } 
          } 
          finally 
          { 
           surface.Unmap(); 

           dataStream.Dispose(); 
          } 
         } 
        } 
       } 

       this.capture = false; 
      } 
     } 
    } 
    catch(Exception ex) 
    { 
     MessageBox.Show(ex.ToString()); 
    } 

    return hook.original(thisPtr, syncInterval, flags); 
} 

risposta

Risulta formato DXGI_FORMAT_R10G10B10A2_UNORM è in questo formato bit:

A=alpha 
B=blue 
G=green 
R=red 

AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR 

E Format32bppArgb è in questo ordine di byte:

BGRA 

Così il codice di ciclo finale sarebbe:

while (pixelIndex < pixelData.Length) 
{ 
    uint currentPixel = dataStream.Read<uint>(); 

    uint r = (currentPixel & 0x3FF); 
    uint g = (currentPixel & 0xFFC00) >> 10; 
    uint b = (currentPixel & 0x3FF00000) >> 20; 
    uint a = (currentPixel & 0xC0000000) >> 30; 

    pixelData[pixelIndex++] = (byte)(b >> 2); 
    pixelData[pixelIndex++] = (byte)(g >> 2); 
    pixelData[pixelIndex++] = (byte)(r >> 2); 
    pixelData[pixelIndex++] = (byte)(a << 6); 

    while ((pixelIndex % map.Pitch) >= actualWidth) 
    { 
     dataStream.Read<byte>(); 
     pixelIndex++; 
    } 
} 

risposta

3

Questo screenshot fa apparire come R10G10B10A2 sta ottenendo infilati in R8G8B8A8. Non ho ancora testato il codice, ma dovremmo avere questo layout po

xxxxxxxx yyyyyyyy zzzzzzzz wwwwwwww 
RRRRRRRR RRGGGGGG GGGGBBBB BBBBBBAA 

ed è possibile estrarre loro come segue

byte x = data[ptr++]; 
byte y = data[ptr++]; 
byte z = data[ptr++]; 
byte w = data[ptr++]; 

int r = x << 2 | y >> 6; 
int g = (y & 0x3F) << 4 | z >> 4; 
int b = (z & 0xF) << 6 | w >> 2; 
int a = w & 0x3; 

dove r, g, b ora hanno una risoluzione di 10 bit. Se vuoi ridimensionarli in byte, puoi farlo con (byte) (r >> 2).

Aggiornamento

Questo sarebbe sostituire il doppio ciclo for. Non ho modo di testarlo, quindi non voglio spingerlo oltre, ma credo che l'idea sia corretta. L'ultimo controllo dovrebbe saltare i byte di riempimento in ogni riga.

while(dataCounter < pixelData.Length) 
{ 

    byte x = dataStream.Read<byte>(); 
    byte y = dataStream.Read<byte>(); 
    byte z = dataStream.Read<byte>(); 
    byte w = dataStream.Read<byte>(); 

    int r = x << 2 | y >> 6; 
    int g = (y & 0x3F) << 4 | z >> 4; 
    int b = (z & 0xF) << 6 | w >> 2; 
    int a = w & 0x3; 

    pixelData[dataCounter++] = (byte)(r >> 2); 
    pixelData[dataCounter++] = (byte)(g >> 2); 
    pixelData[dataCounter++] = (byte)(b >> 2); 
    pixelData[dataCounter++] = (byte)(a << 6); 

    while((dataCounter % map.Pitch) >= actualWidth) 
    { 
     dataStream.Read<byte>(); 
     dataCounter++; 
    } 

} 
+0

wow grazie finalmente qualcuno è in grado di aiutarmi. quindi come potrei andare in giro e passare attraverso questo? solo un ciclo for dove x Neijwiert

+0

non proprio, le nostre x sono diverse. Dovresti eseguire il loop su ptr: 'for (int ptr = 0; ptr

+0

Purtroppo non posso testare oggi. potresti adattare il mio codice per assicurarmi che abbia capito bene? – Neijwiert