From 82797eb367d73a8694a987cb17c3ea5436e765df Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:31:54 -0800 Subject: [PATCH 01/12] libobs: Make ComPtr header self-sufficient Don't make someone else define IUnknown. --- libobs/util/windows/ComPtr.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libobs/util/windows/ComPtr.hpp b/libobs/util/windows/ComPtr.hpp index 6d83d2d21..27eeee71c 100644 --- a/libobs/util/windows/ComPtr.hpp +++ b/libobs/util/windows/ComPtr.hpp @@ -16,6 +16,10 @@ #pragma once +#ifdef _WIN32 +#include +#endif + /* Oh no I have my own com pointer class, the world is ending, how dare you * write your own! */ From f27f858ab39f4bde45b28ec195286ba955b6b98e Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:35:27 -0800 Subject: [PATCH 02/12] libobs: Support device loss registration Complex external systems using the D3D11 device may need to perform their own device loss handling, the upcoming Windows Graphics Capture support for example. --- libobs-d3d11/d3d11-rebuild.cpp | 7 +++++++ libobs-d3d11/d3d11-subsystem.cpp | 19 +++++++++++++++++++ libobs-d3d11/d3d11-subsystem.hpp | 1 + libobs/graphics/graphics-imports.c | 2 ++ libobs/graphics/graphics-internal.h | 4 ++++ libobs/graphics/graphics.c | 24 ++++++++++++++++++++++++ libobs/graphics/graphics.h | 9 +++++++++ 7 files changed, 66 insertions(+) diff --git a/libobs-d3d11/d3d11-rebuild.cpp b/libobs-d3d11/d3d11-rebuild.cpp index 6cefef3eb..113f711b4 100644 --- a/libobs-d3d11/d3d11-rebuild.cpp +++ b/libobs-d3d11/d3d11-rebuild.cpp @@ -348,6 +348,9 @@ try { /* ----------------------------------------------------------------- */ + for (gs_device_loss &callback : loss_callbacks) + callback.device_loss_release(callback.data); + gs_obj *obj = first_obj; while (obj) { @@ -404,6 +407,7 @@ try { state.Release(); context->ClearState(); + context->Flush(); context.Release(); device.Release(); @@ -506,6 +510,9 @@ try { for (auto &state : blendStates) state.Rebuild(dev); + for (gs_device_loss &callback : loss_callbacks) + callback.device_loss_rebuild(device.Get(), callback.data); + } catch (const char *error) { bcrash("Failed to recreate D3D11: %s", error); diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index fc5c60a28..745c4f8ab 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -2798,3 +2798,22 @@ device_stagesurface_create_nv12(gs_device_t *device, uint32_t width, return surf; } + +extern "C" EXPORT void +device_register_loss_callbacks(gs_device_t *device, + const gs_device_loss *callbacks) +{ + device->loss_callbacks.emplace_back(*callbacks); +} + +extern "C" EXPORT void device_unregister_loss_callbacks(gs_device_t *device, + void *data) +{ + for (auto iter = device->loss_callbacks.begin(); + iter != device->loss_callbacks.end(); ++iter) { + if (iter->data == data) { + device->loss_callbacks.erase(iter); + break; + } + } +} diff --git a/libobs-d3d11/d3d11-subsystem.hpp b/libobs-d3d11/d3d11-subsystem.hpp index 362a242a3..a2c260cf5 100644 --- a/libobs-d3d11/d3d11-subsystem.hpp +++ b/libobs-d3d11/d3d11-subsystem.hpp @@ -946,6 +946,7 @@ struct gs_device { matrix4 curViewMatrix; matrix4 curViewProjMatrix; + vector loss_callbacks; gs_obj *first_obj = nullptr; void InitCompiler(); diff --git a/libobs/graphics/graphics-imports.c b/libobs/graphics/graphics-imports.c index 424c06c90..0d3c25939 100644 --- a/libobs/graphics/graphics-imports.c +++ b/libobs/graphics/graphics-imports.c @@ -214,6 +214,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module, GRAPHICS_IMPORT_OPTIONAL(device_texture_release_sync); GRAPHICS_IMPORT_OPTIONAL(device_texture_create_nv12); GRAPHICS_IMPORT_OPTIONAL(device_stagesurface_create_nv12); + GRAPHICS_IMPORT_OPTIONAL(device_register_loss_callbacks); + GRAPHICS_IMPORT_OPTIONAL(device_unregister_loss_callbacks); #endif return success; diff --git a/libobs/graphics/graphics-internal.h b/libobs/graphics/graphics-internal.h index 4adea3f55..4154688c5 100644 --- a/libobs/graphics/graphics-internal.h +++ b/libobs/graphics/graphics-internal.h @@ -311,6 +311,10 @@ struct gs_exports { gs_stagesurf_t *(*device_stagesurface_create_nv12)(gs_device_t *device, uint32_t width, uint32_t height); + void (*device_register_loss_callbacks)( + gs_device_t *device, const struct gs_device_loss *callbacks); + void (*device_unregister_loss_callbacks)(gs_device_t *device, + void *data); #endif }; diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c index b3aaa8256..0bddee5c0 100644 --- a/libobs/graphics/graphics.c +++ b/libobs/graphics/graphics.c @@ -2959,4 +2959,28 @@ gs_stagesurf_t *gs_stagesurface_create_nv12(uint32_t width, uint32_t height) return NULL; } +void gs_register_loss_callbacks(const struct gs_device_loss *callbacks) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_register_loss_callbacks")) + return; + + if (graphics->exports.device_register_loss_callbacks) + graphics->exports.device_register_loss_callbacks( + graphics->device, callbacks); +} + +void gs_unregister_loss_callbacks(void *data) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_unregister_loss_callbacks")) + return; + + if (graphics->exports.device_unregister_loss_callbacks) + graphics->exports.device_unregister_loss_callbacks( + graphics->device, data); +} + #endif diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index 3802fbbf9..341a55d1a 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -169,6 +169,12 @@ enum gs_texture_type { GS_TEXTURE_CUBE, }; +struct gs_device_loss { + void (*device_loss_release)(void *data); + void (*device_loss_rebuild)(void *device, void *data); + void *data; +}; + struct gs_monitor_info { int rotation_degrees; long x; @@ -883,6 +889,9 @@ EXPORT bool gs_texture_create_nv12(gs_texture_t **tex_y, gs_texture_t **tex_uv, EXPORT gs_stagesurf_t *gs_stagesurface_create_nv12(uint32_t width, uint32_t height); +EXPORT void gs_register_loss_callbacks(const struct gs_device_loss *callbacks); +EXPORT void gs_unregister_loss_callbacks(void *data); + #endif /* inline functions used by modules */ From 82cdc6e8c6a30d621d47093996a1f1844f202bd9 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:38:35 -0800 Subject: [PATCH 03/12] libobs: Pump Win32 messages on the graphics thread Necessary for upcoming Windows Graphics Capture support. --- libobs/obs-video.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 4b82aa366..207ff1453 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -24,6 +24,11 @@ #include "media-io/format-conversion.h" #include "media-io/video-frame.h" +#ifdef _WIN32 +#define WIN32_MEAN_AND_LEAN +#include +#endif + static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) { struct obs_core_data *data = &obs->data; @@ -874,6 +879,14 @@ void *obs_graphics_thread(void *param) last_time = tick_sources(obs->video.video_time, last_time); profile_end(tick_sources_name); +#ifdef _WIN32 + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + profile_start(output_frame_name); output_frame(raw_active, gpu_active); profile_end(output_frame_name); From 9cff2ae6bb3b6e30447520ec7bd3223735e9bd5d Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:40:25 -0800 Subject: [PATCH 04/12] libobs: Add extra reference when source is displayed This provides a chance to clean up resources on the graphics thread in the hide callback, which Windows Graphics Capture requires. --- libobs/obs-source.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index dd1304b89..b32706fcd 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -958,6 +958,8 @@ static void deactivate_source(obs_source_t *source) static void show_source(obs_source_t *source) { + obs_source_addref(source); + if (source->context.data && source->info.show) source->info.show(source->context.data); obs_source_dosignal(source, "source_show", "show"); @@ -968,6 +970,8 @@ static void hide_source(obs_source_t *source) if (source->context.data && source->info.hide) source->info.hide(source->context.data); obs_source_dosignal(source, "source_hide", "hide"); + + obs_source_release(source); } static void activate_tree(obs_source_t *parent, obs_source_t *child, From d4ca08c4c7a880971f0677761c60807f7c6e68db Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:42:49 -0800 Subject: [PATCH 05/12] win-capture: Add support for finding windows via EnumWindows The top-level window appears to be preferred for upcoming Windows Graphics Capture support, at least for UWP apps. --- plugins/win-capture/window-helpers.c | 49 ++++++++++++++++++++++++++++ plugins/win-capture/window-helpers.h | 4 +++ 2 files changed, 53 insertions(+) diff --git a/plugins/win-capture/window-helpers.c b/plugins/win-capture/window-helpers.c index 8f19ee48b..8b578e72b 100644 --- a/plugins/win-capture/window-helpers.c +++ b/plugins/win-capture/window-helpers.c @@ -424,3 +424,52 @@ HWND find_window(enum window_search_mode mode, enum window_priority priority, return best_window; } + +struct top_level_enum_data { + enum window_search_mode mode; + enum window_priority priority; + const char *class; + const char *title; + const char *exe; + bool uwp_window; + HWND best_window; + int best_rating; +}; + +BOOL CALLBACK enum_windows_proc(HWND window, LPARAM lParam) +{ + struct top_level_enum_data *data = (struct top_level_enum_data *)lParam; + + if (!check_window_valid(window, data->mode)) + return TRUE; + + const int rating = window_rating(window, data->priority, data->class, + data->title, data->exe, + data->uwp_window); + if (rating < data->best_rating) { + data->best_rating = rating; + data->best_window = window; + } + + return rating > 0; +} + +HWND find_window_top_level(enum window_search_mode mode, + enum window_priority priority, const char *class, + const char *title, const char *exe) +{ + if (!class) + return NULL; + + struct top_level_enum_data data; + data.mode = mode; + data.priority = priority; + data.class = class; + data.title = title; + data.exe = exe; + data.uwp_window = strcmp(class, "Windows.UI.Core.CoreWindow") == 0; + data.best_window = NULL; + data.best_rating = 0x7FFFFFFF; + EnumWindows(enum_windows_proc, (LPARAM)&data); + return data.best_window; +} diff --git a/plugins/win-capture/window-helpers.h b/plugins/win-capture/window-helpers.h index ffcd7cefc..1537b57a3 100644 --- a/plugins/win-capture/window-helpers.h +++ b/plugins/win-capture/window-helpers.h @@ -31,3 +31,7 @@ extern void build_window_strings(const char *str, char **class, char **title, extern HWND find_window(enum window_search_mode mode, enum window_priority priority, const char *class, const char *title, const char *exe); +extern HWND find_window_top_level(enum window_search_mode mode, + enum window_priority priority, + const char *class, const char *title, + const char *exe); From ab53f31eb52183559de9b43d4ff86eeb55bdf28d Mon Sep 17 00:00:00 2001 From: jpark37 Date: Thu, 28 Nov 2019 01:47:42 -0800 Subject: [PATCH 06/12] CI: Use VS 2019 and windows-2019 VM image --- CI/install-script-win.cmd | 8 ++++---- azure-pipelines.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CI/install-script-win.cmd b/CI/install-script-win.cmd index 196e0eef9..bc1fab6a5 100644 --- a/CI/install-script-win.cmd +++ b/CI/install-script-win.cmd @@ -17,13 +17,13 @@ set build_config=RelWithDebInfo mkdir build build32 build64 if "%TWITCH-CLIENTID%"=="$(twitch_clientid)" ( cd ./build32 -cmake -G "Visual Studio 15 2017" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_32% .. +cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_32% .. cd ../build64 -cmake -G "Visual Studio 15 2017 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_64% .. +cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_64% .. ) else ( cd ./build32 -cmake -G "Visual Studio 15 2017" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_32% -DTWITCH_CLIENTID="%TWITCH-CLIENTID%" -DTWITCH_HASH="%TWITCH-HASH%" -DMIXER_CLIENTID="%MIXER-CLIENTID%" -DMIXER_HASH="%MIXER-HASH%" -DRESTREAM_CLIENTID="%RESTREAM-CLIENTID%" -DRESTREAM_HASH="%RESTREAM-HASH%" .. +cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_32% -DTWITCH_CLIENTID="%TWITCH-CLIENTID%" -DTWITCH_HASH="%TWITCH-HASH%" -DMIXER_CLIENTID="%MIXER-CLIENTID%" -DMIXER_HASH="%MIXER-HASH%" -DRESTREAM_CLIENTID="%RESTREAM-CLIENTID%" -DRESTREAM_HASH="%RESTREAM-HASH%" .. cd ../build64 -cmake -G "Visual Studio 15 2017 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_64% -DTWITCH_CLIENTID="%TWITCH-CLIENTID%" -DTWITCH_HASH="%TWITCH-HASH%" -DMIXER_CLIENTID="%MIXER-CLIENTID%" -DMIXER_HASH="%MIXER-HASH%" -DRESTREAM_CLIENTID="%RESTREAM-CLIENTID%" -DRESTREAM_HASH="%RESTREAM-HASH%" .. +cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true -DBUILD_CAPTIONS=true -DCOMPILE_D3D12_HOOK=true -DBUILD_BROWSER=true -DCEF_ROOT_DIR=%CEF_64% -DTWITCH_CLIENTID="%TWITCH-CLIENTID%" -DTWITCH_HASH="%TWITCH-HASH%" -DMIXER_CLIENTID="%MIXER-CLIENTID%" -DMIXER_HASH="%MIXER-HASH%" -DRESTREAM_CLIENTID="%RESTREAM-CLIENTID%" -DRESTREAM_HASH="%RESTREAM-HASH%" .. ) cd .. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9b1a2d891..83c38a668 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -62,7 +62,7 @@ jobs: variables: prHasCILabel: $[ dependencies.Prebuild.outputs['checkPrLabel.prHasCILabel'] ] pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2019' steps: - script: git submodule update --init --recursive displayName: 'Checkout Submodules' @@ -89,7 +89,7 @@ jobs: variables: prHasCILabel: $[ dependencies.Prebuild.outputs['checkPrLabel.prHasCILabel'] ] pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2019' steps: - script: git submodule update --init --recursive displayName: 'Checkout Submodules' From d6d27cfab11053aee552285978a60999587527d7 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:38:10 -0800 Subject: [PATCH 07/12] libobs-winrt: Add module for WinRT functionality Currently wraps Windows Graphics Capture API. --- CMakeLists.txt | 1 + libobs-winrt/CMakeLists.txt | 26 +++ libobs-winrt/winrt-capture.cpp | 337 +++++++++++++++++++++++++++++++++ libobs-winrt/winrt-capture.h | 23 +++ 4 files changed, 387 insertions(+) create mode 100644 libobs-winrt/CMakeLists.txt create mode 100644 libobs-winrt/winrt-capture.cpp create mode 100644 libobs-winrt/winrt-capture.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f4b44e00c..c40cdf478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,7 @@ if(NOT INSTALLER_RUN) if(WIN32) add_subdirectory(libobs-d3d11) + add_subdirectory(libobs-winrt) endif() add_subdirectory(libobs-opengl) diff --git a/libobs-winrt/CMakeLists.txt b/libobs-winrt/CMakeLists.txt new file mode 100644 index 000000000..1b778cf93 --- /dev/null +++ b/libobs-winrt/CMakeLists.txt @@ -0,0 +1,26 @@ +project(libobs-winrt) + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") + +add_definitions(-DLIBOBS_EXPORTS) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(libobs-winrt_SOURCES + winrt-capture.cpp) + +set(libobs-winrt_HEADERS + winrt-capture.h) + +add_library(libobs-winrt MODULE + ${libobs-winrt_SOURCES} + ${libobs-winrt_HEADERS}) +set_target_properties(libobs-winrt + PROPERTIES + OUTPUT_NAME libobs-winrt + PREFIX "") +target_link_libraries(libobs-winrt + libobs + windowsapp) + +install_obs_core(libobs-winrt) diff --git a/libobs-winrt/winrt-capture.cpp b/libobs-winrt/winrt-capture.cpp new file mode 100644 index 000000000..fff5311e6 --- /dev/null +++ b/libobs-winrt/winrt-capture.cpp @@ -0,0 +1,337 @@ +#include "winrt-capture.h" + +#include "../libobs/util/windows/ComPtr.hpp" + +#include +#include +#include +#include + +extern "C" { +HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( + ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); + +HRESULT __stdcall CreateDirect3D11SurfaceFromDXGISurface( + ::IDXGISurface *dgxiSurface, ::IInspectable **graphicsSurface); +} + +struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const &id, + void **object) = 0; +}; + +extern "C" EXPORT bool winrt_capture_supported() +{ + /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */ + return winrt::Windows::Foundation::Metadata::ApiInformation:: + IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", + 8); +} + +template +static winrt::com_ptr GetDXGIInterfaceFromObject( + winrt::Windows::Foundation::IInspectable const &object) +{ + auto access = object.as(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; +} + +struct winrt_capture { + bool capture_cursor; + + gs_texture_t *texture; + bool texture_written; + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr}; + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice device{ + nullptr}; + ComPtr context; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool{ + nullptr}; + winrt::Windows::Graphics::Capture::GraphicsCaptureSession session{ + nullptr}; + winrt::Windows::Graphics::SizeInt32 last_size; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool:: + FrameArrived_revoker frame_arrived; + + bool thread_changed; + struct winrt_capture *next; + + void on_frame_arrived(winrt::Windows::Graphics::Capture:: + Direct3D11CaptureFramePool const &sender, + winrt::Windows::Foundation::IInspectable const &) + { + obs_enter_graphics(); + + const winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame + frame = sender.TryGetNextFrame(); + const winrt::Windows::Graphics::SizeInt32 frame_content_size = + frame.ContentSize(); + + winrt::com_ptr frame_surface = + GetDXGIInterfaceFromObject( + frame.Surface()); + + /* need GetDesc because ContentSize is not reliable */ + D3D11_TEXTURE2D_DESC desc; + frame_surface->GetDesc(&desc); + + if (texture) { + if (desc.Width != gs_texture_get_width(texture) || + desc.Height != gs_texture_get_height(texture)) { + gs_texture_destroy(texture); + texture = nullptr; + } + } + + if (!texture) { + texture = gs_texture_create(desc.Width, desc.Height, + GS_BGRA, 1, nullptr, 0); + } + + /* if they gave an SRV, we could avoid this copy */ + context->CopyResource( + (ID3D11Texture2D *)gs_texture_get_obj(texture), + frame_surface.get()); + + texture_written = true; + + if (frame_content_size.Width != last_size.Width || + frame_content_size.Height != last_size.Height) { + frame_pool.Recreate( + device, + winrt::Windows::Graphics::DirectX:: + DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, frame_content_size); + + last_size = frame_content_size; + } + + obs_leave_graphics(); + } +}; + +struct winrt_capture *capture_list; + +static void winrt_capture_device_loss_release(void *data) +{ + winrt_capture *capture = static_cast(data); + capture->frame_arrived.revoke(); + capture->frame_pool.Close(); + capture->session.Close(); + + capture->session = nullptr; + capture->frame_pool = nullptr; + capture->context = nullptr; + capture->device = nullptr; +} + +static void winrt_capture_device_loss_rebuild(void *device_void, void *data) +{ + winrt_capture *capture = static_cast(data); + + ID3D11Device *const d3d_device = (ID3D11Device *)device_void; + ComPtr dxgi_device; + if (FAILED(d3d_device->QueryInterface(&dxgi_device))) + blog(LOG_ERROR, "Failed to get DXGI device"); + + winrt::com_ptr inspectable; + if (FAILED(CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(), + inspectable.put()))) + blog(LOG_ERROR, "Failed to get WinRT device"); + + const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice + device = inspectable.as(); + const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool + frame_pool = winrt::Windows::Graphics::Capture:: + Direct3D11CaptureFramePool::Create( + device, + winrt::Windows::Graphics::DirectX:: + DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, capture->last_size); + const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session = + frame_pool.CreateCaptureSession(capture->item); + + capture->device = device; + d3d_device->GetImmediateContext(&capture->context); + capture->frame_pool = frame_pool; + capture->session = session; + capture->frame_arrived = frame_pool.FrameArrived( + winrt::auto_revoke, + {capture, &winrt_capture::on_frame_arrived}); + + session.StartCapture(); +} + +thread_local bool initialized_tls; + +extern "C" EXPORT struct winrt_capture *winrt_capture_init(bool cursor, + HWND window) +{ + ID3D11Device *const d3d_device = (ID3D11Device *)gs_get_device_obj(); + ComPtr dxgi_device; + if (FAILED(d3d_device->QueryInterface(&dxgi_device))) { + blog(LOG_WARNING, "[winrt_capture_init] Failed to " + "get DXGI device"); + return nullptr; + } + + winrt::com_ptr inspectable; + HRESULT hr = CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(), + inspectable.put()); + if (FAILED(hr)) { + blog(LOG_WARNING, "[winrt_capture_init] Failed to " + "get WinRT device"); + return nullptr; + } + + auto activation_factory = winrt::get_activation_factory< + winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); + auto interop_factory = + activation_factory.as(); + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; + try { + interop_factory->CreateForWindow( + window, + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(item))); + } catch (winrt::hresult_invalid_argument &) { + blog(LOG_WARNING, "[winrt_capture_init] Failed to " + "create GraphicsCaptureItem"); + return nullptr; + } + + const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice + device = inspectable.as(); + const winrt::Windows::Graphics::SizeInt32 size = item.Size(); + const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool + frame_pool = winrt::Windows::Graphics::Capture:: + Direct3D11CaptureFramePool::Create( + device, + winrt::Windows::Graphics::DirectX:: + DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, size); + const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session = + frame_pool.CreateCaptureSession(item); + + if (capture_list == nullptr) + initialized_tls = true; + + struct winrt_capture *capture = new winrt_capture{}; + capture->capture_cursor = cursor; + capture->item = item; + capture->device = device; + d3d_device->GetImmediateContext(&capture->context); + capture->frame_pool = frame_pool; + capture->session = session; + capture->last_size = size; + capture->frame_arrived = frame_pool.FrameArrived( + winrt::auto_revoke, + {capture, &winrt_capture::on_frame_arrived}); + capture->next = capture_list; + capture_list = capture; + + session.StartCapture(); + + gs_device_loss callbacks; + callbacks.device_loss_release = winrt_capture_device_loss_release; + callbacks.device_loss_rebuild = winrt_capture_device_loss_rebuild; + callbacks.data = capture; + gs_register_loss_callbacks(&callbacks); + + return capture; +} + +extern "C" EXPORT void winrt_capture_free(struct winrt_capture *capture) +{ + if (capture) { + struct winrt_capture *current = capture_list; + if (current == capture) { + capture_list = capture->next; + } else { + struct winrt_capture *previous; + do { + previous = current; + current = current->next; + } while (current != capture); + + previous->next = current->next; + } + + obs_enter_graphics(); + gs_unregister_loss_callbacks(capture); + gs_texture_destroy(capture->texture); + obs_leave_graphics(); + + capture->frame_arrived.revoke(); + capture->frame_pool.Close(); + capture->session.Close(); + + delete capture; + } +} + +static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) +{ + gs_texture_t *const texture = capture->texture; + gs_technique_t *tech = gs_effect_get_technique(effect, "Draw"); + gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); + size_t passes; + + gs_effect_set_texture(image, texture); + + passes = gs_technique_begin(tech); + for (size_t i = 0; i < passes; i++) { + if (gs_technique_begin_pass(tech, i)) { + gs_draw_sprite(texture, 0, 0, 0); + + gs_technique_end_pass(tech); + } + } + gs_technique_end(tech); +} + +extern "C" EXPORT void winrt_capture_render(struct winrt_capture *capture, + gs_effect_t *effect) +{ + if (capture && capture->texture_written) { + if (!initialized_tls) { + struct winrt_capture *current = capture_list; + while (current) { + current->thread_changed = true; + current = current->next; + } + + initialized_tls = true; + } + + if (capture->thread_changed) { + /* new graphics thread. treat like device loss. */ + winrt_capture_device_loss_release(capture); + winrt_capture_device_loss_rebuild(gs_get_device_obj(), + capture); + + capture->thread_changed = false; + } + + draw_texture(capture, effect); + } +} + +extern "C" EXPORT int32_t +winrt_capture_width(const struct winrt_capture *capture) +{ + return capture ? capture->last_size.Width : 0; +} + +extern "C" EXPORT int32_t +winrt_capture_height(const struct winrt_capture *capture) +{ + return capture ? capture->last_size.Height : 0; +} diff --git a/libobs-winrt/winrt-capture.h b/libobs-winrt/winrt-capture.h new file mode 100644 index 000000000..a42e05579 --- /dev/null +++ b/libobs-winrt/winrt-capture.h @@ -0,0 +1,23 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +EXPORT bool winrt_capture_supported(); +EXPORT struct winrt_capture *winrt_capture_init(bool cursor, HWND window); +EXPORT void winrt_capture_free(struct winrt_capture *capture); + +EXPORT void winrt_capture_render(struct winrt_capture *capture, + gs_effect_t *effect); +EXPORT int32_t winrt_capture_width(const struct winrt_capture *capture); +EXPORT int32_t winrt_capture_height(const struct winrt_capture *capture); + +#ifdef __cplusplus +} +#endif From ffc48dbbab4e9e8aa1856979200491d28f950a48 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Wed, 27 Nov 2019 16:44:27 -0800 Subject: [PATCH 08/12] win-capture: Windows Graphics Capture support Users will now have the option of legacy window capture via BitBlt, or Windows Graphics Capture, which is new to Windows 10. There are two annoyances with the new capture method though. One is that there is a bright, yellow border added to the original window (but not the OBS view of it). The other is that the mouse cursor is always captured, and we won't be able to capture without cursor until a later version of Windows 10 is released. It should also be noted that DPI scaling is now applied, which may result in blurrier images caused by Windows rescaling. --- plugins/win-capture/data/locale/en-US.ini | 3 + plugins/win-capture/window-capture.c | 200 +++++++++++++++++++--- 2 files changed, 175 insertions(+), 28 deletions(-) diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index cdcbdf918..feaf8166a 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -1,6 +1,9 @@ MonitorCapture="Display Capture" WindowCapture="Window Capture" WindowCapture.Window="Window" +WindowCapture.Method="Capture Method" +WindowCapture.Method.BitBlt="BitBlt (Windows 7 and up)" +WindowCapture.Method.WindowsGraphicsCapture="Windows Graphics Capture (Windows 10 1903 and up)" WindowCapture.Priority="Window Match Priority" WindowCapture.Priority.Title="Window title must match" WindowCapture.Priority.Class="Match title, otherwise find window of same type" diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c index 57cc20cd6..95e471449 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -2,11 +2,16 @@ #include #include "dc-capture.h" #include "window-helpers.h" +#include "../../libobs/util/platform.h" +#include "../../libobs-winrt/winrt-capture.h" /* clang-format off */ #define TEXT_WINDOW_CAPTURE obs_module_text("WindowCapture") #define TEXT_WINDOW obs_module_text("WindowCapture.Window") +#define TEXT_METHOD obs_module_text("WindowCapture.Method") +#define TEXT_METHOD_BITBLT obs_module_text("WindowCapture.Method.BitBlt") +#define TEXT_METHOD_WGC obs_module_text("WindowCapture.Method.WindowsGraphicsCapture") #define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority") #define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title") #define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class") @@ -18,12 +23,28 @@ #define WC_CHECK_TIMER 1.0f +struct winrt_exports { + bool *(*winrt_capture_supported)(); + struct winrt_capture *(*winrt_capture_init)(bool cursor, HWND window); + void (*winrt_capture_free)(struct winrt_capture *capture); + void (*winrt_capture_render)(struct winrt_capture *capture, + gs_effect_t *effect); + int32_t (*winrt_capture_width)(const struct winrt_capture *capture); + int32_t (*winrt_capture_height)(const struct winrt_capture *capture); +}; + +enum window_capture_method { + METHOD_BITBLT, + METHOD_WGC, +}; + struct window_capture { obs_source_t *source; char *title; char *class; char *executable; + enum window_capture_method method; enum window_priority priority; bool cursor; bool compatibility; @@ -31,6 +52,11 @@ struct window_capture { struct dc_capture capture; + bool wgc_supported; + void *winrt_module; + struct winrt_exports exports; + struct winrt_capture *capture_winrt; + float resize_timer; float check_window_timer; float cursor_check_time; @@ -41,6 +67,7 @@ struct window_capture { static void update_settings(struct window_capture *wc, obs_data_t *s) { + int method = (int)obs_data_get_int(s, "method"); const char *window = obs_data_get_string(s, "window"); int priority = (int)obs_data_get_int(s, "priority"); @@ -58,6 +85,10 @@ static void update_settings(struct window_capture *wc, obs_data_t *s) blog(LOG_DEBUG, "\tclass: %s", wc->class); } + if ((method == METHOD_WGC) && !wc->wgc_supported) + method = METHOD_BITBLT; + + wc->method = method; wc->priority = (enum window_priority)priority; wc->cursor = obs_data_get_bool(s, "cursor"); wc->use_wildcards = obs_data_get_bool(s, "use_wildcards"); @@ -72,11 +103,54 @@ static const char *wc_getname(void *unused) return TEXT_WINDOW_CAPTURE; } +#define WINRT_IMPORT(func) \ + do { \ + exports->func = os_dlsym(module, #func); \ + if (!exports->func) { \ + success = false; \ + blog(LOG_ERROR, \ + "Could not load function '%s' from " \ + "module '%s'", \ + #func, module_name); \ + } \ + } while (false) + +static bool load_winrt_imports(struct winrt_exports *exports, void *module, + const char *module_name) +{ + bool success = true; + + WINRT_IMPORT(winrt_capture_supported); + WINRT_IMPORT(winrt_capture_init); + WINRT_IMPORT(winrt_capture_free); + WINRT_IMPORT(winrt_capture_render); + WINRT_IMPORT(winrt_capture_width); + WINRT_IMPORT(winrt_capture_height); + + return success; +} + static void *wc_create(obs_data_t *settings, obs_source_t *source) { struct window_capture *wc = bzalloc(sizeof(struct window_capture)); wc->source = source; + obs_enter_graphics(); + const bool uses_d3d11 = gs_get_device_type() == GS_DEVICE_DIRECT3D_11; + obs_leave_graphics(); + + if (uses_d3d11) { + static const char *const module = "libobs-winrt"; + bool use_winrt_capture = false; + wc->winrt_module = os_dlopen(module); + if (wc->winrt_module && + load_winrt_imports(&wc->exports, wc->winrt_module, + module) && + wc->exports.winrt_capture_supported()) { + wc->wgc_supported = true; + } + } + update_settings(wc, settings); return wc; } @@ -94,6 +168,9 @@ static void wc_destroy(void *data) bfree(wc->class); bfree(wc->executable); + if (wc->winrt_module) + os_dlclose(wc->winrt_module); + bfree(wc); } } @@ -111,28 +188,55 @@ static void wc_update(void *data, obs_data_t *settings) static uint32_t wc_width(void *data) { struct window_capture *wc = data; - return wc->capture.width; + return (wc->method == METHOD_WGC) + ? wc->exports.winrt_capture_width(wc->capture_winrt) + : wc->capture.width; } static uint32_t wc_height(void *data) { struct window_capture *wc = data; - return wc->capture.height; + return (wc->method == METHOD_WGC) + ? wc->exports.winrt_capture_height(wc->capture_winrt) + : wc->capture.height; } static void wc_defaults(obs_data_t *defaults) { + obs_data_set_default_int(defaults, "method", METHOD_BITBLT); obs_data_set_default_bool(defaults, "cursor", true); obs_data_set_default_bool(defaults, "compatibility", false); } -static obs_properties_t *wc_properties(void *unused) +static bool wc_capture_method_changed(obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) { - UNUSED_PARAMETER(unused); + const int method = (int)obs_data_get_int(settings, "method"); + const bool use_bitblt = method == METHOD_BITBLT; + + p = obs_properties_get(props, "cursor"); + obs_property_set_visible(p, use_bitblt); + + p = obs_properties_get(props, "compatibility"); + obs_property_set_visible(p, use_bitblt); + + return true; +} + +static obs_properties_t *wc_properties(void *data) +{ + struct window_capture *wc = data; obs_properties_t *ppts = obs_properties_create(); obs_property_t *p; + p = obs_properties_add_list(ppts, "method", TEXT_METHOD, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, TEXT_METHOD_BITBLT, METHOD_BITBLT); + obs_property_list_add_int(p, TEXT_METHOD_WGC, METHOD_WGC); + obs_property_list_item_disable(p, 1, !wc->wgc_supported); + obs_property_set_modified_callback(p, wc_capture_method_changed); + p = obs_properties_add_list(ppts, "window", TEXT_WINDOW, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); @@ -151,6 +255,18 @@ static obs_properties_t *wc_properties(void *unused) return ppts; } +static void wc_hide(void *data) +{ + struct window_capture *wc = data; + + if (wc->capture_winrt) { + wc->exports.winrt_capture_free(wc->capture_winrt); + wc->capture_winrt = NULL; + } + + memset(&wc->last_rect, 0, sizeof(wc->last_rect)); +} + #define RESIZE_CHECK_TIME 0.2f #define CURSOR_CHECK_TIME 0.2f @@ -175,10 +291,22 @@ static void wc_tick(void *data, float seconds) return; } + if (wc->capture_winrt) { + wc->exports.winrt_capture_free(wc->capture_winrt); + wc->capture_winrt = NULL; + } + wc->check_window_timer = 0.0f; - wc->window = find_window(EXCLUDE_MINIMIZED, wc->priority, - wc->class, wc->title, wc->executable); + wc->window = (wc->method == METHOD_WGC) + ? find_window_top_level(EXCLUDE_MINIMIZED, + wc->priority, + wc->class, + wc->title, + wc->executable) + : find_window(EXCLUDE_MINIMIZED, + wc->priority, wc->class, + wc->title, wc->executable); if (!wc->window) { if (wc->capture.valid) dc_capture_free(&wc->capture); @@ -203,47 +331,62 @@ static void wc_tick(void *data, float seconds) if (!GetWindowThreadProcessId(wc->window, &target_pid)) target_pid = 0; - if (foreground_pid && target_pid && - foreground_pid != target_pid) - wc->capture.cursor_hidden = true; - else - wc->capture.cursor_hidden = false; + wc->capture.cursor_hidden = foreground_pid && target_pid && + foreground_pid != target_pid; wc->cursor_check_time = 0.0f; } obs_enter_graphics(); - GetClientRect(wc->window, &rect); + if (wc->method == METHOD_BITBLT) { + GetClientRect(wc->window, &rect); - if (!reset_capture) { - wc->resize_timer += seconds; + if (!reset_capture) { + wc->resize_timer += seconds; - if (wc->resize_timer >= RESIZE_CHECK_TIME) { - if (rect.bottom != wc->last_rect.bottom || - rect.right != wc->last_rect.right) - reset_capture = true; + if (wc->resize_timer >= RESIZE_CHECK_TIME) { + if ((rect.bottom - rect.top) != + (wc->last_rect.bottom - + wc->last_rect.top) || + (rect.right - rect.left) != + (wc->last_rect.right - + wc->last_rect.left)) + reset_capture = true; + wc->resize_timer = 0.0f; + } + } + + if (reset_capture) { wc->resize_timer = 0.0f; + wc->last_rect = rect; + dc_capture_free(&wc->capture); + dc_capture_init(&wc->capture, 0, 0, + rect.right - rect.left, + rect.bottom - rect.top, wc->cursor, + wc->compatibility); + } + + dc_capture_capture(&wc->capture, wc->window); + } else if (wc->method == METHOD_WGC) { + if (wc->window && (wc->capture_winrt == NULL)) { + wc->capture_winrt = wc->exports.winrt_capture_init( + wc->cursor, wc->window); } } - if (reset_capture) { - wc->resize_timer = 0.0f; - wc->last_rect = rect; - dc_capture_free(&wc->capture); - dc_capture_init(&wc->capture, 0, 0, rect.right, rect.bottom, - wc->cursor, wc->compatibility); - } - - dc_capture_capture(&wc->capture, wc->window); obs_leave_graphics(); } static void wc_render(void *data, gs_effect_t *effect) { struct window_capture *wc = data; - dc_capture_render(&wc->capture, obs_get_base_effect(OBS_EFFECT_OPAQUE)); + gs_effect_t *const opaque = obs_get_base_effect(OBS_EFFECT_OPAQUE); + if (wc->method == METHOD_WGC) + wc->exports.winrt_capture_render(wc->capture_winrt, opaque); + else + dc_capture_render(&wc->capture, opaque); UNUSED_PARAMETER(effect); } @@ -257,6 +400,7 @@ struct obs_source_info window_capture_info = { .destroy = wc_destroy, .update = wc_update, .video_render = wc_render, + .hide = wc_hide, .video_tick = wc_tick, .get_width = wc_width, .get_height = wc_height, From 74c7375096c151bb9bbbdadba7d32dc0e9b1322e Mon Sep 17 00:00:00 2001 From: jpark37 Date: Tue, 3 Dec 2019 08:20:40 -0800 Subject: [PATCH 09/12] libobs-winrt: Add PCH Reduces compile time from six seconds to less than a second. --- libobs-winrt/CMakeLists.txt | 8 ++++++++ libobs-winrt/winrt-capture.cpp | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libobs-winrt/CMakeLists.txt b/libobs-winrt/CMakeLists.txt index 1b778cf93..f6a88326c 100644 --- a/libobs-winrt/CMakeLists.txt +++ b/libobs-winrt/CMakeLists.txt @@ -19,6 +19,14 @@ set_target_properties(libobs-winrt PROPERTIES OUTPUT_NAME libobs-winrt PREFIX "") +target_precompile_headers(libobs-winrt + PRIVATE + [["../libobs/util/windows/ComPtr.hpp"]] + + + + + ) target_link_libraries(libobs-winrt libobs windowsapp) diff --git a/libobs-winrt/winrt-capture.cpp b/libobs-winrt/winrt-capture.cpp index fff5311e6..b426d1a16 100644 --- a/libobs-winrt/winrt-capture.cpp +++ b/libobs-winrt/winrt-capture.cpp @@ -1,12 +1,3 @@ -#include "winrt-capture.h" - -#include "../libobs/util/windows/ComPtr.hpp" - -#include -#include -#include -#include - extern "C" { HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); From 039c08144adbb3af300a3a1dedacaa48ced94391 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 20 Feb 2020 22:53:57 -0800 Subject: [PATCH 10/12] CMake: Rquire CMake 3.16 The recent graphics capture changes require CMake 3.16 or above to function on Windows, so add a requirement check for 3.16 if on Windows. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c40cdf478..f0155d493 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ project(obs-studio) option(BUILD_CAPTIONS "Build captions" FALSE) if(WIN32) + cmake_minimum_required(VERSION 3.16) if (QTDIR OR DEFINED ENV{QTDIR} OR DEFINED ENV{QTDIR32} OR DEFINED ENV{QTDIR64}) # Qt path set by user or env var else() From cc5918fdb419938a47fcf5662aadc2d8a15373e4 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 20 Feb 2020 22:55:00 -0800 Subject: [PATCH 11/12] CMake: Add check for Win SDK 10.0.18362.0 or above The graphics capture changes require Windows SDK version 10.0.18362.0 or above to function, so check for that in CMake. --- CMakeLists.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0155d493..304167886 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,34 @@ option(BUILD_CAPTIONS "Build captions" FALSE) if(WIN32) cmake_minimum_required(VERSION 3.16) + + # Check for Win SDK version 10.0.18362 or above + if(MSVC AND MSVC_VERSION LESS 1920) + message(STATUS "Windows API version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") + string(REPLACE "." ";" WINAPI_VER "${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") + + list(GET WINAPI_VER 0 WINAPI_VER_MAJOR) + list(GET WINAPI_VER 1 WINAPI_VER_MINOR) + list(GET WINAPI_VER 2 WINAPI_VER_BUILD) + + set(WINAPI_COMPATIBLE FALSE) + if(WINAPI_VER_MAJOR EQUAL 10) + if (WINAPI_VER_MINOR EQUAL 0) + if (WINAPI_VER_BUILD GREATER_EQUAL 18362) + set(WINAPI_COMPATIBLE TRUE) + endif() + else() + set(WINAPI_COMPATIBLE TRUE) + endif() + elseif(WINAPI_VER_MAJOR GREATER 10) + set(WINAPI_COMPATIBLE TRUE) + endif() + + if(NOT WINAPI_COMPATIBLE) + message(FATAL_ERROR "OBS requires Windows 10 SDK version 10.0.18362.0 and above to compile.\nPlease download the most recent Windows 10 SDK in order to compile (or update to Visual Studio 2019).") + endif() + endif() + if (QTDIR OR DEFINED ENV{QTDIR} OR DEFINED ENV{QTDIR32} OR DEFINED ENV{QTDIR64}) # Qt path set by user or env var else() From 572ce7311570a2442ad7ea99abe1ae9071fb37c5 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Fri, 21 Feb 2020 22:32:38 -0800 Subject: [PATCH 12/12] win-capture: Add 'auto' method to window capture Adds an "Automatic" method to the capture method property of window capture. This allows the ability for window capture to automatically determine the best capture method to use. Primarily, it prefers BitBlt in most cases, but will use WGC when it detects the window is a browser, microsoft office, or a UWP program. This was implemented because the new capture method has a number of undesired effects associated with it -- the issue of the capture border that we can do nothing about, the fact that we can't control the capture of the cursor, and the fact that Microsoft designed it to switch the cursor away from hardware cursor mode when the capture is active (there was absolutely no reason to do this because even OBS can capture the hardware cursor with no issue). Until we get a new version of this API that doesn't absolutely blow, we're stuck preferring BitBlt instead. But hey, at least people will be able to capture browser windows now. --- plugins/win-capture/data/locale/en-US.ini | 1 + plugins/win-capture/window-capture.c | 62 +++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index feaf8166a..9c517e5aa 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -2,6 +2,7 @@ MonitorCapture="Display Capture" WindowCapture="Window Capture" WindowCapture.Window="Window" WindowCapture.Method="Capture Method" +WindowCapture.Method.Auto="Automatic" WindowCapture.Method.BitBlt="BitBlt (Windows 7 and up)" WindowCapture.Method.WindowsGraphicsCapture="Windows Graphics Capture (Windows 10 1903 and up)" WindowCapture.Priority="Window Match Priority" diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c index 95e471449..f7bc4a3a8 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -10,6 +10,7 @@ #define TEXT_WINDOW_CAPTURE obs_module_text("WindowCapture") #define TEXT_WINDOW obs_module_text("WindowCapture.Window") #define TEXT_METHOD obs_module_text("WindowCapture.Method") +#define TEXT_METHOD_AUTO obs_module_text("WindowCapture.Method.Auto") #define TEXT_METHOD_BITBLT obs_module_text("WindowCapture.Method.BitBlt") #define TEXT_METHOD_WGC obs_module_text("WindowCapture.Method.WindowsGraphicsCapture") #define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority") @@ -34,6 +35,7 @@ struct winrt_exports { }; enum window_capture_method { + METHOD_AUTO, METHOD_BITBLT, METHOD_WGC, }; @@ -46,6 +48,7 @@ struct window_capture { char *executable; enum window_capture_method method; enum window_priority priority; + bool auto_choose_method; bool cursor; bool compatibility; bool use_wildcards; /* TODO */ @@ -85,11 +88,13 @@ static void update_settings(struct window_capture *wc, obs_data_t *s) blog(LOG_DEBUG, "\tclass: %s", wc->class); } - if ((method == METHOD_WGC) && !wc->wgc_supported) + if (!wc->wgc_supported) { method = METHOD_BITBLT; + } wc->method = method; wc->priority = (enum window_priority)priority; + wc->auto_choose_method = (method == METHOD_AUTO); wc->cursor = obs_data_get_bool(s, "cursor"); wc->use_wildcards = obs_data_get_bool(s, "use_wildcards"); wc->compatibility = obs_data_get_bool(s, "compatibility"); @@ -203,7 +208,7 @@ static uint32_t wc_height(void *data) static void wc_defaults(obs_data_t *defaults) { - obs_data_set_default_int(defaults, "method", METHOD_BITBLT); + obs_data_set_default_int(defaults, "method", METHOD_AUTO); obs_data_set_default_bool(defaults, "cursor", true); obs_data_set_default_bool(defaults, "compatibility", false); } @@ -212,13 +217,13 @@ static bool wc_capture_method_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings) { const int method = (int)obs_data_get_int(settings, "method"); - const bool use_bitblt = method == METHOD_BITBLT; + const bool show_options = method != METHOD_WGC; p = obs_properties_get(props, "cursor"); - obs_property_set_visible(p, use_bitblt); + obs_property_set_visible(p, show_options); p = obs_properties_get(props, "compatibility"); - obs_property_set_visible(p, use_bitblt); + obs_property_set_visible(p, show_options); return true; } @@ -232,6 +237,7 @@ static obs_properties_t *wc_properties(void *data) p = obs_properties_add_list(ppts, "method", TEXT_METHOD, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, TEXT_METHOD_AUTO, METHOD_AUTO); obs_property_list_add_int(p, TEXT_METHOD_BITBLT, METHOD_BITBLT); obs_property_list_add_int(p, TEXT_METHOD_WGC, METHOD_WGC); obs_property_list_item_disable(p, 1, !wc->wgc_supported); @@ -267,6 +273,48 @@ static void wc_hide(void *data) memset(&wc->last_rect, 0, sizeof(wc->last_rect)); } +static const char *wgc_partial_match_classes[] = { + "Chrome", + "Mozilla", + NULL, +}; + +static const char *wgc_whole_match_classes[] = { + "ApplicationFrameWindow", + "Windows.UI.Core.CoreWindow", + "XLMAIN", /* excel*/ + "PPTFrameClass", /* powerpoint */ + "OpusApp", /* word */ + NULL, +}; + +static void auto_choose_method(struct window_capture *wc) +{ + wc->method = METHOD_BITBLT; + + if (!wc->class) { + return; + } + + const char **class = wgc_partial_match_classes; + while (*class) { + if (astrstri(wc->class, *class) != NULL) { + wc->method = METHOD_WGC; + return; + } + class ++; + } + + class = wgc_whole_match_classes; + while (*class) { + if (astrcmpi(wc->class, *class) == 0) { + wc->method = METHOD_WGC; + return; + } + class ++; + } +} + #define RESIZE_CHECK_TIME 0.2f #define CURSOR_CHECK_TIME 0.2f @@ -296,6 +344,10 @@ static void wc_tick(void *data, float seconds) wc->capture_winrt = NULL; } + if (wc->auto_choose_method) { + auto_choose_method(wc); + } + wc->check_window_timer = 0.0f; wc->window = (wc->method == METHOD_WGC)