d3d12-capture.cpp 11 KB

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