Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

436 righe
19 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. //using SlimDX.DXGI;
  5. //using SlimDX.Direct3D10;
  6. //using SlimDX;
  7. using System.IO;
  8. using System.Threading;
  9. //using Device = SlimDX.Direct3D10.Device;
  10. using RenderHookAPI.Interface;
  11. using SharpDX.Direct3D10;
  12. using Device = SharpDX.Direct3D10.Device;
  13. using SharpDX.DXGI;
  14. using SharpDX;
  15. namespace RenderHookAPI.Hook
  16. {
  17. enum D3D10DeviceVTbl : short
  18. {
  19. // IUnknown
  20. QueryInterface = 0,
  21. AddRef = 1,
  22. Release = 2,
  23. // ID3D10Device
  24. VSSetConstantBuffers = 3,
  25. PSSetShaderResources = 4,
  26. PSSetShader = 5,
  27. PSSetSamplers = 6,
  28. VSSetShader = 7,
  29. DrawIndexed = 8,
  30. Draw = 9,
  31. PSSetConstantBuffers = 10,
  32. IASetInputLayout = 11,
  33. IASetVertexBuffers = 12,
  34. IASetIndexBuffer = 13,
  35. DrawIndexedInstanced = 14,
  36. DrawInstanced = 15,
  37. GSSetConstantBuffers = 16,
  38. GSSetShader = 17,
  39. IASetPrimitiveTopology = 18,
  40. VSSetShaderResources = 19,
  41. VSSetSamplers = 20,
  42. SetPredication = 21,
  43. GSSetShaderResources = 22,
  44. GSSetSamplers = 23,
  45. OMSetRenderTargets = 24,
  46. OMSetBlendState = 25,
  47. OMSetDepthStencilState = 26,
  48. SOSetTargets = 27,
  49. DrawAuto = 28,
  50. RSSetState = 29,
  51. RSSetViewports = 30,
  52. RSSetScissorRects = 31,
  53. CopySubresourceRegion = 32,
  54. CopyResource = 33,
  55. UpdateSubresource = 34,
  56. ClearRenderTargetView = 35,
  57. ClearDepthStencilView = 36,
  58. GenerateMips = 37,
  59. ResolveSubresource = 38,
  60. VSGetConstantBuffers = 39,
  61. PSGetShaderResources = 40,
  62. PSGetShader = 41,
  63. PSGetSamplers = 42,
  64. VSGetShader = 43,
  65. PSGetConstantBuffers = 44,
  66. IAGetInputLayout = 45,
  67. IAGetVertexBuffers = 46,
  68. IAGetIndexBuffer = 47,
  69. GSGetConstantBuffers = 48,
  70. GSGetShader = 49,
  71. IAGetPrimitiveTopology = 50,
  72. VSGetShaderResources = 51,
  73. VSGetSamplers = 52,
  74. GetPredication = 53,
  75. GSGetShaderResources = 54,
  76. GSGetSamplers = 55,
  77. OMGetRenderTargets = 56,
  78. OMGetBlendState = 57,
  79. OMGetDepthStencilState = 58,
  80. SOGetTargets = 59,
  81. RSGetState = 60,
  82. RSGetViewports = 61,
  83. RSGetScissorRects = 62,
  84. GetDeviceRemovedReason = 63,
  85. SetExceptionMode = 64,
  86. GetExceptionMode = 65,
  87. GetPrivateData = 66,
  88. SetPrivateData = 67,
  89. SetPrivateDataInterface = 68,
  90. ClearState = 69,
  91. Flush = 70,
  92. CreateBuffer = 71,
  93. CreateTexture1D = 72,
  94. CreateTexture2D = 73,
  95. CreateTexture3D = 74,
  96. CreateShaderResourceView = 75,
  97. CreateRenderTargetView = 76,
  98. CreateDepthStencilView = 77,
  99. CreateInputLayout = 78,
  100. CreateVertexShader = 79,
  101. CreateGeometryShader = 80,
  102. CreateGemoetryShaderWithStreamOutput = 81,
  103. CreatePixelShader = 82,
  104. CreateBlendState = 83,
  105. CreateDepthStencilState = 84,
  106. CreateRasterizerState = 85,
  107. CreateSamplerState = 86,
  108. CreateQuery = 87,
  109. CreatePredicate = 88,
  110. CreateCounter = 89,
  111. CheckFormatSupport = 90,
  112. CheckMultisampleQualityLevels = 91,
  113. CheckCounterInfo = 92,
  114. CheckCounter = 93,
  115. GetCreationFlags = 94,
  116. OpenSharedResource = 95,
  117. SetTextFilterSize = 96,
  118. GetTextFilterSize = 97,
  119. }
  120. /// <summary>
  121. /// Direct3D 10 Hook - this hooks the SwapChain.Present method to capture images
  122. /// </summary>
  123. internal class DXHookD3D10: BaseDXHook
  124. {
  125. const int D3D10_DEVICE_METHOD_COUNT = 98;
  126. public DXHookD3D10(CaptureInterface ssInterface)
  127. : base(ssInterface)
  128. {
  129. this.DebugMessage("Create");
  130. }
  131. List<IntPtr> _d3d10VTblAddresses = null;
  132. List<IntPtr> _dxgiSwapChainVTblAddresses = null;
  133. Hook<DXGISwapChain_PresentDelegate> DXGISwapChain_PresentHook = null;
  134. Hook<DXGISwapChain_ResizeTargetDelegate> DXGISwapChain_ResizeTargetHook = null;
  135. protected override string HookName
  136. {
  137. get
  138. {
  139. return "DXHookD3D10";
  140. }
  141. }
  142. public override void Hook()
  143. {
  144. this.DebugMessage("Hook: Begin");
  145. // Determine method addresses in Direct3D10.Device, and DXGI.SwapChain
  146. if (_d3d10VTblAddresses == null)
  147. {
  148. _d3d10VTblAddresses = new List<IntPtr>();
  149. _dxgiSwapChainVTblAddresses = new List<IntPtr>();
  150. this.DebugMessage("Hook: Before device creation");
  151. using (var factory = new Factory1())
  152. {
  153. using (var device = new Device(factory.GetAdapter(0), DeviceCreationFlags.None))
  154. {
  155. this.DebugMessage("Hook: Device created");
  156. _d3d10VTblAddresses.AddRange(GetVTblAddresses(device.NativePointer, D3D10_DEVICE_METHOD_COUNT));
  157. using (var renderForm = new System.Windows.Forms.Form())
  158. {
  159. using (SharpDX.DXGI.SwapChain sc = new SharpDX.DXGI.SwapChain(factory, device, DXGI.CreateSwapChainDescription(renderForm.Handle)))
  160. {
  161. _dxgiSwapChainVTblAddresses.AddRange(GetVTblAddresses(sc.NativePointer, DXGI.DXGI_SWAPCHAIN_METHOD_COUNT));
  162. }
  163. }
  164. }
  165. }
  166. }
  167. // We will capture the backbuffer here
  168. DXGISwapChain_PresentHook = new Hook<DXGISwapChain_PresentDelegate>(
  169. _dxgiSwapChainVTblAddresses[(int)DXGI.DXGISwapChainVTbl.Present],
  170. new DXGISwapChain_PresentDelegate(PresentHook),
  171. this);
  172. // We will capture target/window resizes here
  173. DXGISwapChain_ResizeTargetHook = new Hook<DXGISwapChain_ResizeTargetDelegate>(
  174. _dxgiSwapChainVTblAddresses[(int)DXGI.DXGISwapChainVTbl.ResizeTarget],
  175. new DXGISwapChain_ResizeTargetDelegate(ResizeTargetHook),
  176. this);
  177. /*
  178. * Don't forget that all hooks will start deactivated...
  179. * The following ensures that all threads are intercepted:
  180. * Note: you must do this for each hook.
  181. */
  182. DXGISwapChain_PresentHook.Activate();
  183. DXGISwapChain_ResizeTargetHook.Activate();
  184. Hooks.Add(DXGISwapChain_PresentHook);
  185. Hooks.Add(DXGISwapChain_ResizeTargetHook);
  186. }
  187. public override void Cleanup()
  188. {
  189. try
  190. {
  191. }
  192. catch
  193. {
  194. }
  195. }
  196. /// <summary>
  197. /// The IDXGISwapChain.Present function definition
  198. /// </summary>
  199. /// <param name="device"></param>
  200. /// <returns></returns>
  201. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  202. delegate int DXGISwapChain_PresentDelegate(IntPtr swapChainPtr, int syncInterval, PresentFlags flags);
  203. /// <summary>
  204. /// The IDXGISwapChain.ResizeTarget function definition
  205. /// </summary>
  206. /// <param name="device"></param>
  207. /// <returns></returns>
  208. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  209. delegate int DXGISwapChain_ResizeTargetDelegate(IntPtr swapChainPtr, ref ModeDescription newTargetParameters);
  210. /// <summary>
  211. /// Hooked to allow resizing a texture/surface that is reused. Currently not in use as we create the texture for each request
  212. /// to support different sizes each time (as we use DirectX to copy only the region we are after rather than the entire backbuffer)
  213. /// </summary>
  214. /// <param name="swapChainPtr"></param>
  215. /// <param name="newTargetParameters"></param>
  216. /// <returns></returns>
  217. int ResizeTargetHook(IntPtr swapChainPtr, ref ModeDescription newTargetParameters)
  218. {
  219. SwapChain swapChain = (SwapChain)swapChainPtr;
  220. // This version creates a new texture for each request so there is nothing to resize.
  221. // IF the size of the texture is known each time, we could create it once, and then possibly need to resize it here
  222. swapChain.ResizeTarget(ref newTargetParameters);
  223. return SharpDX.Result.Ok.Code;
  224. }
  225. /// <summary>
  226. /// Our present hook that will grab a copy of the backbuffer when requested. Note: this supports multi-sampling (anti-aliasing)
  227. /// </summary>
  228. /// <param name="swapChainPtr"></param>
  229. /// <param name="syncInterval"></param>
  230. /// <param name="flags"></param>
  231. /// <returns>The HRESULT of the original method</returns>
  232. int PresentHook(IntPtr swapChainPtr, int syncInterval, PresentFlags flags)
  233. {
  234. this.Frame();
  235. SwapChain swapChain = (SharpDX.DXGI.SwapChain)swapChainPtr;
  236. try
  237. {
  238. #region Screenshot Request
  239. if (this.Request != null)
  240. {
  241. try
  242. {
  243. this.DebugMessage("PresentHook: Request Start");
  244. DateTime startTime = DateTime.Now;
  245. using (Texture2D texture = Texture2D.FromSwapChain<SharpDX.Direct3D10.Texture2D>(swapChain, 0))
  246. {
  247. #region Determine region to capture
  248. System.Drawing.Rectangle regionToCapture = new System.Drawing.Rectangle(0, 0, texture.Description.Width, texture.Description.Height);
  249. if (this.Request.RegionToCapture.Width > 0)
  250. {
  251. regionToCapture = this.Request.RegionToCapture;
  252. }
  253. #endregion
  254. var theTexture = texture;
  255. // If texture is multisampled, then we can use ResolveSubresource to copy it into a non-multisampled texture
  256. Texture2D textureResolved = null;
  257. if (texture.Description.SampleDescription.Count > 1)
  258. {
  259. this.DebugMessage("PresentHook: resolving multi-sampled texture");
  260. // texture is multi-sampled, lets resolve it down to single sample
  261. textureResolved = new Texture2D(texture.Device, new Texture2DDescription()
  262. {
  263. CpuAccessFlags = CpuAccessFlags.None,
  264. Format = texture.Description.Format,
  265. Height = texture.Description.Height,
  266. Usage = ResourceUsage.Default,
  267. Width = texture.Description.Width,
  268. ArraySize = 1,
  269. SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0), // Ensure single sample
  270. BindFlags = BindFlags.None,
  271. MipLevels = 1,
  272. OptionFlags = texture.Description.OptionFlags
  273. });
  274. // Resolve into textureResolved
  275. texture.Device.ResolveSubresource(texture, 0, textureResolved, 0, texture.Description.Format);
  276. // Make "theTexture" be the resolved texture
  277. theTexture = textureResolved;
  278. }
  279. // Create destination texture
  280. Texture2D textureDest = new Texture2D(texture.Device, new Texture2DDescription()
  281. {
  282. CpuAccessFlags = CpuAccessFlags.None,// CpuAccessFlags.Write | CpuAccessFlags.Read,
  283. Format = SharpDX.DXGI.Format.R8G8B8A8_UNorm, // Supports BMP/PNG
  284. Height = regionToCapture.Height,
  285. Usage = ResourceUsage.Default,// ResourceUsage.Staging,
  286. Width = regionToCapture.Width,
  287. ArraySize = 1,//texture.Description.ArraySize,
  288. SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),// texture.Description.SampleDescription,
  289. BindFlags = BindFlags.None,
  290. MipLevels = 1,//texture.Description.MipLevels,
  291. OptionFlags = texture.Description.OptionFlags
  292. });
  293. // Copy the subresource region, we are dealing with a flat 2D texture with no MipMapping, so 0 is the subresource index
  294. theTexture.Device.CopySubresourceRegion(theTexture, 0, new ResourceRegion()
  295. {
  296. Top = regionToCapture.Top,
  297. Bottom = regionToCapture.Bottom,
  298. Left = regionToCapture.Left,
  299. Right = regionToCapture.Right,
  300. Front = 0,
  301. Back = 1 // Must be 1 or only black will be copied
  302. }, textureDest, 0, 0, 0, 0);
  303. // Note: it would be possible to capture multiple frames and process them in a background thread
  304. // Copy to memory and send back to host process on a background thread so that we do not cause any delay in the rendering pipeline
  305. var request = this.Request.Clone(); // this.Request gets set to null, so copy the Request for use in the thread
  306. ThreadPool.QueueUserWorkItem(delegate
  307. {
  308. //FileStream fs = new FileStream(@"c:\temp\temp.bmp", FileMode.Create);
  309. //Texture2D.ToStream(testSubResourceCopy, ImageFileFormat.Bmp, fs);
  310. DateTime startCopyToSystemMemory = DateTime.Now;
  311. using (MemoryStream ms = new MemoryStream())
  312. {
  313. Texture2D.ToStream(textureDest, ImageFileFormat.Bmp, ms);
  314. ms.Position = 0;
  315. this.DebugMessage("PresentHook: Copy to System Memory time: " + (DateTime.Now - startCopyToSystemMemory).ToString());
  316. DateTime startSendResponse = DateTime.Now;
  317. ProcessCapture(ms, request);
  318. this.DebugMessage("PresentHook: Send response time: " + (DateTime.Now - startSendResponse).ToString());
  319. }
  320. // Free the textureDest as we no longer need it.
  321. textureDest.Dispose();
  322. textureDest = null;
  323. this.DebugMessage("PresentHook: Full Capture time: " + (DateTime.Now - startTime).ToString());
  324. });
  325. // Make sure we free up the resolved texture if it was created
  326. if (textureResolved != null)
  327. {
  328. textureResolved.Dispose();
  329. textureResolved = null;
  330. }
  331. }
  332. this.DebugMessage("PresentHook: Copy BackBuffer time: " + (DateTime.Now - startTime).ToString());
  333. this.DebugMessage("PresentHook: Request End");
  334. }
  335. finally
  336. {
  337. // Prevent the request from being processed a second time
  338. this.Request = null;
  339. }
  340. }
  341. #endregion
  342. #region Example: Draw overlay (after screenshot so we don't capture overlay as well)
  343. if (this.Config.ShowOverlay)
  344. {
  345. using (Texture2D texture = Texture2D.FromSwapChain<SharpDX.Direct3D10.Texture2D>(swapChain, 0))
  346. {
  347. if (FPS.GetFPS() >= 1)
  348. {
  349. FontDescription fd = new SharpDX.Direct3D10.FontDescription()
  350. {
  351. Height = 16,
  352. FaceName = "Arial",
  353. Italic = false,
  354. Width = 0,
  355. MipLevels = 1,
  356. CharacterSet = SharpDX.Direct3D10.FontCharacterSet.Default,
  357. OutputPrecision = SharpDX.Direct3D10.FontPrecision.Default,
  358. Quality = SharpDX.Direct3D10.FontQuality.Antialiased,
  359. PitchAndFamily = FontPitchAndFamily.Default | FontPitchAndFamily.DontCare,
  360. Weight = FontWeight.Bold
  361. };
  362. // TODO: Font should not be created every frame!
  363. using (Font font = new Font(texture.Device, fd))
  364. {
  365. DrawText(font, new Vector2(5, 5), String.Format("{0:N0} fps", FPS.GetFPS()), new Color4(SharpDX.Color.Red.ToColor3()));
  366. if (this.TextDisplay != null && this.TextDisplay.Display)
  367. {
  368. DrawText(font, new Vector2(5, 25), this.TextDisplay.Text, new Color4(Color.Red.ToColor3(), (Math.Abs(1.0f - TextDisplay.Remaining))));
  369. }
  370. }
  371. }
  372. }
  373. }
  374. #endregion
  375. }
  376. catch (Exception e)
  377. {
  378. // If there is an error we do not want to crash the hooked application, so swallow the exception
  379. this.DebugMessage("PresentHook: Exeception: " + e.GetType().FullName + ": " + e.Message);
  380. }
  381. // As always we need to call the original method, note that EasyHook has already repatched the original method
  382. // so calling it here will not cause an endless recursion to this function
  383. swapChain.Present(syncInterval, flags);
  384. return SharpDX.Result.Ok.Code;
  385. }
  386. private void DrawText(Font font, Vector2 pos, string text, Color4 color)
  387. {
  388. font.DrawText(null, text, new SharpDX.Rectangle((int)pos.X, (int)pos.Y, 0, 0), SharpDX.Direct3D10.FontDrawFlags.NoClip, color);
  389. }
  390. }
  391. }