winrt-capture.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. extern "C" {
  2. HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
  3. ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
  4. HRESULT __stdcall CreateDirect3D11SurfaceFromDXGISurface(
  5. ::IDXGISurface *dgxiSurface, ::IInspectable **graphicsSurface);
  6. }
  7. struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
  8. IDirect3DDxgiInterfaceAccess : ::IUnknown {
  9. virtual HRESULT __stdcall GetInterface(GUID const &id,
  10. void **object) = 0;
  11. };
  12. extern "C" EXPORT bool winrt_capture_supported()
  13. {
  14. /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
  15. return winrt::Windows::Foundation::Metadata::ApiInformation::
  16. IsApiContractPresent(L"Windows.Foundation.UniversalApiContract",
  17. 8);
  18. }
  19. template<typename T>
  20. static winrt::com_ptr<T> GetDXGIInterfaceFromObject(
  21. winrt::Windows::Foundation::IInspectable const &object)
  22. {
  23. auto access = object.as<IDirect3DDxgiInterfaceAccess>();
  24. winrt::com_ptr<T> result;
  25. winrt::check_hresult(
  26. access->GetInterface(winrt::guid_of<T>(), result.put_void()));
  27. return result;
  28. }
  29. struct winrt_capture {
  30. bool capture_cursor;
  31. gs_texture_t *texture;
  32. bool texture_written;
  33. winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
  34. winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice device{
  35. nullptr};
  36. ComPtr<ID3D11DeviceContext> context;
  37. winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool{
  38. nullptr};
  39. winrt::Windows::Graphics::Capture::GraphicsCaptureSession session{
  40. nullptr};
  41. winrt::Windows::Graphics::SizeInt32 last_size;
  42. winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
  43. FrameArrived_revoker frame_arrived;
  44. bool thread_changed;
  45. struct winrt_capture *next;
  46. void on_frame_arrived(winrt::Windows::Graphics::Capture::
  47. Direct3D11CaptureFramePool const &sender,
  48. winrt::Windows::Foundation::IInspectable const &)
  49. {
  50. obs_enter_graphics();
  51. const winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame
  52. frame = sender.TryGetNextFrame();
  53. const winrt::Windows::Graphics::SizeInt32 frame_content_size =
  54. frame.ContentSize();
  55. winrt::com_ptr<ID3D11Texture2D> frame_surface =
  56. GetDXGIInterfaceFromObject<ID3D11Texture2D>(
  57. frame.Surface());
  58. /* need GetDesc because ContentSize is not reliable */
  59. D3D11_TEXTURE2D_DESC desc;
  60. frame_surface->GetDesc(&desc);
  61. if (texture) {
  62. if (desc.Width != gs_texture_get_width(texture) ||
  63. desc.Height != gs_texture_get_height(texture)) {
  64. gs_texture_destroy(texture);
  65. texture = nullptr;
  66. }
  67. }
  68. if (!texture) {
  69. texture = gs_texture_create(desc.Width, desc.Height,
  70. GS_BGRA, 1, nullptr, 0);
  71. }
  72. /* if they gave an SRV, we could avoid this copy */
  73. context->CopyResource(
  74. (ID3D11Texture2D *)gs_texture_get_obj(texture),
  75. frame_surface.get());
  76. texture_written = true;
  77. if (frame_content_size.Width != last_size.Width ||
  78. frame_content_size.Height != last_size.Height) {
  79. frame_pool.Recreate(
  80. device,
  81. winrt::Windows::Graphics::DirectX::
  82. DirectXPixelFormat::B8G8R8A8UIntNormalized,
  83. 2, frame_content_size);
  84. last_size = frame_content_size;
  85. }
  86. obs_leave_graphics();
  87. }
  88. };
  89. struct winrt_capture *capture_list;
  90. static void winrt_capture_device_loss_release(void *data)
  91. {
  92. winrt_capture *capture = static_cast<winrt_capture *>(data);
  93. capture->frame_arrived.revoke();
  94. capture->frame_pool.Close();
  95. capture->session.Close();
  96. capture->session = nullptr;
  97. capture->frame_pool = nullptr;
  98. capture->context = nullptr;
  99. capture->device = nullptr;
  100. }
  101. static void winrt_capture_device_loss_rebuild(void *device_void, void *data)
  102. {
  103. winrt_capture *capture = static_cast<winrt_capture *>(data);
  104. ID3D11Device *const d3d_device = (ID3D11Device *)device_void;
  105. ComPtr<IDXGIDevice> dxgi_device;
  106. if (FAILED(d3d_device->QueryInterface(&dxgi_device)))
  107. blog(LOG_ERROR, "Failed to get DXGI device");
  108. winrt::com_ptr<IInspectable> inspectable;
  109. if (FAILED(CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(),
  110. inspectable.put())))
  111. blog(LOG_ERROR, "Failed to get WinRT device");
  112. const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice
  113. device = inspectable.as<winrt::Windows::Graphics::DirectX::
  114. Direct3D11::IDirect3DDevice>();
  115. const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool
  116. frame_pool = winrt::Windows::Graphics::Capture::
  117. Direct3D11CaptureFramePool::Create(
  118. device,
  119. winrt::Windows::Graphics::DirectX::
  120. DirectXPixelFormat::B8G8R8A8UIntNormalized,
  121. 2, capture->last_size);
  122. const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session =
  123. frame_pool.CreateCaptureSession(capture->item);
  124. capture->device = device;
  125. d3d_device->GetImmediateContext(&capture->context);
  126. capture->frame_pool = frame_pool;
  127. capture->session = session;
  128. capture->frame_arrived = frame_pool.FrameArrived(
  129. winrt::auto_revoke,
  130. {capture, &winrt_capture::on_frame_arrived});
  131. session.StartCapture();
  132. }
  133. thread_local bool initialized_tls;
  134. extern "C" EXPORT struct winrt_capture *winrt_capture_init(bool cursor,
  135. HWND window)
  136. {
  137. ID3D11Device *const d3d_device = (ID3D11Device *)gs_get_device_obj();
  138. ComPtr<IDXGIDevice> dxgi_device;
  139. if (FAILED(d3d_device->QueryInterface(&dxgi_device))) {
  140. blog(LOG_WARNING, "[winrt_capture_init] Failed to "
  141. "get DXGI device");
  142. return nullptr;
  143. }
  144. winrt::com_ptr<IInspectable> inspectable;
  145. HRESULT hr = CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(),
  146. inspectable.put());
  147. if (FAILED(hr)) {
  148. blog(LOG_WARNING, "[winrt_capture_init] Failed to "
  149. "get WinRT device");
  150. return nullptr;
  151. }
  152. auto activation_factory = winrt::get_activation_factory<
  153. winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
  154. auto interop_factory =
  155. activation_factory.as<IGraphicsCaptureItemInterop>();
  156. winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
  157. try {
  158. interop_factory->CreateForWindow(
  159. window,
  160. winrt::guid_of<ABI::Windows::Graphics::Capture::
  161. IGraphicsCaptureItem>(),
  162. reinterpret_cast<void **>(winrt::put_abi(item)));
  163. } catch (winrt::hresult_invalid_argument &) {
  164. blog(LOG_WARNING, "[winrt_capture_init] Failed to "
  165. "create GraphicsCaptureItem");
  166. return nullptr;
  167. }
  168. const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice
  169. device = inspectable.as<winrt::Windows::Graphics::DirectX::
  170. Direct3D11::IDirect3DDevice>();
  171. const winrt::Windows::Graphics::SizeInt32 size = item.Size();
  172. const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool
  173. frame_pool = winrt::Windows::Graphics::Capture::
  174. Direct3D11CaptureFramePool::Create(
  175. device,
  176. winrt::Windows::Graphics::DirectX::
  177. DirectXPixelFormat::B8G8R8A8UIntNormalized,
  178. 2, size);
  179. const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session =
  180. frame_pool.CreateCaptureSession(item);
  181. if (capture_list == nullptr)
  182. initialized_tls = true;
  183. struct winrt_capture *capture = new winrt_capture{};
  184. capture->capture_cursor = cursor;
  185. capture->item = item;
  186. capture->device = device;
  187. d3d_device->GetImmediateContext(&capture->context);
  188. capture->frame_pool = frame_pool;
  189. capture->session = session;
  190. capture->last_size = size;
  191. capture->frame_arrived = frame_pool.FrameArrived(
  192. winrt::auto_revoke,
  193. {capture, &winrt_capture::on_frame_arrived});
  194. capture->next = capture_list;
  195. capture_list = capture;
  196. session.StartCapture();
  197. gs_device_loss callbacks;
  198. callbacks.device_loss_release = winrt_capture_device_loss_release;
  199. callbacks.device_loss_rebuild = winrt_capture_device_loss_rebuild;
  200. callbacks.data = capture;
  201. gs_register_loss_callbacks(&callbacks);
  202. return capture;
  203. }
  204. extern "C" EXPORT void winrt_capture_free(struct winrt_capture *capture)
  205. {
  206. if (capture) {
  207. struct winrt_capture *current = capture_list;
  208. if (current == capture) {
  209. capture_list = capture->next;
  210. } else {
  211. struct winrt_capture *previous;
  212. do {
  213. previous = current;
  214. current = current->next;
  215. } while (current != capture);
  216. previous->next = current->next;
  217. }
  218. obs_enter_graphics();
  219. gs_unregister_loss_callbacks(capture);
  220. gs_texture_destroy(capture->texture);
  221. obs_leave_graphics();
  222. capture->frame_arrived.revoke();
  223. capture->frame_pool.Close();
  224. capture->session.Close();
  225. delete capture;
  226. }
  227. }
  228. static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect)
  229. {
  230. gs_texture_t *const texture = capture->texture;
  231. gs_technique_t *tech = gs_effect_get_technique(effect, "Draw");
  232. gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
  233. size_t passes;
  234. gs_effect_set_texture(image, texture);
  235. passes = gs_technique_begin(tech);
  236. for (size_t i = 0; i < passes; i++) {
  237. if (gs_technique_begin_pass(tech, i)) {
  238. gs_draw_sprite(texture, 0, 0, 0);
  239. gs_technique_end_pass(tech);
  240. }
  241. }
  242. gs_technique_end(tech);
  243. }
  244. extern "C" EXPORT void winrt_capture_render(struct winrt_capture *capture,
  245. gs_effect_t *effect)
  246. {
  247. if (capture && capture->texture_written) {
  248. if (!initialized_tls) {
  249. struct winrt_capture *current = capture_list;
  250. while (current) {
  251. current->thread_changed = true;
  252. current = current->next;
  253. }
  254. initialized_tls = true;
  255. }
  256. if (capture->thread_changed) {
  257. /* new graphics thread. treat like device loss. */
  258. winrt_capture_device_loss_release(capture);
  259. winrt_capture_device_loss_rebuild(gs_get_device_obj(),
  260. capture);
  261. capture->thread_changed = false;
  262. }
  263. draw_texture(capture, effect);
  264. }
  265. }
  266. extern "C" EXPORT int32_t
  267. winrt_capture_width(const struct winrt_capture *capture)
  268. {
  269. return capture ? capture->last_size.Width : 0;
  270. }
  271. extern "C" EXPORT int32_t
  272. winrt_capture_height(const struct winrt_capture *capture)
  273. {
  274. return capture ? capture->last_size.Height : 0;
  275. }