mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-01-30 03:32:43 +00:00
feat(win): new capture method, Windows.Graphics.Capture (#2580)
This commit is contained in:
parent
2d706d3104
commit
287ac4c0fb
@ -13,6 +13,7 @@ pacman --noconfirm -S \
|
||||
make \
|
||||
mingw-w64-ucrt-x86_64-boost \
|
||||
mingw-w64-ucrt-x86_64-cmake \
|
||||
mingw-w64-ucrt-x86_64-cppwinrt \
|
||||
mingw-w64-ucrt-x86_64-curl \
|
||||
mingw-w64-ucrt-x86_64-graphviz \
|
||||
mingw-w64-ucrt-x86_64-miniupnpc \
|
||||
|
1
.github/workflows/CI.yml
vendored
1
.github/workflows/CI.yml
vendored
@ -942,6 +942,7 @@ jobs:
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-boost
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-cppwinrt
|
||||
mingw-w64-ucrt-x86_64-curl
|
||||
mingw-w64-ucrt-x86_64-graphviz
|
||||
mingw-w64-ucrt-x86_64-miniupnpc
|
||||
|
@ -9,6 +9,9 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
# gcc complains about misleading indentation in some mingw includes
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation)
|
||||
|
||||
# see gcc bug 98723
|
||||
add_definitions(-DUSE_BOOST_REGEX)
|
||||
|
||||
# curl
|
||||
add_definitions(-DCURL_STATICLIB)
|
||||
include_directories(SYSTEM ${CURL_STATIC_INCLUDE_DIRS})
|
||||
@ -47,6 +50,7 @@ set(PLATFORM_TARGET_FILES
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_base.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_vram.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h"
|
||||
|
@ -3,4 +3,5 @@ set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
|
||||
find_library(ZLIB ZLIB1)
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
Windowsapp.lib
|
||||
Wtsapi32.lib)
|
||||
|
@ -216,7 +216,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
.. code-block:: text
|
||||
|
||||
gamepad = auto
|
||||
|
||||
|
||||
`ds4_back_as_touchpad_click <https://localhost:47990/config/#ds4_back_as_touchpad_click>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -394,7 +394,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
When enabled, Sunshine will pass through native pen/touch events from Moonlight clients.
|
||||
When enabled, Sunshine will pass through native pen/touch events from Moonlight clients.
|
||||
|
||||
This can be useful to disable for older applications without native pen/touch support.
|
||||
|
||||
@ -1113,25 +1113,25 @@ keybindings
|
||||
**Description**
|
||||
Force specific screen capture method.
|
||||
|
||||
.. caution:: Applies to Linux only.
|
||||
|
||||
**Choices**
|
||||
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
========= ===========
|
||||
Value Description
|
||||
========= ===========
|
||||
nvfbc Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for
|
||||
NVIDIA cards. For GeForce cards it will only work with drivers patched with
|
||||
`nvidia-patch <https://github.com/keylase/nvidia-patch/>`__
|
||||
or `nvlax <https://github.com/illnyang/nvlax/>`__.
|
||||
wlr Capture for wlroots based Wayland compositors via DMA-BUF.
|
||||
kms DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability.
|
||||
See :ref:`Linux Setup <about/setup:install>`.
|
||||
x11 Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible.
|
||||
========= ===========
|
||||
========= ======== ===========
|
||||
Value Platform Description
|
||||
========= ======== ===========
|
||||
nvfbc Linux Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for
|
||||
NVIDIA cards. For GeForce cards it will only work with drivers patched with
|
||||
`nvidia-patch <https://github.com/keylase/nvidia-patch/>`__
|
||||
or `nvlax <https://github.com/illnyang/nvlax/>`__.
|
||||
wlr Linux Capture for wlroots based Wayland compositors via DMA-BUF.
|
||||
kms Linux DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability.
|
||||
See :ref:`Linux Setup <about/setup:install>`.
|
||||
x11 Linux Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible.
|
||||
ddx Windows Use DirectX Desktop Duplication API to capture the display. This is well-supported on Windows machines.
|
||||
wgc Windows (beta feature) Use Windows.Graphics.Capture to capture the display.
|
||||
========= ======== ===========
|
||||
|
||||
**Default**
|
||||
Automatic. Sunshine will use the first capture method available in the order of the table above.
|
||||
|
@ -19,6 +19,7 @@ Install dependencies:
|
||||
git \
|
||||
mingw-w64-ucrt-x86_64-boost \
|
||||
mingw-w64-ucrt-x86_64-cmake \
|
||||
mingw-w64-ucrt-x86_64-cppwinrt \
|
||||
mingw-w64-ucrt-x86_64-curl \
|
||||
mingw-w64-ucrt-x86_64-graphviz \
|
||||
mingw-w64-ucrt-x86_64-miniupnpc \
|
||||
|
@ -11,6 +11,9 @@
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include <Unknwn.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
@ -153,22 +156,6 @@ namespace platf::dxgi {
|
||||
bool visible;
|
||||
};
|
||||
|
||||
class duplication_t {
|
||||
public:
|
||||
dup_t dup;
|
||||
bool has_frame {};
|
||||
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
|
||||
|
||||
capture_e
|
||||
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
capture_e
|
||||
reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
capture_e
|
||||
release_frame();
|
||||
|
||||
~duplication_t();
|
||||
};
|
||||
|
||||
class display_base_t: public display_t {
|
||||
public:
|
||||
int
|
||||
@ -185,7 +172,6 @@ namespace platf::dxgi {
|
||||
output_t output;
|
||||
device_t device;
|
||||
device_ctx_t device_ctx;
|
||||
duplication_t dup;
|
||||
DXGI_RATIONAL display_refresh_rate;
|
||||
int display_refresh_rate_rounded;
|
||||
|
||||
@ -253,30 +239,32 @@ namespace platf::dxgi {
|
||||
virtual bool
|
||||
get_hdr_metadata(SS_HDR_METADATA &metadata) override;
|
||||
|
||||
const char *
|
||||
dxgi_format_to_string(DXGI_FORMAT format);
|
||||
const char *
|
||||
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
virtual std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() = 0;
|
||||
|
||||
protected:
|
||||
int
|
||||
get_pixel_pitch() {
|
||||
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
|
||||
}
|
||||
|
||||
const char *
|
||||
dxgi_format_to_string(DXGI_FORMAT format);
|
||||
const char *
|
||||
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
|
||||
virtual capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
|
||||
virtual capture_e
|
||||
release_snapshot() = 0;
|
||||
virtual int
|
||||
complete_img(img_t *img, bool dummy) = 0;
|
||||
virtual std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display component for devices that use software encoders.
|
||||
*/
|
||||
class display_ram_t: public display_base_t {
|
||||
public:
|
||||
virtual capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override;
|
||||
int
|
||||
@ -286,22 +274,18 @@ namespace platf::dxgi {
|
||||
std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() override;
|
||||
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
cursor_t cursor;
|
||||
D3D11_MAPPED_SUBRESOURCE img_info;
|
||||
texture2d_t texture;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display component for devices that use hardware encoders.
|
||||
*/
|
||||
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
|
||||
public:
|
||||
virtual capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override;
|
||||
int
|
||||
@ -311,9 +295,6 @@ namespace platf::dxgi {
|
||||
std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() override;
|
||||
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
bool
|
||||
is_codec_supported(std::string_view name, const ::video::config_t &config) override;
|
||||
|
||||
@ -323,6 +304,59 @@ namespace platf::dxgi {
|
||||
std::unique_ptr<nvenc_encode_device_t>
|
||||
make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display duplicator that uses the DirectX Desktop Duplication API.
|
||||
*/
|
||||
class duplication_t {
|
||||
public:
|
||||
dup_t dup;
|
||||
bool has_frame {};
|
||||
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
|
||||
|
||||
int
|
||||
init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e
|
||||
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
capture_e
|
||||
reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
capture_e
|
||||
release_frame();
|
||||
|
||||
~duplication_t();
|
||||
};
|
||||
|
||||
/**
|
||||
* Display backend that uses DDAPI with a software encoder.
|
||||
*/
|
||||
class display_ddup_ram_t: public display_ram_t {
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
cursor_t cursor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display backend that uses DDAPI with a hardware encoder.
|
||||
*/
|
||||
class display_ddup_vram_t: public display_vram_t {
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
sampler_state_t sampler_linear;
|
||||
|
||||
blend_t blend_alpha;
|
||||
@ -338,7 +372,64 @@ namespace platf::dxgi {
|
||||
texture2d_t old_surface_delayed_destruction;
|
||||
std::chrono::steady_clock::time_point old_surface_timestamp;
|
||||
std::variant<std::monostate, texture2d_t, std::shared_ptr<platf::img_t>> last_frame_variant;
|
||||
};
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
/**
|
||||
* Display duplicator that uses the Windows.Graphics.Capture API.
|
||||
*/
|
||||
class wgc_capture_t {
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device { nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item { nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool { nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session { nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame { nullptr }, consumed_frame { nullptr };
|
||||
SRWLOCK frame_lock = SRWLOCK_INIT;
|
||||
CONDITION_VARIABLE frame_present_cv;
|
||||
|
||||
void
|
||||
on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
|
||||
|
||||
public:
|
||||
wgc_capture_t();
|
||||
~wgc_capture_t();
|
||||
|
||||
int
|
||||
init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e
|
||||
next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
|
||||
capture_e
|
||||
release_frame();
|
||||
int
|
||||
set_cursor_visible(bool);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display backend that uses Windows.Graphics.Capture with a software encoder.
|
||||
*/
|
||||
class display_wgc_ram_t: public display_ram_t {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display backend that uses Windows.Graphics.Capture with a hardware encoder.
|
||||
*/
|
||||
class display_wgc_vram_t: public display_vram_t {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
};
|
||||
} // namespace platf::dxgi
|
||||
|
@ -26,6 +26,91 @@ namespace platf {
|
||||
namespace platf::dxgi {
|
||||
namespace bp = boost::process;
|
||||
|
||||
/**
|
||||
* DDAPI-specific initialization goes here.
|
||||
*/
|
||||
int
|
||||
duplication_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
HRESULT status;
|
||||
|
||||
// Capture format will be determined from the first call to AcquireNextFrame()
|
||||
display->capture_format = DXGI_FORMAT_UNKNOWN;
|
||||
|
||||
// FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
|
||||
{
|
||||
// IDXGIOutput5 is optional, but can provide improved performance and wide color support
|
||||
dxgi::output5_t output5 {};
|
||||
status = display->output->QueryInterface(IID_IDXGIOutput5, (void **) &output5);
|
||||
if (SUCCEEDED(status)) {
|
||||
// Ask the display implementation which formats it supports
|
||||
auto supported_formats = display->get_supported_capture_formats();
|
||||
if (supported_formats.empty()) {
|
||||
BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We try this twice, in case we still get an error on reinitialization
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
status = output5->DuplicateOutput1((IUnknown *) display->device.get(), 0, supported_formats.size(), supported_formats.data(), &dup);
|
||||
if (SUCCEEDED(status)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
// We don't retry with DuplicateOutput() because we can hit this codepath when we're racing
|
||||
// with mode changes and we don't want to accidentally fall back to suboptimal capture if
|
||||
// we get unlucky and succeed below.
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv;
|
||||
|
||||
dxgi::output1_t output1 {};
|
||||
status = display->output->QueryInterface(IID_IDXGIOutput1, (void **) &output1);
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
status = output1->DuplicateOutput((IUnknown *) display->device.get(), &dup);
|
||||
if (SUCCEEDED(status)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_DESC dup_desc;
|
||||
dup->GetDesc(&dup_desc);
|
||||
|
||||
BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']';
|
||||
BOOST_LOG(info) << "Desktop format ["sv << display->dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']';
|
||||
|
||||
display->display_refresh_rate = dup_desc.ModeDesc.RefreshRate;
|
||||
double display_refresh_rate_decimal = (double) display->display_refresh_rate.Numerator / display->display_refresh_rate.Denominator;
|
||||
BOOST_LOG(info) << "Display refresh rate [" << display_refresh_rate_decimal << "Hz]";
|
||||
BOOST_LOG(info) << "Requested frame rate [" << display->client_frame_rate << "fps]";
|
||||
display->display_refresh_rate_rounded = lround(display_refresh_rate_decimal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
capture_e
|
||||
duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
|
||||
auto capture_status = release_frame();
|
||||
@ -255,7 +340,7 @@ namespace platf::dxgi {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = dup.release_frame();
|
||||
status = release_snapshot();
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
}
|
||||
@ -694,81 +779,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
|
||||
{
|
||||
// IDXGIOutput5 is optional, but can provide improved performance and wide color support
|
||||
dxgi::output5_t output5 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput5, (void **) &output5);
|
||||
if (SUCCEEDED(status)) {
|
||||
// Ask the display implementation which formats it supports
|
||||
auto supported_formats = get_supported_capture_formats();
|
||||
if (supported_formats.empty()) {
|
||||
BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We try this twice, in case we still get an error on reinitialization
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
status = output5->DuplicateOutput1((IUnknown *) device.get(), 0, supported_formats.size(), supported_formats.data(), &dup.dup);
|
||||
if (SUCCEEDED(status)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
// We don't retry with DuplicateOutput() because we can hit this codepath when we're racing
|
||||
// with mode changes and we don't want to accidentally fall back to suboptimal capture if
|
||||
// we get unlucky and succeed below.
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv;
|
||||
|
||||
dxgi::output1_t output1 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput1, (void **) &output1);
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
status = output1->DuplicateOutput((IUnknown *) device.get(), &dup.dup);
|
||||
if (SUCCEEDED(status)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_DESC dup_desc;
|
||||
dup.dup->GetDesc(&dup_desc);
|
||||
|
||||
BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']';
|
||||
BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']';
|
||||
|
||||
display_refresh_rate = dup_desc.ModeDesc.RefreshRate;
|
||||
double display_refresh_rate_decimal = (double) display_refresh_rate.Numerator / display_refresh_rate.Denominator;
|
||||
BOOST_LOG(info) << "Display refresh rate [" << display_refresh_rate_decimal << "Hz]";
|
||||
display_refresh_rate_rounded = lround(display_refresh_rate_decimal);
|
||||
|
||||
client_frame_rate = config.framerate;
|
||||
BOOST_LOG(info) << "Requested frame rate [" << client_frame_rate << "fps]";
|
||||
|
||||
dxgi::output6_t output6 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
|
||||
if (SUCCEEDED(status)) {
|
||||
@ -788,9 +799,6 @@ namespace platf::dxgi {
|
||||
<< "Max Full Luminance : "sv << desc1.MaxFullFrameLuminance << " nits"sv;
|
||||
}
|
||||
|
||||
// Capture format will be determined from the first call to AcquireNextFrame()
|
||||
capture_format = DXGI_FORMAT_UNKNOWN;
|
||||
|
||||
// Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+)
|
||||
timer.reset(CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS));
|
||||
if (!timer) {
|
||||
@ -1046,23 +1054,47 @@ namespace platf::dxgi {
|
||||
} // namespace platf::dxgi
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* Pick a display adapter and capture method.
|
||||
* @param hwdevice_type enables possible use of hardware encoder
|
||||
*/
|
||||
std::shared_ptr<display_t>
|
||||
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_vram_t>();
|
||||
if (config::video.capture == "ddx" || config::video.capture.empty()) {
|
||||
if (hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_ddup_vram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_ram_t>();
|
||||
else if (hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_ddup_ram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config::video.capture == "wgc" || config::video.capture.empty()) {
|
||||
if (hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_wgc_vram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
else if (hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_wgc_ram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ddx and wgc failed
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -177,9 +177,8 @@ namespace platf::dxgi {
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
display_ddup_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
HRESULT status;
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
|
||||
resource_t::pointer res_p {};
|
||||
@ -326,6 +325,11 @@ namespace platf::dxgi {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ddup_ram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
display_ram_t::alloc_img() {
|
||||
auto img = std::make_shared<img_t>();
|
||||
@ -382,8 +386,8 @@ namespace platf::dxgi {
|
||||
}
|
||||
|
||||
int
|
||||
display_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name)) {
|
||||
display_ddup_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -945,9 +945,8 @@ namespace platf::dxgi {
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
display_ddup_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
HRESULT status;
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
|
||||
resource_t::pointer res_p {};
|
||||
@ -1329,9 +1328,14 @@ namespace platf::dxgi {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ddup_vram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
|
||||
int
|
||||
display_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name)) {
|
||||
display_ddup_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1410,6 +1414,80 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
|
||||
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
|
||||
* @param img_out the captured frame is returned here
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param cursor_visible
|
||||
*/
|
||||
capture_e
|
||||
display_wgc_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
texture2d_t src;
|
||||
uint64_t frame_qpc;
|
||||
dup.set_cursor_visible(cursor_visible);
|
||||
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
|
||||
if (capture_status != capture_e::ok)
|
||||
return capture_status;
|
||||
|
||||
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
src->GetDesc(&desc);
|
||||
|
||||
// It's possible for our display enumeration to race with mode changes and result in
|
||||
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
|
||||
if (desc.Width != width_before_rotation || desc.Height != height_before_rotation) {
|
||||
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
// It's also possible for the capture format to change on the fly. If that happens,
|
||||
// reinitialize capture to try format detection again and create new images.
|
||||
if (capture_format != desc.Format) {
|
||||
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t> img;
|
||||
if (!pull_free_image_cb(img))
|
||||
return capture_e::interrupted;
|
||||
|
||||
auto d3d_img = std::static_pointer_cast<img_d3d_t>(img);
|
||||
d3d_img->blank = false; // image is always ready for capture
|
||||
if (complete_img(d3d_img.get(), false) == 0) {
|
||||
texture_lock_helper lock_helper(d3d_img->capture_mutex.get());
|
||||
if (lock_helper.lock()) {
|
||||
device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get());
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "Failed to lock capture texture";
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return capture_e::error;
|
||||
}
|
||||
img_out = img;
|
||||
if (img_out) {
|
||||
img_out->frame_timestamp = frame_timestamp;
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_wgc_vram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
|
||||
int
|
||||
display_wgc_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
display_vram_t::alloc_img() {
|
||||
auto img = std::make_shared<img_d3d_t>();
|
||||
|
325
src/platform/windows/display_wgc.cpp
Normal file
325
src/platform/windows/display_wgc.cpp
Normal file
@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @file src/platform/windows/display_wgc.cpp
|
||||
* @brief WinRT Windows.Graphics.Capture API
|
||||
*/
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.graphics.directx.direct3d11.h>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||
|
||||
extern "C" {
|
||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||
}
|
||||
|
||||
/* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way.
|
||||
* If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio.
|
||||
* If not, then MinGW GCC has a workaround to assign a GUID to a structure.
|
||||
*/
|
||||
struct
|
||||
#if WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
__declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
|
||||
#endif
|
||||
IDirect3DDxgiInterfaceAccess: ::IUnknown {
|
||||
virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0;
|
||||
};
|
||||
} // namespace winrt
|
||||
#if !WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
|
||||
0xA9B3D012, 0x3DF2, 0x4EE3, { 0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1 }
|
||||
// compare with __declspec(uuid(...)) for the struct above.
|
||||
};
|
||||
template <>
|
||||
constexpr auto
|
||||
__mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
|
||||
return GUID__IDirect3DDxgiInterfaceAccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace platf::dxgi {
|
||||
wgc_capture_t::wgc_capture_t() {
|
||||
InitializeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
|
||||
wgc_capture_t::~wgc_capture_t() {
|
||||
if (capture_session)
|
||||
capture_session.Close();
|
||||
if (frame_pool)
|
||||
frame_pool.Close();
|
||||
item = nullptr;
|
||||
capture_session = nullptr;
|
||||
frame_pool = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Windows.Graphics.Capture backend.
|
||||
* @return 0 on success
|
||||
*/
|
||||
int
|
||||
wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
HRESULT status;
|
||||
dxgi::dxgi_t dxgi;
|
||||
winrt::com_ptr<::IInspectable> d3d_comhandle;
|
||||
try {
|
||||
if (!winrt::GraphicsCaptureSession::IsSupported()) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv;
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) {
|
||||
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) {
|
||||
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC output_desc;
|
||||
uwp_device = d3d_comhandle.as<winrt::IDirect3DDevice>();
|
||||
display->output->GetDesc(&output_desc);
|
||||
|
||||
auto monitor_factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
|
||||
if (monitor_factory == nullptr ||
|
||||
FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of<winrt::IGraphicsCaptureItem>(), winrt::put_abi(item)))) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config.dynamicRange)
|
||||
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
else
|
||||
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
try {
|
||||
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size());
|
||||
capture_session = frame_pool.CreateCaptureSession(item);
|
||||
frame_pool.FrameArrived({ this, &wgc_capture_t::on_frame_arrived });
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
capture_session.IsBorderRequired(false);
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
}
|
||||
try {
|
||||
capture_session.StartCapture();
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs in a separate thread spawned by the frame pool and is a producer of frames.
|
||||
* To maintain parity with the original display interface, this frame will be consumed by the capture thread.
|
||||
* Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
|
||||
*/
|
||||
void
|
||||
wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame { nullptr };
|
||||
try {
|
||||
frame = sender.TryGetNextFrame();
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
|
||||
return;
|
||||
}
|
||||
if (frame != nullptr) {
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame)
|
||||
produced_frame.Close();
|
||||
|
||||
produced_frame = frame;
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
WakeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next frame from the producer thread.
|
||||
* If not available, the capture thread blocks until one is, or the wait times out.
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param out a texture containing the frame just captured
|
||||
* @param out_time the timestamp of the frame just captured
|
||||
*/
|
||||
capture_e
|
||||
wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
|
||||
// this CONSUMER runs in the capture thread
|
||||
release_frame();
|
||||
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (GetLastError() == ERROR_TIMEOUT)
|
||||
return capture_e::timeout;
|
||||
else
|
||||
return capture_e::error;
|
||||
}
|
||||
if (produced_frame) {
|
||||
consumed_frame = produced_frame;
|
||||
produced_frame = nullptr;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (consumed_frame == nullptr) // spurious wakeup
|
||||
return capture_e::timeout;
|
||||
|
||||
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
|
||||
if (capture_access == nullptr)
|
||||
return capture_e::error;
|
||||
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
|
||||
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
wgc_capture_t::release_frame() {
|
||||
if (consumed_frame != nullptr) {
|
||||
consumed_frame.Close();
|
||||
consumed_frame = nullptr;
|
||||
}
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int
|
||||
wgc_capture_t::set_cursor_visible(bool x) {
|
||||
try {
|
||||
if (capture_session.IsCursorCaptureEnabled() != x)
|
||||
capture_session.IsCursorCaptureEnabled(x);
|
||||
return 0;
|
||||
}
|
||||
catch (winrt::hresult_error &) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config))
|
||||
return -1;
|
||||
|
||||
texture.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
|
||||
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
|
||||
* @param img_out the captured frame is returned here
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param cursor_visible
|
||||
*/
|
||||
capture_e
|
||||
display_wgc_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
HRESULT status;
|
||||
texture2d_t src;
|
||||
uint64_t frame_qpc;
|
||||
dup.set_cursor_visible(cursor_visible);
|
||||
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
|
||||
if (capture_status != capture_e::ok)
|
||||
return capture_status;
|
||||
|
||||
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
src->GetDesc(&desc);
|
||||
|
||||
// Create the staging texture if it doesn't exist. It should match the source in size and format.
|
||||
if (texture == nullptr) {
|
||||
capture_format = desc.Format;
|
||||
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_STAGING;
|
||||
t.Format = capture_format;
|
||||
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &texture);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible for our display enumeration to race with mode changes and result in
|
||||
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
|
||||
if (desc.Width != width || desc.Height != height) {
|
||||
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
// It's also possible for the capture format to change on the fly. If that happens,
|
||||
// reinitialize capture to try format detection again and create new images.
|
||||
if (capture_format != desc.Format) {
|
||||
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
// Copy from GPU to CPU
|
||||
device_ctx->CopyResource(texture.get(), src.get());
|
||||
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return capture_e::interrupted;
|
||||
}
|
||||
auto img = (img_t *) img_out.get();
|
||||
|
||||
// Map the staging texture for CPU access (making it inaccessible for the GPU)
|
||||
if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) {
|
||||
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
// Now that we know the capture format, we can finish creating the image
|
||||
if (complete_img(img, false)) {
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
|
||||
|
||||
// Unmap the staging texture to allow GPU access again
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
|
||||
if (img) {
|
||||
img->frame_timestamp = frame_timestamp;
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_wgc_ram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
} // namespace platf::dxgi
|
@ -59,14 +59,22 @@ const config = ref(props.config)
|
||||
</div>
|
||||
|
||||
<!-- Capture -->
|
||||
<div class="mb-3" v-if="platform === 'linux'">
|
||||
<div class="mb-3" v-if="platform !== 'macos'">
|
||||
<label for="capture" class="form-label">{{ $t('config.capture') }}</label>
|
||||
<select id="capture" class="form-select" v-model="config.capture">
|
||||
<option value="">{{ $t('_common.autodetect') }}</option>
|
||||
<option value="nvfbc">NvFBC</option>
|
||||
<option value="wlr">wlroots</option>
|
||||
<option value="kms">KMS</option>
|
||||
<option value="x11">X11</option>
|
||||
<PlatformLayout :platform="platform">
|
||||
<template #linux>
|
||||
<option value="nvfbc">NvFBC</option>
|
||||
<option value="wlr">wlroots</option>
|
||||
<option value="kms">KMS</option>
|
||||
<option value="x11">X11</option>
|
||||
</template>
|
||||
<template #windows>
|
||||
<option value="ddx">Desktop Duplication API</option>
|
||||
<option value="wgc">Windows.Graphics.Capture {{ $t('_common.beta') }}</option>
|
||||
</template>
|
||||
</PlatformLayout>
|
||||
</select>
|
||||
<div class="form-text">{{ $t('config.capture_desc') }}</div>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@
|
||||
"apply": "Apply",
|
||||
"auto": "Automatic",
|
||||
"autodetect": "Autodetect (recommended)",
|
||||
"beta": "(beta)",
|
||||
"cancel": "Cancel",
|
||||
"disabled": "Disabled",
|
||||
"disabled_def": "Disabled (default)",
|
||||
|
Loading…
x
Reference in New Issue
Block a user