Browse Source

Merge pull request #2208 from jpark37/screen-capture

Windows Graphics Capture support
Jim 5 years ago
parent
commit
4d15e76c5b

+ 4 - 4
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 ..

+ 30 - 0
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)

+ 2 - 2
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'

+ 7 - 0
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);
 

+ 19 - 0
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;
+		}
+	}
+}

+ 1 - 0
libobs-d3d11/d3d11-subsystem.hpp

@@ -946,6 +946,7 @@ struct gs_device {
 	matrix4 curViewMatrix;
 	matrix4 curViewProjMatrix;
 
+	vector<gs_device_loss> loss_callbacks;
 	gs_obj *first_obj = nullptr;
 
 	void InitCompiler();

+ 34 - 0
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"]]
+		<obs-module.h>
+		<d3d11.h>
+		<Windows.Graphics.Capture.Interop.h>
+		<winrt/Windows.Foundation.Metadata.h>
+		<winrt/Windows.Graphics.Capture.h>)
+target_link_libraries(libobs-winrt
+	libobs
+	windowsapp)
+
+install_obs_core(libobs-winrt)

+ 328 - 0
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<typename T>
+static winrt::com_ptr<T> GetDXGIInterfaceFromObject(
+	winrt::Windows::Foundation::IInspectable const &object)
+{
+	auto access = object.as<IDirect3DDxgiInterfaceAccess>();
+	winrt::com_ptr<T> result;
+	winrt::check_hresult(
+		access->GetInterface(winrt::guid_of<T>(), 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<ID3D11DeviceContext> 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<ID3D11Texture2D> frame_surface =
+			GetDXGIInterfaceFromObject<ID3D11Texture2D>(
+				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<winrt_capture *>(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<winrt_capture *>(data);
+
+	ID3D11Device *const d3d_device = (ID3D11Device *)device_void;
+	ComPtr<IDXGIDevice> dxgi_device;
+	if (FAILED(d3d_device->QueryInterface(&dxgi_device)))
+		blog(LOG_ERROR, "Failed to get DXGI device");
+
+	winrt::com_ptr<IInspectable> 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<winrt::Windows::Graphics::DirectX::
+						Direct3D11::IDirect3DDevice>();
+	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<IDXGIDevice> 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<IInspectable> 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<IGraphicsCaptureItemInterop>();
+	winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
+	try {
+		interop_factory->CreateForWindow(
+			window,
+			winrt::guid_of<ABI::Windows::Graphics::Capture::
+					       IGraphicsCaptureItem>(),
+			reinterpret_cast<void **>(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<winrt::Windows::Graphics::DirectX::
+						Direct3D11::IDirect3DDevice>();
+	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;
+}

+ 23 - 0
libobs-winrt/winrt-capture.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <obs-module.h>
+
+#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

+ 2 - 0
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;

+ 4 - 0
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
 };
 

+ 24 - 0
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

+ 9 - 0
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 */

+ 4 - 0
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,

+ 13 - 0
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 <windows.h>
+#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);

+ 4 - 0
libobs/util/windows/ComPtr.hpp

@@ -16,6 +16,10 @@
 
 #pragma once
 
+#ifdef _WIN32
+#include <Unknwn.h>
+#endif
+
 /* Oh no I have my own com pointer class, the world is ending, how dare you
  * write your own! */
 

+ 4 - 0
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"

+ 223 - 27
plugins/win-capture/window-capture.c

@@ -2,11 +2,17 @@
 #include <util/dstr.h>
 #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);
 		}
-	}
 
-	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);
+	} 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);
+		}
 	}
 
-	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,

+ 49 - 0
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;
+}

+ 4 - 0
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);