d3d12-capture.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. #include <windows.h>
  2. #include "graphics-hook.h"
  3. #if COMPILE_D3D12_HOOK
  4. #include <d3d11on12.h>
  5. #include <d3d12.h>
  6. #include <dxgi1_4.h>
  7. #include <inttypes.h>
  8. #include <detours.h>
  9. #include "dxgi-helpers.hpp"
  10. #define MAX_BACKBUFFERS 8
  11. typedef HRESULT(STDMETHODCALLTYPE *PFN_ExecuteCommandLists)(ID3D12CommandQueue *, UINT, ID3D12CommandList *const *);
  12. static PFN_ExecuteCommandLists RealExecuteCommandLists = nullptr;
  13. struct d3d12_data {
  14. uint32_t cx;
  15. uint32_t cy;
  16. DXGI_FORMAT format;
  17. bool using_shtex;
  18. bool multisampled;
  19. bool dxgi_1_4;
  20. ID3D11Device *device11;
  21. ID3D11DeviceContext *context11;
  22. ID3D11On12Device *device11on12;
  23. union {
  24. struct {
  25. struct shtex_data *shtex_info;
  26. UINT backbuffer_count;
  27. UINT cur_backbuffer;
  28. ID3D11Texture2D *copy_tex;
  29. HANDLE handle;
  30. };
  31. };
  32. };
  33. static struct d3d12_data data = {};
  34. extern thread_local int dxgi_presenting;
  35. extern ID3D12CommandQueue *dxgi_possible_swap_queues[8];
  36. extern size_t dxgi_possible_swap_queue_count;
  37. extern bool dxgi_present_attempted;
  38. void d3d12_free(void)
  39. {
  40. if (data.copy_tex)
  41. data.copy_tex->Release();
  42. if (data.device11)
  43. data.device11->Release();
  44. if (data.context11)
  45. data.context11->Release();
  46. if (data.device11on12)
  47. data.device11on12->Release();
  48. capture_free();
  49. memset(&data, 0, sizeof(data));
  50. hlog("----------------- d3d12 capture freed ----------------");
  51. }
  52. static bool create_d3d12_tex(UINT count)
  53. {
  54. HRESULT hr;
  55. if (count == 0)
  56. return false;
  57. data.backbuffer_count = count;
  58. D3D11_TEXTURE2D_DESC desc11 = {};
  59. desc11.Width = data.cx;
  60. desc11.Height = data.cy;
  61. desc11.MipLevels = 1;
  62. desc11.ArraySize = 1;
  63. desc11.Format = apply_dxgi_format_typeless(data.format, global_hook_info->allow_srgb_alias);
  64. desc11.SampleDesc.Count = 1;
  65. desc11.BindFlags = D3D11_BIND_SHADER_RESOURCE;
  66. desc11.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
  67. hr = data.device11->CreateTexture2D(&desc11, nullptr, &data.copy_tex);
  68. if (FAILED(hr)) {
  69. hlog_hr("create_d3d12_tex: creation of d3d11 copy tex failed", hr);
  70. return false;
  71. }
  72. IDXGIResource *dxgi_res;
  73. hr = data.copy_tex->QueryInterface(&dxgi_res);
  74. if (FAILED(hr)) {
  75. hlog_hr("create_d3d12_tex: failed to query "
  76. "IDXGIResource interface from texture",
  77. hr);
  78. return false;
  79. }
  80. hr = dxgi_res->GetSharedHandle(&data.handle);
  81. dxgi_res->Release();
  82. if (FAILED(hr)) {
  83. hlog_hr("create_d3d12_tex: failed to get shared handle", hr);
  84. return false;
  85. }
  86. return true;
  87. }
  88. static bool d3d12_init_11on12(ID3D12Device *device)
  89. {
  90. static HMODULE d3d11 = nullptr;
  91. static PFN_D3D11ON12_CREATE_DEVICE create_11_on_12 = nullptr;
  92. static bool initialized_11 = false;
  93. static bool initialized_func = false;
  94. if (!initialized_11 && !d3d11) {
  95. d3d11 = load_system_library("d3d11.dll");
  96. if (!d3d11) {
  97. hlog("d3d12_init_11on12: failed to load d3d11");
  98. }
  99. initialized_11 = true;
  100. }
  101. if (!d3d11) {
  102. return false;
  103. }
  104. if (!initialized_func && !create_11_on_12) {
  105. create_11_on_12 = (PFN_D3D11ON12_CREATE_DEVICE)GetProcAddress(d3d11, "D3D11On12CreateDevice");
  106. if (!create_11_on_12) {
  107. hlog("d3d12_init_11on12: Failed to get "
  108. "D3D11On12CreateDevice address");
  109. }
  110. initialized_func = true;
  111. }
  112. if (!create_11_on_12) {
  113. return false;
  114. }
  115. bool created = false;
  116. for (size_t i = 0; i < dxgi_possible_swap_queue_count; ++i) {
  117. hlog("d3d12_init_11on12: creating 11 device: queue=0x%" PRIX64,
  118. (uint64_t)(uintptr_t)dxgi_possible_swap_queues[i]);
  119. IUnknown *const queue = dxgi_possible_swap_queues[i];
  120. const HRESULT hr =
  121. create_11_on_12(device, 0, nullptr, 0, &queue, 1, 0, &data.device11, &data.context11, nullptr);
  122. created = SUCCEEDED(hr);
  123. if (created) {
  124. break;
  125. }
  126. hlog_hr("d3d12_init_11on12: failed to create 11 device", hr);
  127. }
  128. if (!created) {
  129. return false;
  130. }
  131. memset(dxgi_possible_swap_queues, 0, sizeof(dxgi_possible_swap_queues));
  132. dxgi_possible_swap_queue_count = 0;
  133. dxgi_present_attempted = false;
  134. const HRESULT hr = data.device11->QueryInterface(IID_PPV_ARGS(&data.device11on12));
  135. if (FAILED(hr)) {
  136. hlog_hr("d3d12_init_11on12: failed to query 11on12 device", hr);
  137. return false;
  138. }
  139. return true;
  140. }
  141. static bool d3d12_shtex_init(ID3D12Device *device, HWND window, UINT count)
  142. {
  143. if (!d3d12_init_11on12(device)) {
  144. return false;
  145. }
  146. if (!create_d3d12_tex(count)) {
  147. return false;
  148. }
  149. if (!capture_init_shtex(&data.shtex_info, window, data.cx, data.cy, data.format, false,
  150. (uintptr_t)data.handle)) {
  151. return false;
  152. }
  153. hlog("d3d12 shared texture capture successful");
  154. return true;
  155. }
  156. static inline UINT d3d12_init_format(IDXGISwapChain *swap, HWND &window)
  157. {
  158. DXGI_SWAP_CHAIN_DESC desc;
  159. IDXGISwapChain3 *swap3;
  160. HRESULT hr;
  161. hr = swap->GetDesc(&desc);
  162. if (FAILED(hr)) {
  163. hlog_hr("d3d12_init_format: swap->GetDesc failed", hr);
  164. return 0;
  165. }
  166. print_swap_desc(&desc);
  167. data.format = strip_dxgi_format_srgb(desc.BufferDesc.Format);
  168. data.multisampled = desc.SampleDesc.Count > 1;
  169. window = desc.OutputWindow;
  170. data.cx = desc.BufferDesc.Width;
  171. data.cy = desc.BufferDesc.Height;
  172. hr = swap->QueryInterface(&swap3);
  173. if (SUCCEEDED(hr)) {
  174. data.dxgi_1_4 = true;
  175. hlog("We're DXGI1.4 boys!");
  176. swap3->Release();
  177. }
  178. UINT count = desc.SwapEffect == DXGI_SWAP_EFFECT_DISCARD ? 1 : desc.BufferCount;
  179. if (count == 1)
  180. data.dxgi_1_4 = false;
  181. if (count > MAX_BACKBUFFERS) {
  182. hlog("Somehow it's using more than the max backbuffers. "
  183. "Not sure why anyone would do that.");
  184. count = 1;
  185. data.dxgi_1_4 = false;
  186. }
  187. return count;
  188. }
  189. static void d3d12_init(IDXGISwapChain *swap)
  190. {
  191. ID3D12Device *device = nullptr;
  192. const HRESULT hr = swap->GetDevice(IID_PPV_ARGS(&device));
  193. if (SUCCEEDED(hr)) {
  194. hlog("d3d12_init: device=0x%" PRIX64, (uint64_t)(uintptr_t)device);
  195. HWND window;
  196. UINT count = d3d12_init_format(swap, window);
  197. if (count > 0) {
  198. if (global_hook_info->force_shmem) {
  199. hlog("d3d12_init: shared memory capture currently "
  200. "unsupported; ignoring");
  201. }
  202. if (!d3d12_shtex_init(device, window, count))
  203. d3d12_free();
  204. }
  205. device->Release();
  206. } else {
  207. hlog_hr("d3d12_init: failed to get device from swap", hr);
  208. }
  209. }
  210. static inline void d3d12_copy_texture(ID3D11Resource *dst, ID3D11Resource *src)
  211. {
  212. if (data.multisampled) {
  213. data.context11->ResolveSubresource(dst, 0, src, 0, data.format);
  214. } else {
  215. data.context11->CopyResource(dst, src);
  216. }
  217. }
  218. static inline void d3d12_shtex_capture(IDXGISwapChain *swap)
  219. {
  220. if (!data.device11on12) {
  221. return;
  222. }
  223. bool dxgi_1_4 = data.dxgi_1_4;
  224. UINT cur_idx;
  225. if (dxgi_1_4) {
  226. IDXGISwapChain3 *swap3 = reinterpret_cast<IDXGISwapChain3 *>(swap);
  227. cur_idx = swap3->GetCurrentBackBufferIndex();
  228. } else {
  229. cur_idx = data.cur_backbuffer;
  230. }
  231. ID3D12Resource *backbuffer12;
  232. if (SUCCEEDED(swap->GetBuffer(cur_idx, IID_PPV_ARGS(&backbuffer12)))) {
  233. D3D11_RESOURCE_FLAGS rf11 = {};
  234. ID3D11Resource *backbuffer;
  235. if (SUCCEEDED(data.device11on12->CreateWrappedResource(
  236. backbuffer12, &rf11, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_PRESENT,
  237. IID_PPV_ARGS(&backbuffer)))) {
  238. data.device11on12->AcquireWrappedResources(&backbuffer, 1);
  239. d3d12_copy_texture(data.copy_tex, backbuffer);
  240. data.device11on12->ReleaseWrappedResources(&backbuffer, 1);
  241. data.context11->Flush();
  242. if (!dxgi_1_4) {
  243. if (++data.cur_backbuffer >= data.backbuffer_count)
  244. data.cur_backbuffer = 0;
  245. }
  246. backbuffer->Release();
  247. }
  248. backbuffer12->Release();
  249. }
  250. }
  251. void d3d12_capture(void *swap_ptr, void *)
  252. {
  253. IDXGISwapChain *swap = (IDXGISwapChain *)swap_ptr;
  254. if (capture_should_stop()) {
  255. d3d12_free();
  256. }
  257. if (capture_should_init()) {
  258. d3d12_init(swap);
  259. }
  260. if (data.handle != nullptr && capture_ready()) {
  261. d3d12_shtex_capture(swap);
  262. }
  263. }
  264. static bool try_append_queue_if_unique(ID3D12CommandQueue *queue)
  265. {
  266. for (size_t i = 0; i < dxgi_possible_swap_queue_count; ++i) {
  267. if (dxgi_possible_swap_queues[i] == queue)
  268. return false;
  269. }
  270. dxgi_possible_swap_queues[dxgi_possible_swap_queue_count] = queue;
  271. ++dxgi_possible_swap_queue_count;
  272. return true;
  273. }
  274. static HRESULT STDMETHODCALLTYPE hook_execute_command_lists(ID3D12CommandQueue *queue, UINT NumCommandLists,
  275. ID3D12CommandList *const *ppCommandLists)
  276. {
  277. hlog_verbose("ExecuteCommandLists callback: queue=0x%" PRIX64, (uint64_t)(uintptr_t)queue);
  278. if (dxgi_possible_swap_queue_count < _countof(dxgi_possible_swap_queues)) {
  279. if ((dxgi_presenting > 0) && (queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_DIRECT)) {
  280. if (try_append_queue_if_unique(queue)) {
  281. hlog("Remembering D3D12 queue from present: queue=0x%" PRIX64,
  282. (uint64_t)(uintptr_t)queue);
  283. }
  284. } else if (dxgi_present_attempted && (queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_DIRECT)) {
  285. if (try_append_queue_if_unique(queue)) {
  286. hlog("Remembering D3D12 queue from first direct submit after present: queue=0x%" PRIX64,
  287. (uint64_t)(uintptr_t)queue);
  288. }
  289. } else {
  290. hlog_verbose("Ignoring D3D12 queue=0x%" PRIX64, (uint64_t)(uintptr_t)queue);
  291. }
  292. }
  293. return RealExecuteCommandLists(queue, NumCommandLists, ppCommandLists);
  294. }
  295. static bool manually_get_d3d12_addrs(HMODULE d3d12_module, PFN_ExecuteCommandLists *execute_command_lists_addr)
  296. {
  297. PFN_D3D12_CREATE_DEVICE create = (PFN_D3D12_CREATE_DEVICE)GetProcAddress(d3d12_module, "D3D12CreateDevice");
  298. if (!create) {
  299. hlog("Failed to load D3D12CreateDevice");
  300. return false;
  301. }
  302. bool success = false;
  303. ID3D12Device *device;
  304. if (SUCCEEDED(create(NULL, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device)))) {
  305. D3D12_COMMAND_QUEUE_DESC desc{};
  306. ID3D12CommandQueue *queue;
  307. HRESULT hr = device->CreateCommandQueue(&desc, IID_PPV_ARGS(&queue));
  308. success = SUCCEEDED(hr);
  309. if (success) {
  310. void **queue_vtable = *(void ***)queue;
  311. *execute_command_lists_addr = (PFN_ExecuteCommandLists)queue_vtable[10];
  312. queue->Release();
  313. } else {
  314. hlog("Failed to create D3D12 command queue");
  315. }
  316. device->Release();
  317. } else {
  318. hlog("Failed to create D3D12 device");
  319. }
  320. return success;
  321. }
  322. bool hook_d3d12(void)
  323. {
  324. HMODULE d3d12_module = get_system_module("d3d12.dll");
  325. if (!d3d12_module) {
  326. hlog_verbose("Failed to find d3d12.dll. Skipping hook attempt.");
  327. return false;
  328. }
  329. PFN_ExecuteCommandLists execute_command_lists_addr = nullptr;
  330. if (!manually_get_d3d12_addrs(d3d12_module, &execute_command_lists_addr)) {
  331. hlog("Failed to get D3D12 values");
  332. return true;
  333. }
  334. if (!execute_command_lists_addr) {
  335. hlog("Invalid D3D12 values");
  336. return true;
  337. }
  338. DetourTransactionBegin();
  339. RealExecuteCommandLists = execute_command_lists_addr;
  340. DetourAttach(&(PVOID &)RealExecuteCommandLists, hook_execute_command_lists);
  341. const LONG error = DetourTransactionCommit();
  342. const bool success = error == NO_ERROR;
  343. if (success) {
  344. hlog("Hooked ID3D12CommandQueue::ExecuteCommandLists");
  345. hlog("Hooked D3D12");
  346. } else {
  347. RealExecuteCommandLists = nullptr;
  348. hlog("Failed to attach Detours hook: %ld", error);
  349. }
  350. return success;
  351. }
  352. #endif