dxgi-capture.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #include <d3d10_1.h>
  2. #include <d3d11.h>
  3. #include <dxgi1_2.h>
  4. #include <d3dcompiler.h>
  5. #include <inttypes.h>
  6. #include "graphics-hook.h"
  7. #include <detours.h>
  8. #if COMPILE_D3D12_HOOK
  9. #include <d3d12.h>
  10. #endif
  11. typedef HRESULT(STDMETHODCALLTYPE *resize_buffers_t)(IDXGISwapChain *, UINT, UINT, UINT, DXGI_FORMAT, UINT);
  12. typedef HRESULT(STDMETHODCALLTYPE *present_t)(IDXGISwapChain *, UINT, UINT);
  13. typedef HRESULT(STDMETHODCALLTYPE *present1_t)(IDXGISwapChain1 *, UINT, UINT, const DXGI_PRESENT_PARAMETERS *);
  14. resize_buffers_t RealResizeBuffers = nullptr;
  15. present_t RealPresent = nullptr;
  16. present1_t RealPresent1 = nullptr;
  17. thread_local int dxgi_presenting = 0;
  18. struct ID3D12CommandQueue *dxgi_possible_swap_queues[8]{};
  19. size_t dxgi_possible_swap_queue_count;
  20. bool dxgi_present_attempted = false;
  21. struct dxgi_swap_data {
  22. IDXGISwapChain *swap;
  23. void (*capture)(void *, void *);
  24. void (*free)(void);
  25. };
  26. static struct dxgi_swap_data data = {};
  27. static int swap_chain_mismatch_count = 0;
  28. constexpr int swap_chain_mismtach_limit = 16;
  29. static void STDMETHODCALLTYPE SwapChainDestructed(void *pData)
  30. {
  31. if (pData == data.swap) {
  32. data.swap = nullptr;
  33. data.capture = nullptr;
  34. memset(dxgi_possible_swap_queues, 0, sizeof(dxgi_possible_swap_queues));
  35. dxgi_possible_swap_queue_count = 0;
  36. dxgi_present_attempted = false;
  37. if (data.free)
  38. data.free();
  39. data.free = nullptr;
  40. }
  41. }
  42. static void init_swap_data(IDXGISwapChain *swap, void (*capture)(void *, void *), void (*free)(void))
  43. {
  44. data.swap = swap;
  45. data.capture = capture;
  46. data.free = free;
  47. ID3DDestructionNotifier *notifier;
  48. if (SUCCEEDED(swap->QueryInterface<ID3DDestructionNotifier>(&notifier))) {
  49. UINT callbackID;
  50. notifier->RegisterDestructionCallback(&SwapChainDestructed, swap, &callbackID);
  51. notifier->Release();
  52. }
  53. }
  54. static bool setup_dxgi(IDXGISwapChain *swap)
  55. {
  56. IUnknown *device;
  57. HRESULT hr;
  58. hr = swap->GetDevice(__uuidof(ID3D11Device), (void **)&device);
  59. if (SUCCEEDED(hr)) {
  60. ID3D11Device *d3d11 = static_cast<ID3D11Device *>(device);
  61. D3D_FEATURE_LEVEL level = d3d11->GetFeatureLevel();
  62. device->Release();
  63. if (level >= D3D_FEATURE_LEVEL_11_0) {
  64. hlog("Found D3D11 11.0 device on swap chain");
  65. init_swap_data(swap, d3d11_capture, d3d11_free);
  66. return true;
  67. }
  68. }
  69. hr = swap->GetDevice(__uuidof(ID3D10Device), (void **)&device);
  70. if (SUCCEEDED(hr)) {
  71. device->Release();
  72. hlog("Found D3D10 device on swap chain");
  73. init_swap_data(swap, d3d10_capture, d3d10_free);
  74. return true;
  75. }
  76. hr = swap->GetDevice(__uuidof(ID3D11Device), (void **)&device);
  77. if (SUCCEEDED(hr)) {
  78. device->Release();
  79. hlog("Found D3D11 device on swap chain");
  80. init_swap_data(swap, d3d11_capture, d3d11_free);
  81. return true;
  82. }
  83. #if COMPILE_D3D12_HOOK
  84. hr = swap->GetDevice(__uuidof(ID3D12Device), (void **)&device);
  85. if (SUCCEEDED(hr)) {
  86. device->Release();
  87. hlog("Found D3D12 device on swap chain: swap=0x%" PRIX64 ", device=0x%" PRIX64,
  88. (uint64_t)(uintptr_t)swap, (uint64_t)(uintptr_t)device);
  89. for (size_t i = 0; i < dxgi_possible_swap_queue_count; ++i) {
  90. hlog(" queue=0x%" PRIX64, (uint64_t)(uintptr_t)dxgi_possible_swap_queues[i]);
  91. }
  92. if (dxgi_possible_swap_queue_count > 0) {
  93. init_swap_data(swap, d3d12_capture, d3d12_free);
  94. return true;
  95. }
  96. }
  97. #endif
  98. hlog_verbose("Failed to setup DXGI");
  99. return false;
  100. }
  101. static bool resize_buffers_called = false;
  102. static HRESULT STDMETHODCALLTYPE hook_resize_buffers(IDXGISwapChain *swap, UINT buffer_count, UINT width, UINT height,
  103. DXGI_FORMAT format, UINT flags)
  104. {
  105. hlog_verbose("ResizeBuffers callback");
  106. data.swap = nullptr;
  107. data.capture = nullptr;
  108. memset(dxgi_possible_swap_queues, 0, sizeof(dxgi_possible_swap_queues));
  109. dxgi_possible_swap_queue_count = 0;
  110. dxgi_present_attempted = false;
  111. if (data.free)
  112. data.free();
  113. data.free = nullptr;
  114. const HRESULT hr = RealResizeBuffers(swap, buffer_count, width, height, format, flags);
  115. resize_buffers_called = true;
  116. return hr;
  117. }
  118. static inline IUnknown *get_dxgi_backbuffer(IDXGISwapChain *swap)
  119. {
  120. IUnknown *res = nullptr;
  121. const HRESULT hr = swap->GetBuffer(0, IID_PPV_ARGS(&res));
  122. if (FAILED(hr))
  123. hlog_hr("get_dxgi_backbuffer: GetBuffer failed", hr);
  124. return res;
  125. }
  126. static void update_mismatch_count(bool match)
  127. {
  128. if (match) {
  129. swap_chain_mismatch_count = 0;
  130. } else {
  131. ++swap_chain_mismatch_count;
  132. if (swap_chain_mismatch_count == swap_chain_mismtach_limit) {
  133. data.swap = nullptr;
  134. data.capture = nullptr;
  135. memset(dxgi_possible_swap_queues, 0, sizeof(dxgi_possible_swap_queues));
  136. dxgi_possible_swap_queue_count = 0;
  137. dxgi_present_attempted = false;
  138. if (data.free)
  139. data.free();
  140. data.free = nullptr;
  141. swap_chain_mismatch_count = 0;
  142. }
  143. }
  144. }
  145. static HRESULT STDMETHODCALLTYPE hook_present(IDXGISwapChain *swap, UINT sync_interval, UINT flags)
  146. {
  147. if (should_passthrough()) {
  148. dxgi_presenting = true;
  149. const HRESULT hr = RealPresent(swap, sync_interval, flags);
  150. dxgi_presenting = false;
  151. return hr;
  152. }
  153. const bool capture_overlay = global_hook_info->capture_overlay;
  154. const bool test_draw = (flags & DXGI_PRESENT_TEST) != 0;
  155. if (data.swap) {
  156. update_mismatch_count(swap == data.swap);
  157. }
  158. if (!data.swap && !capture_active()) {
  159. setup_dxgi(swap);
  160. }
  161. hlog_verbose("Present callback: sync_interval=%u, flags=%u, current_swap=0x%" PRIX64
  162. ", expected_swap=0x%" PRIX64,
  163. sync_interval, flags, swap, data.swap);
  164. const bool capture = !test_draw && swap == data.swap && data.capture;
  165. if (capture && !capture_overlay) {
  166. IUnknown *backbuffer = get_dxgi_backbuffer(swap);
  167. if (backbuffer) {
  168. data.capture(swap, backbuffer);
  169. backbuffer->Release();
  170. }
  171. }
  172. ++dxgi_presenting;
  173. const HRESULT hr = RealPresent(swap, sync_interval, flags);
  174. --dxgi_presenting;
  175. dxgi_present_attempted = true;
  176. if (capture && capture_overlay) {
  177. /*
  178. * It seems that the first call to Present after ResizeBuffers
  179. * will cause the backbuffer to be invalidated, so do not
  180. * perform the post-overlay capture if ResizeBuffers has
  181. * recently been called. (The backbuffer returned by
  182. * get_dxgi_backbuffer *will* be invalid otherwise)
  183. */
  184. if (resize_buffers_called) {
  185. resize_buffers_called = false;
  186. } else {
  187. IUnknown *backbuffer = get_dxgi_backbuffer(swap);
  188. if (backbuffer) {
  189. data.capture(swap, backbuffer);
  190. backbuffer->Release();
  191. }
  192. }
  193. }
  194. return hr;
  195. }
  196. static HRESULT STDMETHODCALLTYPE hook_present1(IDXGISwapChain1 *swap, UINT sync_interval, UINT flags,
  197. const DXGI_PRESENT_PARAMETERS *params)
  198. {
  199. if (should_passthrough()) {
  200. dxgi_presenting = true;
  201. const HRESULT hr = RealPresent1(swap, sync_interval, flags, params);
  202. dxgi_presenting = false;
  203. return hr;
  204. }
  205. const bool capture_overlay = global_hook_info->capture_overlay;
  206. const bool test_draw = (flags & DXGI_PRESENT_TEST) != 0;
  207. if (data.swap) {
  208. update_mismatch_count(swap == data.swap);
  209. }
  210. if (!data.swap && !capture_active()) {
  211. setup_dxgi(swap);
  212. }
  213. hlog_verbose("Present1 callback: sync_interval=%u, flags=%u, current_swap=0x%" PRIX64
  214. ", expected_swap=0x%" PRIX64,
  215. sync_interval, flags, swap, data.swap);
  216. const bool capture = !test_draw && swap == data.swap && !!data.capture;
  217. if (capture && !capture_overlay) {
  218. IUnknown *backbuffer = get_dxgi_backbuffer(swap);
  219. if (backbuffer) {
  220. data.capture(swap, backbuffer);
  221. backbuffer->Release();
  222. }
  223. }
  224. ++dxgi_presenting;
  225. const HRESULT hr = RealPresent1(swap, sync_interval, flags, params);
  226. --dxgi_presenting;
  227. dxgi_present_attempted = true;
  228. if (capture && capture_overlay) {
  229. if (resize_buffers_called) {
  230. resize_buffers_called = false;
  231. } else {
  232. IUnknown *backbuffer = get_dxgi_backbuffer(swap);
  233. if (backbuffer) {
  234. data.capture(swap, backbuffer);
  235. backbuffer->Release();
  236. }
  237. }
  238. }
  239. return hr;
  240. }
  241. bool hook_dxgi(void)
  242. {
  243. HMODULE dxgi_module = get_system_module("dxgi.dll");
  244. if (!dxgi_module) {
  245. hlog_verbose("Failed to find dxgi.dll. Skipping hook attempt.");
  246. return false;
  247. }
  248. /* ---------------------- */
  249. void *present_addr = get_offset_addr(dxgi_module, global_hook_info->offsets.dxgi.present);
  250. void *resize_addr = get_offset_addr(dxgi_module, global_hook_info->offsets.dxgi.resize);
  251. void *present1_addr = nullptr;
  252. if (global_hook_info->offsets.dxgi.present1)
  253. present1_addr = get_offset_addr(dxgi_module, global_hook_info->offsets.dxgi.present1);
  254. DetourTransactionBegin();
  255. RealPresent = (present_t)present_addr;
  256. DetourAttach(&(PVOID &)RealPresent, hook_present);
  257. RealResizeBuffers = (resize_buffers_t)resize_addr;
  258. DetourAttach(&(PVOID &)RealResizeBuffers, hook_resize_buffers);
  259. if (present1_addr) {
  260. RealPresent1 = (present1_t)present1_addr;
  261. DetourAttach(&(PVOID &)RealPresent1, hook_present1);
  262. }
  263. const LONG error = DetourTransactionCommit();
  264. const bool success = error == NO_ERROR;
  265. if (success) {
  266. hlog("Hooked IDXGISwapChain::Present");
  267. hlog("Hooked IDXGISwapChain::ResizeBuffers");
  268. if (RealPresent1)
  269. hlog("Hooked IDXGISwapChain1::Present1");
  270. hlog("Hooked DXGI");
  271. } else {
  272. RealPresent = nullptr;
  273. RealResizeBuffers = nullptr;
  274. RealPresent1 = nullptr;
  275. hlog("Failed to attach Detours hook: %ld", error);
  276. }
  277. return success;
  278. }