Merge pull request #2208 from jpark37/screen-capture

Windows Graphics Capture support
This commit is contained in:
Jim 2020-02-23 00:18:14 -08:00 committed by GitHub
commit 4d15e76c5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 789 additions and 34 deletions

View File

@ -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 ..

View File

@ -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)

View File

@ -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'

View File

@ -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);

View File

@ -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;
}
}
}

View File

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

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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
};

View File

@ -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

View File

@ -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 */

View File

@ -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,

View File

@ -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);

View File

@ -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! */

View File

@ -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"

View File

@ -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);
}
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,

View File

@ -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;
}

View File

@ -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);