Захват снимка экрана полноэкранного DX11-программы с помощью SharpDX и EasyHook

Прежде чем кто-нибудь это упоминает, я ссылаюсь на эту ссылку, чтобы узнать, как мне нужно скопировать backbuffer в растровое изображение.

Текущая ситуация

  • Меня вводят в целевой процесс.
  • Целевой процесс 'FeatureLevel = Level_11_0
  • Target SwapChain выполняется с флагом DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.
  • SwapChain:: Текущая функция подключена.
  • Скриншот получается черным и целевым сбоем процесса. без выполнения скриншотов выполняется нормально.

Желаемая ситуация

Сделайте снимок экрана правильно и продолжите процесс цели с его нормальным выполнением.

код

ПРИМЕЧАНИЕ Класс Hook такой же, как в ссылке. Я добавил только версию UnmodifiableHook, которая делает то, что говорит его имя. Я оставил все несущественные биты.

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

Использование кода

инициализации

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

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

ИЗМЕНИТЬ

Я использовал другой метод для захвата снимка экрана, описанного в этой ссылке. Однако мой скриншот выглядит следующим образом:

Изображение радуги

Теперь это, похоже, проблема с настройками конверсии или что-то еще, но я не могу выяснить, что именно мне нужно сделать, чтобы исправить это. Я знаю, что поверхность, которую я конвертирую в растровое изображение, использует формат DXGI_FORMAT_R10G10B10A2_UNORM (32 бита, 10 бит на цвет и 2 для альфа, я думаю?). Но я не уверен, как это работает даже в циклах for (пропускать байты и прочее). Я просто скопировал его.

новая функция 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);
}

Ответ

Оказывается, формат DXGI_FORMAT_R10G10B10A2_UNORM находится в этом битном формате:

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

AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR

И Format32bppArgb находится в этом порядке:

BGRA

Таким образом, конечный код цикла будет выглядеть следующим образом:

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

Ответ 1

Этот снимок экрана выглядит так, как R10G10B10A2 загружается в R8G8B8A8. Я не тестировал ваш код, но мы должны иметь этот бит-макет

xxxxxxxx yyyyyyyy zzzzzzzz wwwwwwww
RRRRRRRR RRGGGGGG GGGGBBBB BBBBBBAA

и вы можете извлечь их следующим образом

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;

где r, g, b теперь имеют разрешение 10 бит. Если вы хотите масштабировать их обратно в байты, вы можете сделать это с помощью (byte) (r → 2).

Обновление

Это заменит цикл double for. У меня нет возможности проверить это, поэтому я не хочу продвигать его дальше, но я считаю, что идея правильная. Последняя проверка должна пропускать байты заполнения в каждой строке.

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

}