Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

438 Zeilen
20 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. //using SlimDX.Direct3D9;
  4. using System.Runtime.InteropServices;
  5. using RenderHookAPI.Interface;
  6. using SharpDX.Direct3D9;
  7. using RenderHookAPI.Hook.Common;
  8. namespace RenderHookAPI.Hook
  9. {
  10. internal class DXHookD3D9: BaseDXHook
  11. {
  12. public DXHookD3D9(CaptureInterface ssInterface)
  13. : base(ssInterface)
  14. {
  15. }
  16. Hook<Direct3D9Device_EndSceneDelegate> Direct3DDevice_EndSceneHook = null;
  17. Hook<Direct3D9Device_ResetDelegate> Direct3DDevice_ResetHook = null;
  18. Hook<Direct3D9Device_PresentDelegate> Direct3DDevice_PresentHook = null;
  19. Hook<Direct3D9DeviceEx_PresentExDelegate> Direct3DDeviceEx_PresentExHook = null;
  20. object _lockRenderTarget = new object();
  21. bool _resourcesInitialised;
  22. Query _query;
  23. SharpDX.Direct3D9.Font _font;
  24. bool _queryIssued;
  25. ScreenshotRequest _requestCopy;
  26. bool _renderTargetCopyLocked = false;
  27. Surface _renderTargetCopy;
  28. Surface _resolvedTarget;
  29. protected override string HookName
  30. {
  31. get
  32. {
  33. return "DXHookD3D9";
  34. }
  35. }
  36. List<IntPtr> id3dDeviceFunctionAddresses = new List<IntPtr>();
  37. //List<IntPtr> id3dDeviceExFunctionAddresses = new List<IntPtr>();
  38. const int D3D9_DEVICE_METHOD_COUNT = 119;
  39. const int D3D9Ex_DEVICE_METHOD_COUNT = 15;
  40. bool _supportsDirect3D9Ex = false;
  41. public override void Hook()
  42. {
  43. this.DebugMessage("Hook: Begin");
  44. // First we need to determine the function address for IDirect3DDevice9
  45. Device device;
  46. id3dDeviceFunctionAddresses = new List<IntPtr>();
  47. //id3dDeviceExFunctionAddresses = new List<IntPtr>();
  48. this.DebugMessage("Hook: Before device creation");
  49. using (Direct3D d3d = new Direct3D())
  50. {
  51. using (var renderForm = new System.Windows.Forms.Form())
  52. {
  53. using (device = new Device(d3d, 0, DeviceType.NullReference, IntPtr.Zero, CreateFlags.HardwareVertexProcessing, new PresentParameters() { BackBufferWidth = 1, BackBufferHeight = 1, DeviceWindowHandle = renderForm.Handle }))
  54. {
  55. this.DebugMessage("Hook: Device created");
  56. id3dDeviceFunctionAddresses.AddRange(GetVTblAddresses(device.NativePointer, D3D9_DEVICE_METHOD_COUNT));
  57. }
  58. }
  59. }
  60. try
  61. {
  62. using (Direct3DEx d3dEx = new Direct3DEx())
  63. {
  64. this.DebugMessage("Hook: Direct3DEx...");
  65. using (var renderForm = new System.Windows.Forms.Form())
  66. {
  67. using (var deviceEx = new DeviceEx(d3dEx, 0, DeviceType.NullReference, IntPtr.Zero, CreateFlags.HardwareVertexProcessing, new PresentParameters() { BackBufferWidth = 1, BackBufferHeight = 1, DeviceWindowHandle = renderForm.Handle }, new DisplayModeEx() { Width = 800, Height = 600 }))
  68. {
  69. this.DebugMessage("Hook: DeviceEx created - PresentEx supported");
  70. id3dDeviceFunctionAddresses.AddRange(GetVTblAddresses(deviceEx.NativePointer, D3D9_DEVICE_METHOD_COUNT, D3D9Ex_DEVICE_METHOD_COUNT));
  71. _supportsDirect3D9Ex = true;
  72. }
  73. }
  74. }
  75. }
  76. catch (Exception)
  77. {
  78. _supportsDirect3D9Ex = false;
  79. }
  80. // We want to hook each method of the IDirect3DDevice9 interface that we are interested in
  81. // 42 - EndScene (we will retrieve the back buffer here)
  82. Direct3DDevice_EndSceneHook = new Hook<Direct3D9Device_EndSceneDelegate>(
  83. id3dDeviceFunctionAddresses[(int)Direct3DDevice9FunctionOrdinals.EndScene],
  84. // On Windows 7 64-bit w/ 32-bit app and d3d9 dll version 6.1.7600.16385, the address is equiv to:
  85. // (IntPtr)(GetModuleHandle("d3d9").ToInt32() + 0x1ce09),
  86. // A 64-bit app would use 0xff18
  87. // Note: GetD3D9DeviceFunctionAddress will output these addresses to a log file
  88. new Direct3D9Device_EndSceneDelegate(EndSceneHook),
  89. this);
  90. unsafe
  91. {
  92. // If Direct3D9Ex is available - hook the PresentEx
  93. if (_supportsDirect3D9Ex)
  94. {
  95. Direct3DDeviceEx_PresentExHook = new Hook<Direct3D9DeviceEx_PresentExDelegate>(
  96. id3dDeviceFunctionAddresses[(int)Direct3DDevice9ExFunctionOrdinals.PresentEx],
  97. new Direct3D9DeviceEx_PresentExDelegate(PresentExHook),
  98. this);
  99. }
  100. // Always hook Present also (device will only call Present or PresentEx not both)
  101. Direct3DDevice_PresentHook = new Hook<Direct3D9Device_PresentDelegate>(
  102. id3dDeviceFunctionAddresses[(int)Direct3DDevice9FunctionOrdinals.Present],
  103. new Direct3D9Device_PresentDelegate(PresentHook),
  104. this);
  105. }
  106. // 16 - Reset (called on resolution change or windowed/fullscreen change - we will reset some things as well)
  107. Direct3DDevice_ResetHook = new Hook<Direct3D9Device_ResetDelegate>(
  108. id3dDeviceFunctionAddresses[(int)Direct3DDevice9FunctionOrdinals.Reset],
  109. // On Windows 7 64-bit w/ 32-bit app and d3d9 dll version 6.1.7600.16385, the address is equiv to:
  110. //(IntPtr)(GetModuleHandle("d3d9").ToInt32() + 0x58dda),
  111. // A 64-bit app would use 0x3b3a0
  112. // Note: GetD3D9DeviceFunctionAddress will output these addresses to a log file
  113. new Direct3D9Device_ResetDelegate(ResetHook),
  114. this);
  115. /*
  116. * Don't forget that all hooks will start deactivated...
  117. * The following ensures that all threads are intercepted:
  118. * Note: you must do this for each hook.
  119. */
  120. Direct3DDevice_EndSceneHook.Activate();
  121. Hooks.Add(Direct3DDevice_EndSceneHook);
  122. Direct3DDevice_PresentHook.Activate();
  123. Hooks.Add(Direct3DDevice_PresentHook);
  124. if (_supportsDirect3D9Ex)
  125. {
  126. Direct3DDeviceEx_PresentExHook.Activate();
  127. Hooks.Add(Direct3DDeviceEx_PresentExHook);
  128. }
  129. Direct3DDevice_ResetHook.Activate();
  130. Hooks.Add(Direct3DDevice_ResetHook);
  131. this.DebugMessage("Hook: End");
  132. }
  133. /// <summary>
  134. /// Just ensures that the surface we created is cleaned up.
  135. /// </summary>
  136. public override void Cleanup()
  137. {
  138. lock (_lockRenderTarget)
  139. {
  140. _resourcesInitialised = false;
  141. RemoveAndDispose(ref _renderTargetCopy);
  142. _renderTargetCopyLocked = false;
  143. RemoveAndDispose(ref _resolvedTarget);
  144. RemoveAndDispose(ref _query);
  145. _queryIssued = false;
  146. RemoveAndDispose(ref _font);
  147. RemoveAndDispose(ref _overlayEngine);
  148. }
  149. }
  150. /// <summary>
  151. /// The IDirect3DDevice9.EndScene function definition
  152. /// </summary>
  153. /// <param name="device"></param>
  154. /// <returns></returns>
  155. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  156. delegate int Direct3D9Device_EndSceneDelegate(IntPtr device);
  157. /// <summary>
  158. /// The IDirect3DDevice9.Reset function definition
  159. /// </summary>
  160. /// <param name="device"></param>
  161. /// <param name="presentParameters"></param>
  162. /// <returns></returns>
  163. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  164. delegate int Direct3D9Device_ResetDelegate(IntPtr device, ref PresentParameters presentParameters);
  165. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  166. unsafe delegate int Direct3D9Device_PresentDelegate(IntPtr devicePtr, SharpDX.Rectangle* pSourceRect, SharpDX.Rectangle* pDestRect, IntPtr hDestWindowOverride, IntPtr pDirtyRegion);
  167. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  168. unsafe delegate int Direct3D9DeviceEx_PresentExDelegate(IntPtr devicePtr, SharpDX.Rectangle* pSourceRect, SharpDX.Rectangle* pDestRect, IntPtr hDestWindowOverride, IntPtr pDirtyRegion, Present dwFlags);
  169. /// <summary>
  170. /// Reset the _renderTarget so that we are sure it will have the correct presentation parameters (required to support working across changes to windowed/fullscreen or resolution changes)
  171. /// </summary>
  172. /// <param name="devicePtr"></param>
  173. /// <param name="presentParameters"></param>
  174. /// <returns></returns>
  175. int ResetHook(IntPtr devicePtr, ref PresentParameters presentParameters)
  176. {
  177. // Ensure certain overlay resources have performed necessary pre-reset tasks
  178. if (_overlayEngine != null)
  179. _overlayEngine.BeforeDeviceReset();
  180. Cleanup();
  181. return Direct3DDevice_ResetHook.Original(devicePtr, ref presentParameters);
  182. }
  183. bool _isUsingPresent = false;
  184. // Used in the overlay
  185. unsafe int PresentExHook(IntPtr devicePtr, SharpDX.Rectangle* pSourceRect, SharpDX.Rectangle* pDestRect, IntPtr hDestWindowOverride, IntPtr pDirtyRegion, Present dwFlags)
  186. {
  187. _isUsingPresent = true;
  188. DeviceEx device = (DeviceEx)devicePtr;
  189. DoCaptureRenderTarget(device, "PresentEx");
  190. return Direct3DDeviceEx_PresentExHook.Original(devicePtr, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags);
  191. }
  192. unsafe int PresentHook(IntPtr devicePtr, SharpDX.Rectangle* pSourceRect, SharpDX.Rectangle* pDestRect, IntPtr hDestWindowOverride, IntPtr pDirtyRegion)
  193. {
  194. _isUsingPresent = true;
  195. Device device = (Device)devicePtr;
  196. DoCaptureRenderTarget(device, "PresentHook");
  197. return Direct3DDevice_PresentHook.Original(devicePtr, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
  198. }
  199. /// <summary>
  200. /// Hook for IDirect3DDevice9.EndScene
  201. /// </summary>
  202. /// <param name="devicePtr">Pointer to the IDirect3DDevice9 instance. Note: object member functions always pass "this" as the first parameter.</param>
  203. /// <returns>The HRESULT of the original EndScene</returns>
  204. /// <remarks>Remember that this is called many times a second by the Direct3D application - be mindful of memory and performance!</remarks>
  205. int EndSceneHook(IntPtr devicePtr)
  206. {
  207. Device device = (Device)devicePtr;
  208. if (!_isUsingPresent)
  209. DoCaptureRenderTarget(device, "EndSceneHook");
  210. return Direct3DDevice_EndSceneHook.Original(devicePtr);
  211. }
  212. RenderHookAPI.Hook.DX9.DXOverlayEngine _overlayEngine;
  213. /// <summary>
  214. /// Implementation of capturing from the render target of the Direct3D9 Device (or DeviceEx)
  215. /// </summary>
  216. /// <param name="device"></param>
  217. void DoCaptureRenderTarget(Device device, string hook)
  218. {
  219. this.Frame();
  220. try
  221. {
  222. #region Screenshot Request
  223. // If we have issued the command to copy data to our render target, check if it is complete
  224. bool qryResult;
  225. if (_queryIssued && _requestCopy != null && _query.GetData(out qryResult, false))
  226. {
  227. // The GPU has finished copying data to _renderTargetCopy, we can now lock
  228. // the data and access it on another thread.
  229. _queryIssued = false;
  230. // Lock the render target
  231. SharpDX.Rectangle rect;
  232. SharpDX.DataRectangle lockedRect = LockRenderTarget(_renderTargetCopy, out rect);
  233. _renderTargetCopyLocked = true;
  234. // Copy the data from the render target
  235. System.Threading.Tasks.Task.Factory.StartNew(() =>
  236. {
  237. lock (_lockRenderTarget)
  238. {
  239. ProcessCapture(rect.Width, rect.Height, lockedRect.Pitch, _renderTargetCopy.Description.Format.ToPixelFormat(), lockedRect.DataPointer, _requestCopy);
  240. }
  241. });
  242. }
  243. // Single frame capture request
  244. if (this.Request != null)
  245. {
  246. DateTime start = DateTime.Now;
  247. try
  248. {
  249. using (Surface renderTarget = device.GetRenderTarget(0))
  250. {
  251. int width, height;
  252. //If resizing of the captured image, determine correct dimensions
  253. if (Request.Resize != null && (renderTarget.Description.Width > Request.Resize.Value.Width || renderTarget.Description.Height > Request.Resize.Value.Height))
  254. {
  255. if (renderTarget.Description.Width > Request.Resize.Value.Width)
  256. {
  257. width = Request.Resize.Value.Width;
  258. height = (int)Math.Round((renderTarget.Description.Height * ((double)Request.Resize.Value.Width / (double)renderTarget.Description.Width)));
  259. }
  260. else
  261. {
  262. height = Request.Resize.Value.Height;
  263. width = (int)Math.Round((renderTarget.Description.Width * ((double)Request.Resize.Value.Height / (double)renderTarget.Description.Height)));
  264. }
  265. }
  266. else
  267. {
  268. width = renderTarget.Description.Width;
  269. height = renderTarget.Description.Height;
  270. }
  271. // If existing _renderTargetCopy, ensure that it is the correct size and format
  272. if (_renderTargetCopy != null && (_renderTargetCopy.Description.Width != width || _renderTargetCopy.Description.Height != height || _renderTargetCopy.Description.Format != renderTarget.Description.Format))
  273. {
  274. // Cleanup resources
  275. Cleanup();
  276. }
  277. // Ensure that we have something to put the render target data into
  278. if (!_resourcesInitialised || _renderTargetCopy == null)
  279. {
  280. CreateResources(device, width, height, renderTarget.Description.Format);
  281. }
  282. // Resize from render target Surface to resolvedSurface (also deals with resolving multi-sampling)
  283. device.StretchRectangle(renderTarget, _resolvedTarget, TextureFilter.None);
  284. }
  285. // If the render target is locked from a previous request unlock it
  286. if (_renderTargetCopyLocked)
  287. {
  288. // Wait for the the ProcessCapture thread to finish with it
  289. lock (_lockRenderTarget)
  290. {
  291. if (_renderTargetCopyLocked)
  292. {
  293. _renderTargetCopy.UnlockRectangle();
  294. _renderTargetCopyLocked = false;
  295. }
  296. }
  297. }
  298. // Copy data from resolved target to our render target copy
  299. device.GetRenderTargetData(_resolvedTarget, _renderTargetCopy);
  300. _requestCopy = Request.Clone();
  301. _query.Issue(Issue.End);
  302. _queryIssued = true;
  303. }
  304. finally
  305. {
  306. // We have completed the request - mark it as null so we do not continue to try to capture the same request
  307. // Note: If you are after high frame rates, consider implementing buffers here to capture more frequently
  308. // and send back to the host application as needed. The IPC overhead significantly slows down
  309. // the whole process if sending frame by frame.
  310. Request = null;
  311. }
  312. DateTime end = DateTime.Now;
  313. this.DebugMessage(hook + ": Capture time: " + (end - start).ToString());
  314. }
  315. #endregion
  316. var displayOverlays = Overlays;
  317. if (this.Config.ShowOverlay && displayOverlays != null)
  318. {
  319. #region Draw Overlay
  320. // Check if overlay needs to be initialised
  321. if (_overlayEngine == null || _overlayEngine.Device.NativePointer != device.NativePointer
  322. || IsOverlayUpdatePending)
  323. {
  324. // Cleanup if necessary
  325. if (_overlayEngine != null)
  326. RemoveAndDispose(ref _overlayEngine);
  327. _overlayEngine = ToDispose(new DX9.DXOverlayEngine());
  328. _overlayEngine.Overlays.AddRange((IEnumerable<IOverlay>)displayOverlays);
  329. _overlayEngine.Initialise(device);
  330. IsOverlayUpdatePending = false;
  331. }
  332. // Draw Overlay(s)
  333. if (_overlayEngine != null)
  334. {
  335. foreach (var overlay in _overlayEngine.Overlays)
  336. overlay.Frame();
  337. _overlayEngine.Draw();
  338. }
  339. #endregion
  340. }
  341. }
  342. catch (Exception e)
  343. {
  344. DebugMessage(e.ToString());
  345. }
  346. }
  347. private SharpDX.DataRectangle LockRenderTarget(Surface _renderTargetCopy, out SharpDX.Rectangle rect)
  348. {
  349. if (_requestCopy.RegionToCapture.Height > 0 && _requestCopy.RegionToCapture.Width > 0)
  350. {
  351. rect = new SharpDX.Rectangle(_requestCopy.RegionToCapture.Left, _requestCopy.RegionToCapture.Top, _requestCopy.RegionToCapture.Width, _requestCopy.RegionToCapture.Height);
  352. }
  353. else
  354. {
  355. rect = new SharpDX.Rectangle(0, 0, _renderTargetCopy.Description.Width, _renderTargetCopy.Description.Height);
  356. }
  357. return _renderTargetCopy.LockRectangle(rect, LockFlags.ReadOnly);
  358. }
  359. private void CreateResources(Device device, int width, int height, Format format)
  360. {
  361. if (_resourcesInitialised) return;
  362. _resourcesInitialised = true;
  363. // Create offscreen surface to use as copy of render target data
  364. _renderTargetCopy = ToDispose(Surface.CreateOffscreenPlain(device, width, height, format, Pool.SystemMemory));
  365. // Create our resolved surface (resizing if necessary and to resolve any multi-sampling)
  366. _resolvedTarget = ToDispose(Surface.CreateRenderTarget(device, width, height, format, MultisampleType.None, 0, false));
  367. _query = ToDispose(new Query(device, QueryType.Event));
  368. }
  369. }
  370. }