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.

437 righe
20 KiB

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