You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8 kuukautta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. using System;
  2. using System.Collections.Generic;
  3. using SharpDX.Direct3D11;
  4. using SharpDX.DXGI;
  5. using SharpDX;
  6. using System.Runtime.InteropServices;
  7. using System.Threading;
  8. using System.IO;
  9. using RenderHookAPI.Interface;
  10. using SharpDX.Direct3D;
  11. using RenderHookAPI.Hook.Common;
  12. namespace RenderHookAPI.Hook
  13. {
  14. enum D3D11DeviceVTbl : short
  15. {
  16. // IUnknown
  17. QueryInterface = 0,
  18. AddRef = 1,
  19. Release = 2,
  20. // ID3D11Device
  21. CreateBuffer = 3,
  22. CreateTexture1D = 4,
  23. CreateTexture2D = 5,
  24. CreateTexture3D = 6,
  25. CreateShaderResourceView = 7,
  26. CreateUnorderedAccessView = 8,
  27. CreateRenderTargetView = 9,
  28. CreateDepthStencilView = 10,
  29. CreateInputLayout = 11,
  30. CreateVertexShader = 12,
  31. CreateGeometryShader = 13,
  32. CreateGeometryShaderWithStreamOutput = 14,
  33. CreatePixelShader = 15,
  34. CreateHullShader = 16,
  35. CreateDomainShader = 17,
  36. CreateComputeShader = 18,
  37. CreateClassLinkage = 19,
  38. CreateBlendState = 20,
  39. CreateDepthStencilState = 21,
  40. CreateRasterizerState = 22,
  41. CreateSamplerState = 23,
  42. CreateQuery = 24,
  43. CreatePredicate = 25,
  44. CreateCounter = 26,
  45. CreateDeferredContext = 27,
  46. OpenSharedResource = 28,
  47. CheckFormatSupport = 29,
  48. CheckMultisampleQualityLevels = 30,
  49. CheckCounterInfo = 31,
  50. CheckCounter = 32,
  51. CheckFeatureSupport = 33,
  52. GetPrivateData = 34,
  53. SetPrivateData = 35,
  54. SetPrivateDataInterface = 36,
  55. GetFeatureLevel = 37,
  56. GetCreationFlags = 38,
  57. GetDeviceRemovedReason = 39,
  58. GetImmediateContext = 40,
  59. SetExceptionMode = 41,
  60. GetExceptionMode = 42,
  61. }
  62. /// <summary>
  63. /// Direct3D 11 Hook - this hooks the SwapChain.Present to take screenshots
  64. /// </summary>
  65. internal class DXHookD3D11: BaseDXHook
  66. {
  67. const int D3D11_DEVICE_METHOD_COUNT = 43;
  68. public DXHookD3D11(CaptureInterface ssInterface)
  69. : base(ssInterface)
  70. {
  71. }
  72. List<IntPtr> _d3d11VTblAddresses = null;
  73. List<IntPtr> _dxgiSwapChainVTblAddresses = null;
  74. Hook<DXGISwapChain_PresentDelegate> DXGISwapChain_PresentHook = null;
  75. Hook<DXGISwapChain_ResizeTargetDelegate> DXGISwapChain_ResizeTargetHook = null;
  76. object _lock = new object();
  77. #region Internal device resources
  78. SharpDX.Direct3D11.Device _device;
  79. SwapChain _swapChain;
  80. SharpDX.Windows.RenderForm _renderForm;
  81. Texture2D _resolvedRTShared;
  82. SharpDX.DXGI.KeyedMutex _resolvedRTSharedKeyedMutex;
  83. ShaderResourceView _resolvedSRV;
  84. RenderHookAPI.Hook.DX11.ScreenAlignedQuadRenderer _saQuad;
  85. Texture2D _finalRT;
  86. Texture2D _resizedRT;
  87. RenderTargetView _resizedRTV;
  88. #endregion
  89. Query _query;
  90. bool _queryIssued;
  91. bool _finalRTMapped;
  92. ScreenshotRequest _requestCopy;
  93. #region Main device resources
  94. Texture2D _resolvedRT;
  95. SharpDX.DXGI.KeyedMutex _resolvedRTKeyedMutex;
  96. SharpDX.DXGI.KeyedMutex _resolvedRTKeyedMutex_Dev2;
  97. #endregion
  98. protected override string HookName
  99. {
  100. get
  101. {
  102. return "DXHookD3D11";
  103. }
  104. }
  105. public override void Hook()
  106. {
  107. this.DebugMessage("Hook: Begin");
  108. if (_d3d11VTblAddresses == null)
  109. {
  110. _d3d11VTblAddresses = new List<IntPtr>();
  111. _dxgiSwapChainVTblAddresses = new List<IntPtr>();
  112. #region Get Device and SwapChain method addresses
  113. // Create temporary device + swapchain and determine method addresses
  114. _renderForm = ToDispose(new SharpDX.Windows.RenderForm());
  115. this.DebugMessage("Hook: Before device creation");
  116. SharpDX.Direct3D11.Device.CreateWithSwapChain(
  117. DriverType.Hardware,
  118. DeviceCreationFlags.BgraSupport,
  119. DXGI.CreateSwapChainDescription(_renderForm.Handle),
  120. out _device,
  121. out _swapChain);
  122. ToDispose(_device);
  123. ToDispose(_swapChain);
  124. if (_device != null && _swapChain != null)
  125. {
  126. this.DebugMessage("Hook: Device created");
  127. _d3d11VTblAddresses.AddRange(GetVTblAddresses(_device.NativePointer, D3D11_DEVICE_METHOD_COUNT));
  128. _dxgiSwapChainVTblAddresses.AddRange(GetVTblAddresses(_swapChain.NativePointer, DXGI.DXGI_SWAPCHAIN_METHOD_COUNT));
  129. }
  130. else
  131. {
  132. this.DebugMessage("Hook: Device creation failed");
  133. }
  134. #endregion
  135. }
  136. // We will capture the backbuffer here
  137. DXGISwapChain_PresentHook = new Hook<DXGISwapChain_PresentDelegate>(
  138. _dxgiSwapChainVTblAddresses[(int)DXGI.DXGISwapChainVTbl.Present],
  139. new DXGISwapChain_PresentDelegate(PresentHook),
  140. this);
  141. // We will capture target/window resizes here
  142. DXGISwapChain_ResizeTargetHook = new Hook<DXGISwapChain_ResizeTargetDelegate>(
  143. _dxgiSwapChainVTblAddresses[(int)DXGI.DXGISwapChainVTbl.ResizeTarget],
  144. new DXGISwapChain_ResizeTargetDelegate(ResizeTargetHook),
  145. this);
  146. /*
  147. * Don't forget that all hooks will start deactivated...
  148. * The following ensures that all threads are intercepted:
  149. * Note: you must do this for each hook.
  150. */
  151. DXGISwapChain_PresentHook.Activate();
  152. DXGISwapChain_ResizeTargetHook.Activate();
  153. Hooks.Add(DXGISwapChain_PresentHook);
  154. Hooks.Add(DXGISwapChain_ResizeTargetHook);
  155. }
  156. public override void Cleanup()
  157. {
  158. try
  159. {
  160. if (_overlayEngine != null)
  161. {
  162. _overlayEngine.Dispose();
  163. _overlayEngine = null;
  164. }
  165. }
  166. catch
  167. {
  168. }
  169. }
  170. /// <summary>
  171. /// The IDXGISwapChain.Present function definition
  172. /// </summary>
  173. /// <param name="device"></param>
  174. /// <returns></returns>
  175. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  176. delegate int DXGISwapChain_PresentDelegate(IntPtr swapChainPtr, int syncInterval, /* int */ SharpDX.DXGI.PresentFlags flags);
  177. /// <summary>
  178. /// The IDXGISwapChain.ResizeTarget function definition
  179. /// </summary>
  180. /// <param name="device"></param>
  181. /// <returns></returns>
  182. [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
  183. delegate int DXGISwapChain_ResizeTargetDelegate(IntPtr swapChainPtr, ref ModeDescription newTargetParameters);
  184. /// <summary>
  185. /// Hooked to allow resizing a texture/surface that is reused. Currently not in use as we create the texture for each request
  186. /// to support different sizes each time (as we use DirectX to copy only the region we are after rather than the entire backbuffer)
  187. /// </summary>
  188. /// <param name="swapChainPtr"></param>
  189. /// <param name="newTargetParameters"></param>
  190. /// <returns></returns>
  191. int ResizeTargetHook(IntPtr swapChainPtr, ref ModeDescription newTargetParameters)
  192. {
  193. // Dispose of overlay engine (so it will be recreated with correct renderTarget view size)
  194. if (_overlayEngine != null)
  195. {
  196. _overlayEngine.Dispose();
  197. _overlayEngine = null;
  198. }
  199. return DXGISwapChain_ResizeTargetHook.Original(swapChainPtr, ref newTargetParameters);
  200. }
  201. void EnsureResources(SharpDX.Direct3D11.Device device, Texture2DDescription description, Rectangle captureRegion, ScreenshotRequest request, bool useSameDeviceForResize = false)
  202. {
  203. var resizeDevice = useSameDeviceForResize ? device : _device;
  204. // Check if _resolvedRT or _finalRT require creation
  205. if (_finalRT != null && (_finalRT.Device.NativePointer == device.NativePointer || _finalRT.Device.NativePointer == _device.NativePointer) &&
  206. _finalRT.Description.Height == captureRegion.Height && _finalRT.Description.Width == captureRegion.Width &&
  207. _resolvedRT != null && _resolvedRT.Description.Height == description.Height && _resolvedRT.Description.Width == description.Width &&
  208. (_resolvedRT.Device.NativePointer == device.NativePointer || _resolvedRT.Device.NativePointer == _device.NativePointer) && _resolvedRT.Description.Format == description.Format
  209. )
  210. {
  211. }
  212. else
  213. {
  214. RemoveAndDispose(ref _query);
  215. RemoveAndDispose(ref _resolvedRT);
  216. RemoveAndDispose(ref _resolvedSRV);
  217. RemoveAndDispose(ref _finalRT);
  218. RemoveAndDispose(ref _resolvedRTShared);
  219. RemoveAndDispose(ref _resolvedRTKeyedMutex);
  220. RemoveAndDispose(ref _resolvedRTKeyedMutex_Dev2);
  221. _query = new Query(resizeDevice, new QueryDescription()
  222. {
  223. Flags = QueryFlags.None,
  224. Type = QueryType.Event
  225. });
  226. _queryIssued = false;
  227. try
  228. {
  229. ResourceOptionFlags resolvedRTOptionFlags = ResourceOptionFlags.None;
  230. if (device != resizeDevice)
  231. resolvedRTOptionFlags |= ResourceOptionFlags.SharedKeyedmutex;
  232. _resolvedRT = ToDispose(new Texture2D(device, new Texture2DDescription()
  233. {
  234. CpuAccessFlags = CpuAccessFlags.None,
  235. Format = description.Format, // for multisampled backbuffer, this must be same format
  236. Height = description.Height,
  237. Usage = ResourceUsage.Default,
  238. Width = description.Width,
  239. ArraySize = 1,
  240. SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0), // Ensure single sample
  241. BindFlags = BindFlags.ShaderResource,
  242. MipLevels = 1,
  243. OptionFlags = resolvedRTOptionFlags
  244. }));
  245. }
  246. catch
  247. {
  248. // Failed to create the shared resource, try again using the same device as game for resize
  249. EnsureResources(device, description, captureRegion, request, true);
  250. return;
  251. }
  252. // Retrieve reference to the keyed mutex
  253. _resolvedRTKeyedMutex = ToDispose(_resolvedRT.QueryInterfaceOrNull<SharpDX.DXGI.KeyedMutex>());
  254. // If the resolvedRT is a shared resource _resolvedRTKeyedMutex will not be null
  255. if (_resolvedRTKeyedMutex != null)
  256. {
  257. using (var resource = _resolvedRT.QueryInterface<SharpDX.DXGI.Resource>())
  258. {
  259. _resolvedRTShared = ToDispose(resizeDevice.OpenSharedResource<Texture2D>(resource.SharedHandle));
  260. _resolvedRTKeyedMutex_Dev2 = ToDispose(_resolvedRTShared.QueryInterfaceOrNull<SharpDX.DXGI.KeyedMutex>());
  261. }
  262. // SRV for use if resizing
  263. _resolvedSRV = ToDispose(new ShaderResourceView(resizeDevice, _resolvedRTShared));
  264. }
  265. else
  266. {
  267. _resolvedSRV = ToDispose(new ShaderResourceView(resizeDevice, _resolvedRT));
  268. }
  269. _finalRT = ToDispose(new Texture2D(resizeDevice, new Texture2DDescription()
  270. {
  271. CpuAccessFlags = CpuAccessFlags.Read,
  272. Format = description.Format,
  273. Height = captureRegion.Height,
  274. Usage = ResourceUsage.Staging,
  275. Width = captureRegion.Width,
  276. ArraySize = 1,
  277. SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
  278. BindFlags = BindFlags.None,
  279. MipLevels = 1,
  280. OptionFlags = ResourceOptionFlags.None
  281. }));
  282. _finalRTMapped = false;
  283. }
  284. if (_resolvedRT != null && _resolvedRTKeyedMutex_Dev2 == null && resizeDevice == _device)
  285. resizeDevice = device;
  286. if (resizeDevice != null && request.Resize != null && (_resizedRT == null || (_resizedRT.Device.NativePointer != resizeDevice.NativePointer || _resizedRT.Description.Width != request.Resize.Value.Width || _resizedRT.Description.Height != request.Resize.Value.Height)))
  287. {
  288. // Create/Recreate resources for resizing
  289. RemoveAndDispose(ref _resizedRT);
  290. RemoveAndDispose(ref _resizedRTV);
  291. RemoveAndDispose(ref _saQuad);
  292. _resizedRT = ToDispose(new Texture2D(resizeDevice, new Texture2DDescription()
  293. {
  294. Format = SharpDX.DXGI.Format.R8G8B8A8_UNorm, // Supports BMP/PNG/etc
  295. Height = request.Resize.Value.Height,
  296. Width = request.Resize.Value.Width,
  297. ArraySize = 1,
  298. SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
  299. BindFlags = BindFlags.RenderTarget,
  300. MipLevels = 1,
  301. Usage = ResourceUsage.Default,
  302. OptionFlags = ResourceOptionFlags.None
  303. }));
  304. _resizedRTV = ToDispose(new RenderTargetView(resizeDevice, _resizedRT));
  305. _saQuad = ToDispose(new DX11.ScreenAlignedQuadRenderer());
  306. _saQuad.Initialize(new DX11.DeviceManager(resizeDevice));
  307. }
  308. }
  309. /// <summary>
  310. /// Our present hook that will grab a copy of the backbuffer when requested. Note: this supports multi-sampling (anti-aliasing)
  311. /// </summary>
  312. /// <param name="swapChainPtr"></param>
  313. /// <param name="syncInterval"></param>
  314. /// <param name="flags"></param>
  315. /// <returns>The HRESULT of the original method</returns>
  316. int PresentHook(IntPtr swapChainPtr, int syncInterval, SharpDX.DXGI.PresentFlags flags)
  317. {
  318. this.Frame();
  319. SwapChain swapChain = (SharpDX.DXGI.SwapChain)swapChainPtr;
  320. try
  321. {
  322. #region Screenshot Request
  323. if (this.Request != null)
  324. {
  325. this.DebugMessage("PresentHook: Request Start");
  326. DateTime startTime = DateTime.Now;
  327. using (Texture2D currentRT = Texture2D.FromSwapChain<Texture2D>(swapChain, 0))
  328. {
  329. #region Determine region to capture
  330. Rectangle captureRegion = new Rectangle(0, 0, currentRT.Description.Width, currentRT.Description.Height);
  331. if (this.Request.RegionToCapture.Width > 0)
  332. {
  333. captureRegion = new Rectangle(this.Request.RegionToCapture.Left, this.Request.RegionToCapture.Top, this.Request.RegionToCapture.Width, this.Request.RegionToCapture.Height);
  334. }
  335. else if (this.Request.Resize.HasValue)
  336. {
  337. captureRegion = new Rectangle(0, 0, this.Request.Resize.Value.Width, this.Request.Resize.Value.Height);
  338. }
  339. #endregion
  340. // Create / Recreate resources as necessary
  341. EnsureResources(currentRT.Device, currentRT.Description, captureRegion, Request);
  342. Texture2D sourceTexture = null;
  343. // If texture is multisampled, then we can use ResolveSubresource to copy it into a non-multisampled texture
  344. if (currentRT.Description.SampleDescription.Count > 1 || Request.Resize.HasValue)
  345. {
  346. if (Request.Resize.HasValue)
  347. this.DebugMessage("PresentHook: resizing texture");
  348. else
  349. this.DebugMessage("PresentHook: resolving multi-sampled texture");
  350. // Resolve into _resolvedRT
  351. if (_resolvedRTKeyedMutex != null)
  352. _resolvedRTKeyedMutex.Acquire(0, int.MaxValue);
  353. currentRT.Device.ImmediateContext.ResolveSubresource(currentRT, 0, _resolvedRT, 0, _resolvedRT.Description.Format);
  354. if (_resolvedRTKeyedMutex != null)
  355. _resolvedRTKeyedMutex.Release(1);
  356. if (Request.Resize.HasValue)
  357. {
  358. lock(_lock)
  359. {
  360. if (_resolvedRTKeyedMutex_Dev2 != null)
  361. _resolvedRTKeyedMutex_Dev2.Acquire(1, int.MaxValue);
  362. _saQuad.ShaderResource = _resolvedSRV;
  363. _saQuad.RenderTargetView = _resizedRTV;
  364. _saQuad.RenderTarget = _resizedRT;
  365. _saQuad.Render();
  366. if (_resolvedRTKeyedMutex_Dev2 != null)
  367. _resolvedRTKeyedMutex_Dev2.Release(0);
  368. }
  369. // set sourceTexture to the resized RT
  370. sourceTexture = _resizedRT;
  371. }
  372. else
  373. {
  374. // Make sourceTexture be the resolved texture
  375. if (_resolvedRTShared != null)
  376. sourceTexture = _resolvedRTShared;
  377. else
  378. sourceTexture = _resolvedRT;
  379. }
  380. }
  381. else
  382. {
  383. // Copy the resource into the shared texture
  384. if (_resolvedRTKeyedMutex != null) _resolvedRTKeyedMutex.Acquire(0, int.MaxValue);
  385. currentRT.Device.ImmediateContext.CopySubresourceRegion(currentRT, 0, null, _resolvedRT, 0);
  386. if (_resolvedRTKeyedMutex != null) _resolvedRTKeyedMutex.Release(1);
  387. if (_resolvedRTShared != null)
  388. sourceTexture = _resolvedRTShared;
  389. else
  390. sourceTexture = _resolvedRT;
  391. }
  392. // 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
  393. _requestCopy = this.Request.Clone(); // this.Request gets set to null, so copy the Request for use in the thread
  394. // Prevent the request from being processed a second time
  395. this.Request = null;
  396. bool acquireLock = sourceTexture == _resolvedRTShared;
  397. ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
  398. {
  399. // Acquire lock on second device
  400. if (acquireLock && _resolvedRTKeyedMutex_Dev2 != null)
  401. _resolvedRTKeyedMutex_Dev2.Acquire(1, int.MaxValue);
  402. lock (_lock)
  403. {
  404. // Copy the subresource region, we are dealing with a flat 2D texture with no MipMapping, so 0 is the subresource index
  405. sourceTexture.Device.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, new ResourceRegion()
  406. {
  407. Top = captureRegion.Top,
  408. Bottom = captureRegion.Bottom,
  409. Left = captureRegion.Left,
  410. Right = captureRegion.Right,
  411. Front = 0,
  412. Back = 1 // Must be 1 or only black will be copied
  413. }, _finalRT, 0, 0, 0, 0);
  414. // Release lock upon shared surface on second device
  415. if (acquireLock && _resolvedRTKeyedMutex_Dev2 != null)
  416. _resolvedRTKeyedMutex_Dev2.Release(0);
  417. _finalRT.Device.ImmediateContext.End(_query);
  418. _queryIssued = true;
  419. while (_finalRT.Device.ImmediateContext.GetData(_query).ReadByte() != 1)
  420. {
  421. // Spin (usually only one cycle or no spin takes place)
  422. }
  423. DateTime startCopyToSystemMemory = DateTime.Now;
  424. try
  425. {
  426. DataBox db = default(DataBox);
  427. if (_requestCopy.Format == ImageFormat.PixelData)
  428. {
  429. db = _finalRT.Device.ImmediateContext.MapSubresource(_finalRT, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.DoNotWait);
  430. _finalRTMapped = true;
  431. }
  432. _queryIssued = false;
  433. try
  434. {
  435. using (MemoryStream ms = new MemoryStream())
  436. {
  437. switch (_requestCopy.Format)
  438. {
  439. case ImageFormat.Bitmap:
  440. case ImageFormat.Jpeg:
  441. case ImageFormat.Png:
  442. ToStream(_finalRT.Device.ImmediateContext, _finalRT, _requestCopy.Format, ms);
  443. break;
  444. case ImageFormat.PixelData:
  445. if (db.DataPointer != IntPtr.Zero)
  446. {
  447. ProcessCapture(_finalRT.Description.Width, _finalRT.Description.Height, db.RowPitch, System.Drawing.Imaging.PixelFormat.Format32bppArgb, db.DataPointer, _requestCopy);
  448. }
  449. return;
  450. }
  451. ms.Position = 0;
  452. ProcessCapture(ms, _requestCopy);
  453. }
  454. }
  455. finally
  456. {
  457. this.DebugMessage("PresentHook: Copy to System Memory time: " + (DateTime.Now - startCopyToSystemMemory).ToString());
  458. if (_finalRTMapped)
  459. {
  460. lock (_lock)
  461. {
  462. _finalRT.Device.ImmediateContext.UnmapSubresource(_finalRT, 0);
  463. _finalRTMapped = false;
  464. }
  465. }
  466. }
  467. }
  468. catch (SharpDX.SharpDXException exc)
  469. {
  470. // Catch DXGI_ERROR_WAS_STILL_DRAWING and ignore - the data isn't available yet
  471. }
  472. }
  473. }));
  474. // Note: it would be possible to capture multiple frames and process them in a background thread
  475. }
  476. this.DebugMessage("PresentHook: Copy BackBuffer time: " + (DateTime.Now - startTime).ToString());
  477. this.DebugMessage("PresentHook: Request End");
  478. }
  479. #endregion
  480. #region Draw overlay (after screenshot so we don't capture overlay as well)
  481. var displayOverlays = Overlays;
  482. if (this.Config.ShowOverlay && displayOverlays != null)
  483. {
  484. // Initialise Overlay Engine
  485. if (_swapChainPointer != swapChain.NativePointer || _overlayEngine == null
  486. || IsOverlayUpdatePending)
  487. {
  488. if (_overlayEngine != null)
  489. _overlayEngine.Dispose();
  490. _overlayEngine = new DX11.DXOverlayEngine();
  491. _overlayEngine.Overlays.AddRange((IEnumerable<IOverlay>)displayOverlays);
  492. _overlayEngine.Initialise(swapChain);
  493. _swapChainPointer = swapChain.NativePointer;
  494. IsOverlayUpdatePending = false;
  495. }
  496. // Draw Overlay(s)
  497. if (_overlayEngine != null)
  498. {
  499. foreach (var overlay in _overlayEngine.Overlays)
  500. overlay.Frame();
  501. _overlayEngine.Draw();
  502. }
  503. }
  504. #endregion
  505. }
  506. catch (Exception e)
  507. {
  508. // If there is an error we do not want to crash the hooked application, so swallow the exception
  509. this.DebugMessage("PresentHook: Exeception: " + e.GetType().FullName + ": " + e.ToString());
  510. //return unchecked((int)0x8000FFFF); //E_UNEXPECTED
  511. }
  512. // As always we need to call the original method, note that EasyHook will automatically skip the hook and call the original method
  513. // i.e. calling it here will not cause a stack overflow into this function
  514. return DXGISwapChain_PresentHook.Original(swapChainPtr, syncInterval, flags);
  515. }
  516. RenderHookAPI.Hook.DX11.DXOverlayEngine _overlayEngine;
  517. IntPtr _swapChainPointer = IntPtr.Zero;
  518. SharpDX.WIC.ImagingFactory2 wicFactory;
  519. /// <summary>
  520. /// Copies to a stream using WIC. The format is converted if necessary.
  521. /// </summary>
  522. /// <param name="context"></param>
  523. /// <param name="texture"></param>
  524. /// <param name="outputFormat"></param>
  525. /// <param name="stream"></param>
  526. public void ToStream(SharpDX.Direct3D11.DeviceContext context, Texture2D texture, ImageFormat outputFormat, Stream stream)
  527. {
  528. if (wicFactory == null)
  529. wicFactory = ToDispose(new SharpDX.WIC.ImagingFactory2());
  530. DataStream dataStream;
  531. var dataBox = context.MapSubresource(
  532. texture,
  533. 0,
  534. 0,
  535. MapMode.Read,
  536. SharpDX.Direct3D11.MapFlags.None,
  537. out dataStream);
  538. try
  539. {
  540. var dataRectangle = new DataRectangle
  541. {
  542. DataPointer = dataStream.DataPointer,
  543. Pitch = dataBox.RowPitch
  544. };
  545. var format = PixelFormatFromFormat(texture.Description.Format);
  546. if (format == Guid.Empty)
  547. return;
  548. using (var bitmap = new SharpDX.WIC.Bitmap(
  549. wicFactory,
  550. texture.Description.Width,
  551. texture.Description.Height,
  552. format,
  553. dataRectangle))
  554. {
  555. stream.Position = 0;
  556. SharpDX.WIC.BitmapEncoder bitmapEncoder = null;
  557. switch (outputFormat)
  558. {
  559. case ImageFormat.Bitmap:
  560. bitmapEncoder = new SharpDX.WIC.BmpBitmapEncoder(wicFactory, stream);
  561. break;
  562. case ImageFormat.Jpeg:
  563. bitmapEncoder = new SharpDX.WIC.JpegBitmapEncoder(wicFactory, stream);
  564. break;
  565. case ImageFormat.Png:
  566. bitmapEncoder = new SharpDX.WIC.PngBitmapEncoder(wicFactory, stream);
  567. break;
  568. default:
  569. return;
  570. }
  571. try
  572. {
  573. using (var bitmapFrameEncode = new SharpDX.WIC.BitmapFrameEncode(bitmapEncoder))
  574. {
  575. bitmapFrameEncode.Initialize();
  576. bitmapFrameEncode.SetSize(bitmap.Size.Width, bitmap.Size.Height);
  577. var pixelFormat = format;
  578. bitmapFrameEncode.SetPixelFormat(ref pixelFormat);
  579. if (pixelFormat != format)
  580. {
  581. // IWICFormatConverter
  582. using (var converter = new SharpDX.WIC.FormatConverter(wicFactory))
  583. {
  584. if (converter.CanConvert(format, pixelFormat))
  585. {
  586. converter.Initialize(bitmap, SharpDX.WIC.PixelFormat.Format24bppBGR, SharpDX.WIC.BitmapDitherType.None, null, 0, SharpDX.WIC.BitmapPaletteType.MedianCut);
  587. bitmapFrameEncode.SetPixelFormat(ref pixelFormat);
  588. bitmapFrameEncode.WriteSource(converter);
  589. }
  590. else
  591. {
  592. this.DebugMessage(string.Format("Unable to convert Direct3D texture format {0} to a suitable WIC format", texture.Description.Format.ToString()));
  593. return;
  594. }
  595. }
  596. }
  597. else
  598. {
  599. bitmapFrameEncode.WriteSource(bitmap);
  600. }
  601. bitmapFrameEncode.Commit();
  602. bitmapEncoder.Commit();
  603. }
  604. }
  605. finally
  606. {
  607. bitmapEncoder.Dispose();
  608. }
  609. }
  610. }
  611. finally
  612. {
  613. context.UnmapSubresource(texture, 0);
  614. }
  615. }
  616. public static Guid PixelFormatFromFormat(SharpDX.DXGI.Format format)
  617. {
  618. switch (format)
  619. {
  620. case SharpDX.DXGI.Format.R32G32B32A32_Typeless:
  621. case SharpDX.DXGI.Format.R32G32B32A32_Float:
  622. return SharpDX.WIC.PixelFormat.Format128bppRGBAFloat;
  623. case SharpDX.DXGI.Format.R32G32B32A32_UInt:
  624. case SharpDX.DXGI.Format.R32G32B32A32_SInt:
  625. return SharpDX.WIC.PixelFormat.Format128bppRGBAFixedPoint;
  626. case SharpDX.DXGI.Format.R32G32B32_Typeless:
  627. case SharpDX.DXGI.Format.R32G32B32_Float:
  628. return SharpDX.WIC.PixelFormat.Format96bppRGBFloat;
  629. case SharpDX.DXGI.Format.R32G32B32_UInt:
  630. case SharpDX.DXGI.Format.R32G32B32_SInt:
  631. return SharpDX.WIC.PixelFormat.Format96bppRGBFixedPoint;
  632. case SharpDX.DXGI.Format.R16G16B16A16_Typeless:
  633. case SharpDX.DXGI.Format.R16G16B16A16_Float:
  634. case SharpDX.DXGI.Format.R16G16B16A16_UNorm:
  635. case SharpDX.DXGI.Format.R16G16B16A16_UInt:
  636. case SharpDX.DXGI.Format.R16G16B16A16_SNorm:
  637. case SharpDX.DXGI.Format.R16G16B16A16_SInt:
  638. return SharpDX.WIC.PixelFormat.Format64bppRGBA;
  639. case SharpDX.DXGI.Format.R32G32_Typeless:
  640. case SharpDX.DXGI.Format.R32G32_Float:
  641. case SharpDX.DXGI.Format.R32G32_UInt:
  642. case SharpDX.DXGI.Format.R32G32_SInt:
  643. case SharpDX.DXGI.Format.R32G8X24_Typeless:
  644. case SharpDX.DXGI.Format.D32_Float_S8X24_UInt:
  645. case SharpDX.DXGI.Format.R32_Float_X8X24_Typeless:
  646. case SharpDX.DXGI.Format.X32_Typeless_G8X24_UInt:
  647. return Guid.Empty;
  648. case SharpDX.DXGI.Format.R10G10B10A2_Typeless:
  649. case SharpDX.DXGI.Format.R10G10B10A2_UNorm:
  650. case SharpDX.DXGI.Format.R10G10B10A2_UInt:
  651. return SharpDX.WIC.PixelFormat.Format32bppRGBA1010102;
  652. case SharpDX.DXGI.Format.R11G11B10_Float:
  653. return Guid.Empty;
  654. case SharpDX.DXGI.Format.R8G8B8A8_Typeless:
  655. case SharpDX.DXGI.Format.R8G8B8A8_UNorm:
  656. case SharpDX.DXGI.Format.R8G8B8A8_UNorm_SRgb:
  657. case SharpDX.DXGI.Format.R8G8B8A8_UInt:
  658. case SharpDX.DXGI.Format.R8G8B8A8_SNorm:
  659. case SharpDX.DXGI.Format.R8G8B8A8_SInt:
  660. return SharpDX.WIC.PixelFormat.Format32bppRGBA;
  661. case SharpDX.DXGI.Format.R16G16_Typeless:
  662. case SharpDX.DXGI.Format.R16G16_Float:
  663. case SharpDX.DXGI.Format.R16G16_UNorm:
  664. case SharpDX.DXGI.Format.R16G16_UInt:
  665. case SharpDX.DXGI.Format.R16G16_SNorm:
  666. case SharpDX.DXGI.Format.R16G16_SInt:
  667. return Guid.Empty;
  668. case SharpDX.DXGI.Format.R32_Typeless:
  669. case SharpDX.DXGI.Format.D32_Float:
  670. case SharpDX.DXGI.Format.R32_Float:
  671. case SharpDX.DXGI.Format.R32_UInt:
  672. case SharpDX.DXGI.Format.R32_SInt:
  673. return Guid.Empty;
  674. case SharpDX.DXGI.Format.R24G8_Typeless:
  675. case SharpDX.DXGI.Format.D24_UNorm_S8_UInt:
  676. case SharpDX.DXGI.Format.R24_UNorm_X8_Typeless:
  677. return SharpDX.WIC.PixelFormat.Format32bppGrayFloat;
  678. case SharpDX.DXGI.Format.X24_Typeless_G8_UInt:
  679. case SharpDX.DXGI.Format.R9G9B9E5_Sharedexp:
  680. case SharpDX.DXGI.Format.R8G8_B8G8_UNorm:
  681. case SharpDX.DXGI.Format.G8R8_G8B8_UNorm:
  682. return Guid.Empty;
  683. case SharpDX.DXGI.Format.B8G8R8A8_UNorm:
  684. case SharpDX.DXGI.Format.B8G8R8X8_UNorm:
  685. return SharpDX.WIC.PixelFormat.Format32bppBGRA;
  686. case SharpDX.DXGI.Format.R10G10B10_Xr_Bias_A2_UNorm:
  687. return SharpDX.WIC.PixelFormat.Format32bppBGR101010;
  688. case SharpDX.DXGI.Format.B8G8R8A8_Typeless:
  689. case SharpDX.DXGI.Format.B8G8R8A8_UNorm_SRgb:
  690. case SharpDX.DXGI.Format.B8G8R8X8_Typeless:
  691. case SharpDX.DXGI.Format.B8G8R8X8_UNorm_SRgb:
  692. return SharpDX.WIC.PixelFormat.Format32bppBGRA;
  693. case SharpDX.DXGI.Format.R8G8_Typeless:
  694. case SharpDX.DXGI.Format.R8G8_UNorm:
  695. case SharpDX.DXGI.Format.R8G8_UInt:
  696. case SharpDX.DXGI.Format.R8G8_SNorm:
  697. case SharpDX.DXGI.Format.R8G8_SInt:
  698. return Guid.Empty;
  699. case SharpDX.DXGI.Format.R16_Typeless:
  700. case SharpDX.DXGI.Format.R16_Float:
  701. case SharpDX.DXGI.Format.D16_UNorm:
  702. case SharpDX.DXGI.Format.R16_UNorm:
  703. case SharpDX.DXGI.Format.R16_SNorm:
  704. return SharpDX.WIC.PixelFormat.Format16bppGrayHalf;
  705. case SharpDX.DXGI.Format.R16_UInt:
  706. case SharpDX.DXGI.Format.R16_SInt:
  707. return SharpDX.WIC.PixelFormat.Format16bppGrayFixedPoint;
  708. case SharpDX.DXGI.Format.B5G6R5_UNorm:
  709. return SharpDX.WIC.PixelFormat.Format16bppBGR565;
  710. case SharpDX.DXGI.Format.B5G5R5A1_UNorm:
  711. return SharpDX.WIC.PixelFormat.Format16bppBGRA5551;
  712. case SharpDX.DXGI.Format.B4G4R4A4_UNorm:
  713. return Guid.Empty;
  714. case SharpDX.DXGI.Format.R8_Typeless:
  715. case SharpDX.DXGI.Format.R8_UNorm:
  716. case SharpDX.DXGI.Format.R8_UInt:
  717. case SharpDX.DXGI.Format.R8_SNorm:
  718. case SharpDX.DXGI.Format.R8_SInt:
  719. return SharpDX.WIC.PixelFormat.Format8bppGray;
  720. case SharpDX.DXGI.Format.A8_UNorm:
  721. return SharpDX.WIC.PixelFormat.Format8bppAlpha;
  722. case SharpDX.DXGI.Format.R1_UNorm:
  723. return SharpDX.WIC.PixelFormat.Format1bppIndexed;
  724. default:
  725. return Guid.Empty;
  726. }
  727. }
  728. }
  729. }