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/CMakeLists.txt b/CMakeLists.txt index f4b44e00c..304167886 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,35 @@ project(obs-studio) 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() @@ -191,6 +220,7 @@ if(NOT INSTALLER_RUN) if(WIN32) add_subdirectory(libobs-d3d11) + add_subdirectory(libobs-winrt) endif() add_subdirectory(libobs-opengl) 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' 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-winrt/CMakeLists.txt b/libobs-winrt/CMakeLists.txt new file mode 100644 index 000000000..f6a88326c --- /dev/null +++ b/libobs-winrt/CMakeLists.txt @@ -0,0 +1,34 @@ +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_precompile_headers(libobs-winrt + PRIVATE + [["../libobs/util/windows/ComPtr.hpp"]] + + + + + ) +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..b426d1a16 --- /dev/null +++ b/libobs-winrt/winrt-capture.cpp @@ -0,0 +1,328 @@ +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 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 */ 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, 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); 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! */ diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index cdcbdf918..9c517e5aa 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -1,6 +1,10 @@ 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" 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..f7bc4a3a8 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -2,11 +2,17 @@ #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_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") #define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title") #define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class") @@ -18,19 +24,42 @@ #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_AUTO, + 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 auto_choose_method; bool cursor; bool compatibility; bool use_wildcards; /* TODO */ 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 +70,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,7 +88,13 @@ static void update_settings(struct window_capture *wc, obs_data_t *s) blog(LOG_DEBUG, "\tclass: %s", wc->class); } + 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"); @@ -72,11 +108,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 +173,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 +193,56 @@ 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_AUTO); 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 show_options = method != METHOD_WGC; + + p = obs_properties_get(props, "cursor"); + obs_property_set_visible(p, show_options); + + p = obs_properties_get(props, "compatibility"); + obs_property_set_visible(p, show_options); + + 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_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); + 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 +261,60 @@ 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)); +} + +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 @@ -175,10 +339,26 @@ 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; + } + + if (wc->auto_choose_method) { + auto_choose_method(wc); + } + 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 +383,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 +452,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, 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);