Захват скриншота полноэкранной программы DX11 с помощью SharpDX и EasyHook
прежде чем кто-либо упомянет об этом, я сослался на этой ссылка, чтобы узнать, как мне нужно скопировать backbuffer в растровое изображение.
текущая ситуация
- я вводится в целевой процесс
- целевой процесс ' FeatureLevel = Level_11_0
- Target SwapChain выполняется с флагом DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.
- SwapChain::присутствует функция зацепившийся.
- скриншот получается черный и целевой процесс сбоев. без скриншота процесс работает нормально.
нужные ситуации
сделайте снимок экрана правильно и позвольте целевому процессу продолжить его нормальное выполнение.
код
Примечание класс крючка такой же, как в ссылке. Я только добавлена версия 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 (пропуская байты и прочее). Я просто скопировал его.новая функция крючком
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:UsersSOMEUSERNAMEDesktoptest.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. Я не тестировал ваш код, но у нас должен быть этот бит layout
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).
обновление
Это заменит ваш двойной цикл 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++;
}
}