1 /*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
21
22 #include <dwmapi.h>
23 #include <d3d11.h>
24 #include <d3dcompiler.h>
25 #include <dispatcherqueue.h>
26 #include <windows.foundation.h>
27 #include <windows.graphics.capture.h>
28 #include <windows.graphics.capture.interop.h>
29 #include <windows.graphics.directx.direct3d11.h>
30 #if HAVE_IDIRECT3DDXGIINTERFACEACCESS
31 #include <windows.graphics.directx.direct3d11.interop.h>
32 #endif
33
34 extern "C" {
46
48 }
49
50 #include <cinttypes>
51 #include <condition_variable>
52 #include <cwchar>
53 #include <memory>
54 #include <mutex>
55 #include <regex>
56 #include <string>
57 #include <thread>
58 #include <type_traits>
59
60 using namespace ABI::Windows::System;
61 using namespace ABI::Windows::Foundation;
62 using namespace ABI::Windows::Graphics::Capture;
63 using namespace ABI::Windows::Graphics::DirectX::Direct3D11;
65 using Microsoft::WRL::ComPtr;
66 using ABI::Windows::Graphics::SizeInt32;
67 using ABI::Windows::Foundation::TimeSpan;
68 using ABI::Windows::Graphics::DirectX::DirectXPixelFormat;
69
70 #define TIMESPAN_RES 10000000
71 #define TIMESPAN_RES64 INT64_C(10000000)
72
73 #define CAPTURE_POOL_SIZE 2
74
75 enum {
77 };
78
79 #define CCTX(ctx) static_cast<GfxCaptureContext*>(ctx)
80
83
85 HRESULT (WINAPI *RoInitialize)(RO_INIT_TYPE initType);
86 void (WINAPI *RoUninitialize)(void);
87 HRESULT (WINAPI *RoGetActivationFactory)(HSTRING activatableClassId, REFIID
iid,
void **factory);
88 HRESULT (WINAPI *WindowsCreateStringReference)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *
string);
89
91 HRESULT (WINAPI *DwmGetWindowAttribute)(HWND hwnd, DWORD dwAttribute, PVOID
pvAttribute, DWORD cbAttribute);
92
94 HRESULT (WINAPI *CreateDirect3D11DeviceFromDXGIDevice)(IDXGIDevice *dxgiDevice, IInspectable **
graphicsDevice);
95
98
100 DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext);
101
104
106 HRESULT (WINAPI *D3DCompile)(LPCVOID pSrcData, SIZE_T
SrcDataSize, LPCSTR pSourceName,
const D3D10_SHADER_MACRO *pDefines, ID3DInclude *pInclude,
107 LPCSTR pEntrypoint, LPCSTR
pTarget, UINT Flags1, UINT Flags2, ID3DBlob **ppCode, ID3DBlob **ppErrorMsgs);
109
110 // This struct contains all data handled by the capture thread
114
119
120 EventRegistrationToken frame_arrived_token { 0 };
121 EventRegistrationToken closed_token { 0 };
122
125 bool window_closed {
false };
126 uint64_t frame_seq { 0 };
127
128 SizeInt32 cap_size { 0, 0 };
129 RECT client_area_offsets { 0, 0, 0, 0 };
130 };
131
138 };
139
142 std::unique_ptr<GfxCaptureContextWgc>
wgc;
143 std::unique_ptr<GfxCaptureContextD3D>
d3d;
144
146 DWORD wgc_thread_id { 0 };
149 volatile int wgc_thread_init_res { INT_MAX };
151 volatile int wgc_thread_res { 0 };
153
154 HWND capture_hwnd {
nullptr };
155 HMONITOR capture_hmonitor {
nullptr };
156
160
164
167 };
168
169 template <typename T>
171 HSTRING_HEADER hsheader = { 0 };
173
174 HRESULT hr =
ctx->fn.WindowsCreateStringReference(clsid, (UINT32)wcslen(clsid), &hsheader, &hs);
175 if (FAILED(hr))
176 return hr;
177
178 return ctx->fn.RoGetActivationFactory(hs, IID_PPV_ARGS(factory));
179 }
180
181 #define CHECK_HR(fcall, action) \
182 do { \
183 HRESULT fhr = fcall; \
184 if (FAILED(fhr)) { \
185 av_log(avctx, AV_LOG_ERROR, #fcall " failed: 0x%08lX\n", fhr); \
186 action; \
187 } \
188 } while (0)
189 #define CHECK_HR_RET(...) CHECK_HR((__VA_ARGS__), return AVERROR_EXTERNAL)
190 #define CHECK_HR_FAIL(...) CHECK_HR((__VA_ARGS__), ret = AVERROR_EXTERNAL; goto fail)
191 #define CHECK_HR_LOG(...) CHECK_HR((__VA_ARGS__), (void)0)
192
193 /****************************************************
194 * Windows Graphics Capture Worker Thread *
195 * All wgc_* functions must run only on WGC thread! *
196 ****************************************************/
197
199 {
200 std::lock_guard
lock(wgctx->frame_arrived_mutex);
201 wgctx->frame_seq += 1;
202 }
203 wgctx->frame_arrived_cond.notify_one();
204 }
205
207 {
208 std::lock_guard
lock(wgctx->frame_arrived_mutex);
209 wgctx->window_closed = true;
210 }
211 wgctx->frame_arrived_cond.notify_one();
212 }
213
215 {
218 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
219
220 if (wgctx->closed_token.value && wgctx->capture_item) {
221 CHECK_HR_LOG(wgctx->capture_item->remove_Closed(wgctx->closed_token));
222 wgctx->closed_token.value = 0;
223 }
224
225 if (wgctx->frame_arrived_token.value && wgctx->frame_pool) {
226 CHECK_HR_LOG(wgctx->frame_pool->remove_FrameArrived(wgctx->frame_arrived_token));
227 wgctx->frame_arrived_token.value = 0;
228 }
229
230 if (wgctx->capture_session) {
231 ComPtr<IClosable> closable;
232 if (SUCCEEDED(wgctx->capture_session.As(&closable))) {
234 } else {
236 }
237 }
238
239 if (wgctx->frame_pool) {
240 ComPtr<IClosable> closable;
241 if (SUCCEEDED(wgctx->frame_pool.As(&closable))) {
243 } else {
245 }
246 }
247
248 wgctx->capture_session.Reset();
249 wgctx->frame_pool.Reset();
250 wgctx->capture_item.Reset();
251 wgctx->d3d_device.Reset();
252 }
253
255 {
258 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
259
260 if (!
ctx->capture_hwnd) {
261 wgctx->client_area_offsets.left = 0;
262 wgctx->client_area_offsets.top = 0;
263 wgctx->client_area_offsets.right = 0;
264 wgctx->client_area_offsets.bottom = 0;
265 return 0;
266 }
267
268 RECT client_rect = {};
269 RECT frame_bounds = {};
270 RECT window_rect = {};
271
272 if (IsIconic(
ctx->capture_hwnd)) {
274 return 0;
275 }
276
277 if (!GetClientRect(
ctx->capture_hwnd, &client_rect)) {
280 }
281
282 SetLastError(0);
283 if (!MapWindowPoints(
ctx->capture_hwnd,
nullptr, (POINT*)&client_rect, 2) && GetLastError()) {
286 }
287
288 if (FAILED(
ctx->fn.DwmGetWindowAttribute(
ctx->capture_hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame_bounds,
sizeof(window_rect))))
290
291 if (!GetWindowRect(
ctx->capture_hwnd, &window_rect))
293
294 if (wgctx->cap_size.Width == frame_bounds.right - frame_bounds.left ||
295 wgctx->cap_size.Height == frame_bounds.bottom - frame_bounds.top) {
297 } else if (wgctx->cap_size.Width == window_rect.right - window_rect.left ||
298 wgctx->cap_size.Height == window_rect.bottom - window_rect.top) {
300 frame_bounds = window_rect;
301 } else {
302 if ((frame_bounds.top == frame_bounds.bottom || frame_bounds.left == frame_bounds.right) &&
303 (window_rect.top == window_rect.bottom || window_rect.left == window_rect.right))
304 {
307 }
308 av_log(avctx,
AV_LOG_VERBOSE,
"Failed to get valid window rect, client area may be inaccurate\n");
309 return 0;
310 }
311
312 wgctx->client_area_offsets.left =
FFMAX(client_rect.left - frame_bounds.left, 0);
313 wgctx->client_area_offsets.top =
FFMAX(client_rect.top - frame_bounds.top, 0);
314 wgctx->client_area_offsets.right =
FFMAX(frame_bounds.right - client_rect.right, 0);
315 wgctx->client_area_offsets.bottom =
FFMAX(frame_bounds.bottom - client_rect.bottom, 0);
316
317 av_log(avctx,
AV_LOG_DEBUG,
"Client area offsets: left=%ld top=%ld right=%ld bottom=%ld\n",
318 wgctx->client_area_offsets.left, wgctx->client_area_offsets.top,
319 wgctx->client_area_offsets.right, wgctx->client_area_offsets.bottom);
320
321 return 0;
322 }
323
325 {
328 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
330
331 ComPtr<IDirect3D11CaptureFramePoolStatics2> frame_pool_statics;
332 ComPtr<ID3D11Device> d3d11_device =
ctx->device_hwctx->device;
333 ComPtr<ID3D10Multithread> d3d10_multithread;
334 ComPtr<IDXGIDevice> dxgi_device;
335 ComPtr<IGraphicsCaptureSession2> session2;
336 ComPtr<IGraphicsCaptureSession3> session3;
337 ComPtr<IGraphicsCaptureSession5> session5;
338
339 DirectXPixelFormat fmt = DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
341 fmt = DirectXPixelFormat::DirectXPixelFormat_R16G16B16A16Float;
342
343 CHECK_HR_RET(wgctx->capture_item->get_Size(&wgctx->cap_size));
347
349 d3d10_multithread->SetMultithreadProtected(TRUE);
350
352 CHECK_HR_RET(
ctx->fn.CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(), &wgctx->d3d_device));
353
354 CHECK_HR_RET(get_activation_factory<IDirect3D11CaptureFramePoolStatics2>(
ctx, RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, &frame_pool_statics));
356 CHECK_HR_RET(wgctx->frame_pool->CreateCaptureSession(wgctx->capture_item.Get(), &wgctx->capture_session));
357
358 if (SUCCEEDED(wgctx->capture_session.As(&session2))) {
359 if (FAILED(session2->put_IsCursorCaptureEnabled(cctx->
capture_cursor))) {
361 }
362 } else {
364 }
365
366 if (SUCCEEDED(wgctx->capture_session.As(&session3))) {
367 // this one is weird, it can return failure but still work
368 if (FAILED(session3->put_IsBorderRequired(cctx->
display_border))) {
370 }
371 } else {
373 }
374
375 if (SUCCEEDED(wgctx->capture_session.As(&session5))) {
377 if (FAILED(session5->put_MinUpdateInterval(ivl))) {
378 av_log(avctx,
AV_LOG_WARNING,
"Failed setting minimum update interval, framerate may be limited\n");
379 }
380 } else {
381 av_log(avctx,
AV_LOG_WARNING,
"Setting minimum update interval unavailable, framerate may be limited\n");
382 }
383
384 wgctx->window_closed = 0;
385
387 create_cb_handler<ITypedEventHandler<GraphicsCaptureItem*,IInspectable*>, IGraphicsCaptureItem*, IInspectable*>(
388 [avctx,
ctx](
auto,
auto) {
389 av_log(avctx, AV_LOG_INFO, "Capture item closed\n");
390 wgc_closed_handler(ctx->wgc);
391 return S_OK;
392 }).Get(), &wgctx->closed_token));
393
395 create_cb_handler<ITypedEventHandler<Direct3D11CaptureFramePool*,IInspectable*>, IDirect3D11CaptureFramePool*, IInspectable*>(
396 [avctx,
ctx](
auto,
auto) {
397 av_log(avctx, AV_LOG_TRACE, "Frame arrived\n");
398 wgc_frame_arrived_handler(ctx->wgc);
399 return S_OK;
400 }).Get(), &wgctx->frame_arrived_token));
401
402 return 0;
403 }
404
406 {
409 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
410 HRESULT hr;
412
413 ComPtr<IGraphicsCaptureItemInterop> capture_item_interop;
414 CHECK_HR_RET(get_activation_factory<IGraphicsCaptureItemInterop>(
ctx, RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &capture_item_interop));
415
416 if (
ctx->capture_hmonitor) {
417 hr = capture_item_interop->CreateForMonitor(
ctx->capture_hmonitor, IID_PPV_ARGS(&wgctx->capture_item));
418 if (FAILED(hr)) {
419 av_log(avctx,
AV_LOG_ERROR,
"Failed to setup graphics capture for monitor (0x%08lX)\n", hr);
421 }
422 }
else if (
ctx->capture_hwnd) {
423 hr = capture_item_interop->CreateForWindow(
ctx->capture_hwnd, IID_PPV_ARGS(&wgctx->capture_item));
424 if (FAILED(hr)) {
425 av_log(avctx,
AV_LOG_ERROR,
"Failed to setup graphics capture for window (0x%08lX)\n", hr);
427 }
428 }
429
434 }
435
436 hr =
ctx->wgc->capture_session->StartCapture();
437 if (FAILED(hr)) {
438 av_log(avctx,
AV_LOG_ERROR,
"Failed to start graphics capture session (0x%08lX)\n", hr);
440 }
441
442 return 0;
443 }
444
446 {
449 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
450
451 ComPtr<IDirect3DSurface> capture_surface;
452 ComPtr<IDirect3DDxgiInterfaceAccess> dxgi_interface_access;
453 ComPtr<ID3D11Texture2D> frame_texture;
455
456 CHECK_HR_RET(wgctx->frame_pool->TryGetNextFrame(&capture_frame));
457 if (!capture_frame)
459
463
464 DirectXPixelFormat fmt = DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
466 fmt = DirectXPixelFormat::DirectXPixelFormat_R16G16B16A16Float;
467
470
474 }
475
476 return 0;
477 }
478
480 {
483 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
484 MSG msg;
485
486 // pre-create the message-queue
487 PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE);
488
489 DispatcherQueueOptions
options = { 0 };
490 options.dwSize =
sizeof(DispatcherQueueOptions);
491 options.threadType = DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT;
492 options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE;
493
495 CHECK_HR_RET(wgctx->dispatcher_queue_controller->get_DispatcherQueue(&wgctx->dispatcher_queue));
496
497 return 0;
498 }
499
501 {
504
506
508 ctx->fn.RoUninitialize();
509 }
510
512 {
515 HRESULT hr;
517
518 ctx->wgc = std::make_unique<GfxCaptureContextWgc>();
519
520 ctx->fn.SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
521
522 hr =
ctx->fn.RoInitialize(RO_INIT_MULTITHREADED);
523 if (FAILED(hr)) {
527 }
528
533 }
534
539 }
540
541 return 0;
542
546 }
547
549 {
552 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
553 ComPtr<IAsyncAction> async;
554 MSG msg;
555
557
558 while (BOOL res = GetMessage(&msg,
NULL, 0, 0)) {
559 if (res == -1) {
562 }
563
566
568
569 if (FAILED(wgctx->dispatcher_queue_controller->ShutdownQueueAsync(&async))) {
572 }
573 async->put_Completed(create_cb_handler<IAsyncActionCompletedHandler, IAsyncAction*, AsyncStatus>(
575 PostThreadMessage(
ctx->wgc_thread_id, WM_QUIT, 0, 0);
577 return S_OK;
578 }).Get());
579 continue;
580 }
581
583
584 TranslateMessage(&msg);
585 DispatchMessage(&msg);
586 }
587
588 if (!async) {
589 av_log(avctx,
AV_LOG_ERROR,
"WGC Thread message loop ended without proper shutdown\n");
591 }
592
594
595 return msg.wParam;
596 }
597
599 {
602
603 {
604 static const wchar_t name_prefix[] =
L "wgc_winrt@0x";
605 wchar_t thread_name[
FF_ARRAY_ELEMS(name_prefix) +
sizeof(
void*) * 2] = { 0 };
606 swprintf(thread_name,
FF_ARRAY_ELEMS(thread_name),
L "%ls%" PRIxPTR, name_prefix, (uintptr_t)avctx);
607 ctx->fn.SetThreadDescription(GetCurrentThread(), thread_name);
608
609 std::lock_guard init_lock(
ctx->wgc_thread_init_mutex);
610 ctx->wgc_thread_id = GetCurrentThreadId();
611
612 try {
614 } catch (const std::bad_alloc &) {
616 } catch (const std::exception &e) {
617 av_log(avctx,
AV_LOG_ERROR,
"unhandled exception in WGC thread init: %s\n", e.what());
619 } catch (...) {
622 }
623
624 ctx->wgc_thread_init_cond.notify_all();
625 if (
ctx->wgc_thread_init_res < 0) {
626 ctx->wgc_thread_res =
ctx->wgc_thread_init_res;
627 return;
628 }
629 }
630
632
633 try {
635 } catch (const std::bad_alloc &) {
637 } catch (const std::exception &e) {
638 av_log(avctx,
AV_LOG_ERROR,
"unhandled exception in WGC thread worker: %s\n", e.what());
640 } catch (...) {
643 }
644
645 std::lock_guard uninit_lock(
ctx->wgc_thread_uninit_mutex);
647
648 ctx->wgc_thread_res =
ret;
649 }
650
651 /***********************************
652 * WGC Thread Management Functions *
653 ***********************************/
654
656 {
660
661 if (
ctx->wgc_thread.joinable()) {
664
665 ctx->wgc_thread.join();
666 ret =
ctx->wgc_thread_res;
667
668 ctx->wgc_thread_id = 0;
669 }
670
672 }
673
675 {
678
679 if (
ctx->wgc_thread.joinable() ||
ctx->wgc_thread_id) {
682 }
683
684 std::unique_lock wgc_lock(
ctx->wgc_thread_init_mutex);
685 ctx->wgc_thread_init_res = INT_MAX;
686
687 try {
689 } catch (const std::system_error &e) {
692 }
693
694 if (!
ctx->wgc_thread_init_cond.wait_for(wgc_lock, std::chrono::seconds(1), [&]() {
695 return ctx->wgc_thread_init_res != INT_MAX;
696 })) {
699 }
700
701 return ctx->wgc_thread_init_res;
702 }
703
704 template <typename F>
706 {
709 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
710
711 std::lock_guard uninit_lock(
ctx->wgc_thread_uninit_mutex);
712 if (!wgctx) {
715 }
716
717 struct CBData {
719 std::condition_variable
cond;
720 bool done;
721 bool cancel;
723 };
724 auto cbdata =
ctx->wgc_thread_cb_data ?
725 std::static_pointer_cast<CBData>(
ctx->wgc_thread_cb_data) :
726 std::make_shared<CBData>();
727 ctx->wgc_thread_cb_data = cbdata;
728
729 cbdata->done = cbdata->cancel = false;
731
732 boolean res = 0;
734 create_cb_handler<IDispatcherQueueHandler>(
735 [
cb = std::forward<F>(
cb), cbdata]() {
736 {
737 std::lock_guard lock(cbdata->mutex);
738 if (cbdata->cancel)
739 return S_OK;
740
741 try {
742 cbdata->ret = cb();
743 } catch (const std::bad_alloc &) {
744 cbdata->ret = AVERROR(ENOMEM);
745 } catch (...) {
746 cbdata->ret = AVERROR_BUG;
747 }
748
749 cbdata->done = true;
750 }
751
752 cbdata->cond.notify_one();
753 return S_OK;
754 }).Get(), &res));
755 if (!res) {
758 }
759
760 std::unique_lock cblock(cbdata->mutex);
761 if (!cbdata->cond.wait_for(cblock, std::chrono::seconds(1), [&]() { return cbdata->done; })) {
762 cbdata->cancel = true;
765 }
766
767 return cbdata->ret;
768 }
769
770 /*******************************
771 * Standard AVFilter functions *
772 *******************************/
773
775 {
776 if (!pattern)
777 return 0;
778
779 std::string pat(pattern);
780
781 auto flags = std::regex::ECMAScript | std::regex::optimize;
782 if (pat.rfind("(?i)", 0) == 0 || pat.rfind("(?I)", 0) == 0) {
783 pat.erase(0, 4);
784 flags |= std::regex::icase;
785 } else if(pat.rfind("(?c)", 0) == 0 || pat.rfind("(?C)", 0) == 0) {
786 pat.erase(0, 4);
787 }
788
789 try {
791 } catch (const std::regex_error &e) {
792 av_log(avctx,
AV_LOG_ERROR,
"Failed to compile regex '%s': %s\n", pat.c_str(), e.what());
794 }
795
797
798 return 0;
799 }
800
802 {
803 int utf8size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr);
804 if (utf8size <= 0)
806
807 // over-writing std::string by one is valid in C++17 according to 27.4.3.6 if and only if it's overwritten with 0
808 out->resize(utf8size - 1);
809
810 if (WideCharToMultiByte(CP_UTF8, 0, in, -1,
out->data(), utf8size,
nullptr,
nullptr) != utf8size)
812
813 return 0;
814 }
815
817 {
819
820 DWORD pid = 0;
821 if (!GetWindowThreadProcessId(hwnd, &pid))
823
824 handle_ptr_t proc(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
825 if (!proc)
827
828 std::wstring image_name;
829 DWORD image_name_size = 512;
830
831 for (;;) {
832 DWORD
len = image_name_size;
833 image_name.resize(
len);
834 if (QueryFullProcessImageNameW(proc.get(), 0, image_name.data(), &
len)) {
835 image_name.resize(
len);
836 break;
837 }
838 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
839 image_name_size *= 2;
840 continue;
841 }
843 }
844
845 if (image_name.empty())
847
848 const wchar_t *
base = image_name.c_str();
849 size_t pos = image_name.find_last_of(
L "\\/");
850 if (
pos != std::string::npos)
852
854 }
855
857 {
860 int cur_idx = 0;
861
864
867 return 0;
870 return 0;
874 av_log(avctx, AV_LOG_DEBUG, "Found capture monitor: %d\n", cctx->monitor_idx);
875 ctx->capture_hmonitor = hmonitor;
876 return FALSE;
877 }
878 return TRUE;
879 });
880 if (EnumDisplayMonitors(
NULL,
NULL,
cb->proc,
cb->lparam) || !
ctx->capture_hmonitor)
882 return 0;
884 std::regex text_regex;
887
888 std::regex class_regex;
891
892 std::regex exe_regex;
895
896 std::string window_text;
897 std::wstring window_text_w;
898 std::string window_class;
899 std::wstring window_class_w;
900 std::string window_exe;
903 if (!GetWindowRect(hwnd, &
r) ||
r.right <=
r.left ||
r.bottom <=
r.top || !IsWindowVisible(hwnd))
904 return TRUE;
905
906 window_text_w.resize(GetWindowTextLengthW(hwnd) + 1);
907 int len = GetWindowTextW(hwnd, window_text_w.data(), (
int)window_text_w.size());
909 window_text_w.resize(
len);
911 window_text.clear();
912 } else {
913 window_text.clear();
914 }
915
916 window_class_w.resize(256);
917 len = GetClassNameW(hwnd, window_class_w.data(), (
int)window_class_w.size());
919 window_class_w.resize(
len);
921 window_class.clear();
922 } else {
923 window_class.clear();
924 }
925
927
929 hwnd, window_text.c_str(), window_class.c_str(), window_exe.c_str());
930
932 if (window_text.empty() || !std::regex_search(window_text, text_regex))
933 return TRUE;
934 }
935
937 if (window_class.empty() || !std::regex_search(window_class, class_regex))
938 return TRUE;
939 }
940
942 if (window_exe.empty() || !std::regex_search(window_exe, exe_regex))
943 return TRUE;
944 }
945
947 window_text.c_str(), window_class.c_str(), window_exe.c_str());
948 ctx->capture_hwnd = hwnd;
949 return FALSE;
950 });
951 if (EnumWindows(
cb->proc,
cb->lparam) || !
ctx->capture_hwnd)
953
955 ctx->capture_hmonitor = MonitorFromWindow(
ctx->capture_hwnd, MONITOR_DEFAULTTONEAREST);
957 if (!
ctx->capture_hmonitor) {
960 }
961 }
962
963 return 0;
964 }
965
968 }
969
971 {
974
976 return;
977
979
981
984
987 }
988
989 template<typename T>
991 *
out =
reinterpret_cast<T >(GetProcAddress(hModule.get(), lpProcName));
992 }
993
995 {
998
999 #define LOAD_DLL(handle, name) \
1000 handle = hmodule_ptr_t(LoadLibraryExW(L##name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); \
1001 if (!handle) { \
1002 av_log(avctx, AV_LOG_ERROR, "Failed opening " #name "\n"); \
1003 return AVERROR(ENOSYS); \
1004 }
1005
1006 #define LOAD_FUNC(handle, name) \
1007 GetProcAddressTyped(handle, #name, &ctx->fn.name); \
1008 if (!ctx->fn.name) { \
1009 av_log(avctx, AV_LOG_ERROR, "Failed loading " #name "\n"); \
1010 return AVERROR(ENOSYS); \
1011 }
1012
1013 // this handle is not used anywhere, but letting it get auto-freed during RoUninit causes crashes
1014 LOAD_DLL(
ctx->fn.graphicscapture_handle,
"graphicscapture.dll");
1015
1019 LOAD_DLL(
ctx->fn.coremsg_handle,
"coremessaging.dll");
1021 LOAD_DLL(
ctx->fn.kernel32_handle,
"kernel32.dll");
1022 LOAD_DLL(
ctx->fn.d3dcompiler_handle,
"d3dcompiler_47.dll");
1023
1026 LOAD_FUNC(
ctx->fn.combase_handle, RoGetActivationFactory);
1027 LOAD_FUNC(
ctx->fn.combase_handle, WindowsCreateStringReference);
1028
1029 LOAD_FUNC(
ctx->fn.dwmapi_handle, DwmGetWindowAttribute);
1030
1031 LOAD_FUNC(
ctx->fn.d3d11_handle, CreateDirect3D11DeviceFromDXGIDevice);
1032
1033 LOAD_FUNC(
ctx->fn.coremsg_handle, CreateDispatcherQueueController);
1034
1035 LOAD_FUNC(
ctx->fn.user32_handle, SetThreadDpiAwarenessContext);
1036
1037 LOAD_FUNC(
ctx->fn.kernel32_handle, SetThreadDescription);
1038
1040
1041 #undef LOAD_FUNC
1042 #undef LOAD_DLL
1043 return 0;
1044 }
1045
1047 {
1050
1052 ctx->d3d = std::make_unique<GfxCaptureContextD3D>();
1053
1056 ctx->fn.RoUninitialize =
nullptr;
1058 }
1059
1060 return 0;
1061
1065 }
1066
1068 {
1072
1074 if (!
ctx->frames_ref)
1078
1085
1086 ctx->frames_hwctx->BindFlags = D3D11_BIND_RENDER_TARGET;
1087
1092 }
1093
1094 return 0;
1098 }
1099
1101 {
1104 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
1106
1108
1113 }
1114
1119 }
1120
1123
1125 cap_w -= wgctx->client_area_offsets.left + wgctx->client_area_offsets.right;
1126 cap_h -= wgctx->client_area_offsets.top + wgctx->client_area_offsets.bottom;
1127 }
1128
1133
1138
1139 return 0;
1140 }
1141
1143 {
1146 std::unique_ptr<GfxCaptureContextD3D> &d3dctx =
ctx->d3d;
1147 HRESULT hr;
1148
1149 ComPtr<ID3DBlob> vs_blob, ps_blob, err_blob;
1150 CD3D11_SAMPLER_DESC sampler_desc(CD3D11_DEFAULT{});
1151 UINT
flags = D3DCOMPILE_OPTIMIZATION_LEVEL3;
1152
1153 hr =
ctx->fn.D3DCompile(
render_shader_src,
sizeof(
render_shader_src) - 1,
NULL,
NULL,
NULL,
"main_vs",
"vs_4_0",
flags, 0, &vs_blob, &err_blob);
1154 if (FAILED(hr)) {
1155 if (err_blob) {
1156 av_log(avctx,
AV_LOG_ERROR,
"Failed compiling vertex shader: %.*s\n", (
int)err_blob->GetBufferSize(), (
char*)err_blob->GetBufferPointer());
1157 } else {
1159 }
1161 }
1162
1163 const char *ps_entry = "main_ps_bicubic";
1165 ps_entry = "main_ps";
1166 sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
1167 }
1168
1169 hr =
ctx->fn.D3DCompile(
render_shader_src,
sizeof(
render_shader_src) - 1,
NULL,
NULL,
NULL, ps_entry,
"ps_4_0",
flags, 0, &ps_blob, &err_blob);
1170 if (FAILED(hr)) {
1171 if (err_blob) {
1172 av_log(avctx,
AV_LOG_ERROR,
"Failed compiling pixel shader: %.*s\n", (
int)err_blob->GetBufferSize(), (
char*)err_blob->GetBufferPointer());
1173 } else {
1175 }
1177 }
1178
1179 CHECK_HR_RET(
ctx->device_hwctx->device->CreateVertexShader(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(),
NULL, &d3dctx->vertex_shader));
1180 CHECK_HR_RET(
ctx->device_hwctx->device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(),
NULL, &d3dctx->pixel_shader));
1181
1182 CHECK_HR_RET(
ctx->device_hwctx->device->CreateSamplerState(&sampler_desc, &d3dctx->sampler_state));
1183
1184 D3D11_BUFFER_DESC cb_desc = { 0 };
1185 cb_desc.ByteWidth = 48;
1186 cb_desc.Usage = D3D11_USAGE_DYNAMIC;
1187 cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
1188 cb_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
1189
1190 CHECK_HR_RET(
ctx->device_hwctx->device->CreateBuffer(&cb_desc,
NULL, &d3dctx->shader_cb));
1191
1192 CHECK_HR_RET(
ctx->device_hwctx->device->CreateDeferredContext(0, &d3dctx->deferred_ctx));
1193
1194 return 0;
1195 }
1196
1198 {
1202
1205
1208
1212 }
1213
1215 if (!
ctx->device_ref)
1217
1219 } else {
1224 }
1225
1227
1229 }
1230
1232
1237 }
1238
1243 }
1244
1248
1252
1253 std::lock_guard wgc_lock(
ctx->wgc_thread_uninit_mutex);
1257 }
1258
1259 outlink->
w =
ctx->frames_ctx->width;
1260 outlink->
h =
ctx->frames_ctx->height;
1264
1266
1267 return 0;
1268 }
1269
1271 {
1274 std::unique_ptr<GfxCaptureContextD3D> &d3dctx =
ctx->d3d;
1275 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
1276
1278 ID3D11DeviceContext *dev_ctx =
ctx->device_hwctx->device_context;
1279 ComPtr<ID3D11DeviceContext> &def_ctx = d3dctx->deferred_ctx;
1280
1281 D3D11_TEXTURE2D_DESC dst_tex_desc;
1282 reinterpret_cast<ID3D11Texture2D*
>(
frame->data[0])->GetDesc(&dst_tex_desc);
1283
1284 D3D11_TEXTURE2D_DESC src_tex_desc;
1285 src_tex->GetDesc(&src_tex_desc);
1286
1287 D3D11_RENDER_TARGET_VIEW_DESC target_desc = {};
1288 target_desc.Format = dst_tex_desc.Format;
1289
1290 if (dst_tex_desc.ArraySize > 1) {
1291 target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
1292 target_desc.Texture2DArray.ArraySize = 1;
1293 target_desc.Texture2DArray.FirstArraySlice = (uintptr_t)
frame->data[1];
1294 target_desc.Texture2DArray.MipSlice = 0;
1295 } else {
1296 target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
1297 target_desc.Texture2D.MipSlice = 0;
1298 }
1299
1300 ComPtr<ID3D11RenderTargetView> rtv;
1302 reinterpret_cast<ID3D11Resource*
>(
frame->data[0]), &target_desc, &rtv));
1303
1304 ComPtr<ID3D11ShaderResourceView> srv;
1305 CHECK_HR_RET(dev->CreateShaderResourceView(src_tex.Get(),
nullptr, &srv));
1306
1311
1313 crop_left += wgctx->client_area_offsets.left;
1314 crop_top += wgctx->client_area_offsets.top;
1315 crop_right += wgctx->client_area_offsets.right;
1316 crop_bottom += wgctx->client_area_offsets.bottom;
1317 }
1318
1319 // Using the actual capture frame size here adjusts for jank that can happen during rapid
1320 // resizing of the source window. The capture frame pool is only recreated once a frame
1321 // of changed size came out of it, so we need to cut/pad such frames to fit.
1322 // Just discarding such frames can lead to visible stutter if the source window is being
1323 // resized continuously, so this code does its best to adjust them instead. With the risk
1324 // of slight clamping artifacts when enlarging rapidly.
1325 int cropped_w = wgctx->cap_size.Width - crop_left - crop_right;
1326 int cropped_h = wgctx->cap_size.Height - crop_top - crop_bottom;
1327
1328 D3D11_VIEWPORT viewport = { 0 };
1329 viewport.MinDepth = 0.f;
1330 viewport.MaxDepth = 1.f;
1331
1334 viewport.Width = (
float)cropped_w;
1335 viewport.Height = (
float)cropped_h;
1336 break;
1338 viewport.Width = dst_tex_desc.Width;
1339 viewport.Height = dst_tex_desc.Height;
1340 break;
1342 float scale =
FFMIN(dst_tex_desc.Width / (
float)cropped_w,
1343 dst_tex_desc.Height / (float)cropped_h);
1344 viewport.Width = cropped_w *
scale;
1345 viewport.Height = cropped_h *
scale;
1346 break;
1347 }
1348 default:
1351 };
1352
1353 def_ctx->RSSetViewports(1, &viewport);
1354
1355 D3D11_MAPPED_SUBRESOURCE
map;
1356 CHECK_HR_RET(def_ctx->Map(d3dctx->shader_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &
map));
1357 {
1358 float *cb_f =
static_cast<float*
>(
map.pData);
1359 uint32_t *cb_u =
static_cast<uint32_t*
>(
map.pData);
1360 cb_f[0] = (
float)cropped_w;
1361 cb_f[1] = (
float)cropped_h;
1362 cb_f[2] = viewport.Width;
1363 cb_f[3] = viewport.Height;
1364 cb_f[4] = crop_left / (
float)src_tex_desc.Width;
// min_u
1365 cb_f[5] = crop_top / (float)src_tex_desc.Height; // min_v
1366 cb_f[6] = (crop_left + cropped_w) / (float)src_tex_desc.Width; // max_u
1367 cb_f[7] = (crop_top + cropped_h) / (float)src_tex_desc.Height; // max_v
1371 }
1372 def_ctx->Unmap(d3dctx->shader_cb.Get(), 0);
1373
1374 def_ctx->OMSetRenderTargets(1, rtv.GetAddressOf(), nullptr);
1375
1376 const float clear_color[4] = {0.f, 0.f, 0.f, 1.f};
1377 def_ctx->ClearRenderTargetView(rtv.Get(), clear_color);
1378
1379 def_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
1380 def_ctx->VSSetShader(d3dctx->vertex_shader.Get(), nullptr, 0);
1381 def_ctx->VSSetConstantBuffers(0, 1, d3dctx->shader_cb.GetAddressOf());
1382 def_ctx->PSSetShader(d3dctx->pixel_shader.Get(), nullptr, 0);
1383 def_ctx->PSSetSamplers(0, 1, d3dctx->sampler_state.GetAddressOf());
1384 def_ctx->PSSetShaderResources(0, 1, srv.GetAddressOf());
1385 def_ctx->PSSetConstantBuffers(0, 1, d3dctx->shader_cb.GetAddressOf());
1386
1387 def_ctx->Draw(3, 0);
1388
1389 ComPtr<ID3D11CommandList> cmd_list;
1390 CHECK_HR_RET(def_ctx->FinishCommandList(FALSE, &cmd_list));
1391 dev_ctx->ExecuteCommandList(cmd_list.Get(), FALSE);
1392
1393 return 0;
1394 }
1395
1397 {
1402
1404
1406 ComPtr<IDirect3D11CaptureFrame> capture_frame;
1407 ComPtr<IDirect3DSurface> capture_surface;
1408 ComPtr<IDirect3DDxgiInterfaceAccess> dxgi_interface_access;
1409 ComPtr<ID3D11Texture2D> frame_texture;
1410 TimeSpan frame_time = { 0 };
1411
1413 if (res < 0)
1414 return res;
1415
1416 CHECK_HR_RET(capture_frame->get_SystemRelativeTime(&frame_time));
1417
1418 CHECK_HR_RET(capture_frame->get_Surface(&capture_surface));
1419 CHECK_HR_RET(capture_surface.As(&dxgi_interface_access));
1420 CHECK_HR_RET(dxgi_interface_access->GetInterface(IID_PPV_ARGS(&frame_texture)));
1421
1422 if (!frame_texture)
1424
1428
1429 frame->pts = frame_time.Duration;
1430
1432 });
1435
1437
1439 // According to MSDN, all floating point formats contain sRGB image data with linear 1.0 gamma.
1444 } else {
1445 // According to MSDN, all integer formats contain sRGB image data
1450 }
1451
1453
1454 if (!
ctx->first_pts)
1457
1459 }
1460
1462 {
1466 std::unique_ptr<GfxCaptureContextWgc> &wgctx =
ctx->wgc;
1467
1468 std::lock_guard wgc_lock(
ctx->wgc_thread_uninit_mutex);
1469 if (!wgctx) {
1472 }
1473
1476
1477 for (;;) {
1478 uint64_t last_seq = wgctx->frame_seq;
1479
1483
1484 std::unique_lock
frame_lock(wgctx->frame_arrived_mutex);
1485
1486 if (wgctx->window_closed && wgctx->frame_seq == last_seq) {
1488 break;
1489 }
1490
1491 if (!wgctx->frame_arrived_cond.wait_for(
frame_lock, std::chrono::seconds(1), [&]() {
1492 return wgctx->frame_seq != last_seq || wgctx->window_closed;
1493 }))
1494 break;
1495 }
1496
1497 return 0;
1498 }
1499
1501 {
1502 try {
1504 } catch (const std::exception &e) {
1506 } catch (...) {
1508 }
1509 }
1510
1512 {
1513 try {
1515 } catch (const std::bad_alloc&) {
1517 } catch (const std::exception &e) {
1520 } catch (...) {
1523 }
1524 }
1525
1527 {
1528 try {
1530 } catch (const std::bad_alloc&) {
1532 } catch (const std::exception &e) {
1535 } catch (...) {
1538 }
1539 }
1540
1542 {
1544
1545 try {
1547 } catch (const std::bad_alloc&) {
1549 } catch (const std::exception &e) {
1550 av_log(avctx,
AV_LOG_ERROR,
"unhandled exception during config_props: %s\n", e.what());
1552 } catch (...) {
1555 }
1556 }