mirror of
https://github.com/thinkonmay/sunshine-sdk.git
synced 2026-08-23 17:03:09 +00:00
adopt wgc
This commit is contained in:
parent
7e2daf50f3
commit
d07cb8c0b7
@ -25,6 +25,7 @@ set(PLATFORM_TARGET_FILES
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display.h"
|
||||
"${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_wgc.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
|
||||
${NVPREFS_FILES})
|
||||
|
||||
@ -6,7 +6,7 @@ add_library(parent SHARED
|
||||
"${CMAKE_SOURCE_DIR}/lib/interprocess.h"
|
||||
"${CMAKE_SOURCE_DIR}/lib/interprocess.cpp"
|
||||
)
|
||||
set_target_properties(parent PROPERTIES CXX_STANDARD 17)
|
||||
set_target_properties(parent PROPERTIES CXX_STANDARD 20)
|
||||
|
||||
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
|
||||
@ -34,7 +34,7 @@ endif()
|
||||
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 20
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
|
||||
@ -563,6 +563,31 @@ namespace platf {
|
||||
virtual ~audio_control_t() = default;
|
||||
};
|
||||
|
||||
struct high_precision_timer: private boost::noncopyable {
|
||||
virtual ~high_precision_timer() = default;
|
||||
|
||||
/**
|
||||
* @brief Sleep for the duration
|
||||
* @param duration Sleep duration
|
||||
*/
|
||||
virtual void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if platform-specific timer backend has been initialized successfully
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual
|
||||
operator bool() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create platform-specific timer capable of high-precision sleep
|
||||
* @return A unique pointer to timer
|
||||
*/
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer();
|
||||
|
||||
void
|
||||
freeInput(void *);
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
@ -153,21 +155,7 @@ 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:
|
||||
@ -185,7 +173,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;
|
||||
|
||||
@ -198,7 +185,7 @@ namespace platf::dxgi {
|
||||
DXGI_FORMAT capture_format;
|
||||
D3D_FEATURE_LEVEL feature_level;
|
||||
|
||||
util::safe_ptr_v2<std::remove_pointer_t<HANDLE>, BOOL, CloseHandle> timer;
|
||||
std::unique_ptr<high_precision_timer> timer = create_high_precision_timer();
|
||||
|
||||
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
|
||||
@ -252,6 +239,13 @@ namespace platf::dxgi {
|
||||
is_hdr() override;
|
||||
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
|
||||
@ -259,17 +253,12 @@ namespace platf::dxgi {
|
||||
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;
|
||||
};
|
||||
|
||||
class display_ram_t: public display_base_t {
|
||||
@ -341,4 +330,127 @@ namespace platf::dxgi {
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
};
|
||||
|
||||
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;
|
||||
blend_t blend_invert;
|
||||
blend_t blend_disable;
|
||||
|
||||
ps_t cursor_ps;
|
||||
vs_t cursor_vs;
|
||||
|
||||
gpu_cursor_t cursor_alpha;
|
||||
gpu_cursor_t cursor_xor;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@ -165,7 +250,7 @@ namespace platf::dxgi {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = dup.release_frame();
|
||||
status = release_snapshot();
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
}
|
||||
@ -416,6 +501,8 @@ namespace platf::dxgi {
|
||||
// Ensure offset starts at 0x0
|
||||
offset_x -= GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||
offset_y -= GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -604,81 +691,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)) {
|
||||
@ -698,12 +711,15 @@ 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;
|
||||
if (!timer || !*timer) {
|
||||
BOOST_LOG(error) << "Uninitialized high precision timer";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
display_base_t::is_hdr() {
|
||||
dxgi::output6_t output6 {};
|
||||
@ -947,18 +963,37 @@ namespace platf::dxgi {
|
||||
namespace platf {
|
||||
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 (true || 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_ddup_ram_t>();
|
||||
|
||||
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>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
if (false || 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -176,10 +176,10 @@ 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) {
|
||||
HRESULT status;
|
||||
|
||||
capture_e
|
||||
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 +326,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>();
|
||||
@ -390,6 +395,15 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
display_ram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
|
||||
@ -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,6 +1328,93 @@ namespace platf::dxgi {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ddup_vram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
|
||||
int
|
||||
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;
|
||||
}
|
||||
|
||||
D3D11_SAMPLER_DESC sampler_desc {};
|
||||
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
||||
sampler_desc.MinLOD = 0;
|
||||
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
|
||||
|
||||
auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear);
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreateVertexShader(cursor_vs_hlsl->GetBufferPointer(), cursor_vs_hlsl->GetBufferSize(), nullptr, &cursor_vs);
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
int32_t rotation_modifier = display_rotation == DXGI_MODE_ROTATION_UNSPECIFIED ? 0 : display_rotation - 1;
|
||||
int32_t rotation_data[16 / sizeof(int32_t)] { rotation_modifier }; // aligned to 16-byte
|
||||
auto rotation = make_buffer(device.get(), rotation_data);
|
||||
if (!rotation) {
|
||||
BOOST_LOG(error) << "Failed to create display rotation vertex constant buffer";
|
||||
return -1;
|
||||
}
|
||||
device_ctx->VSSetConstantBuffers(2, 1, &rotation);
|
||||
}
|
||||
|
||||
if (config.dynamicRange && is_hdr()) {
|
||||
// This shader will normalize scRGB white levels to a user-defined white level
|
||||
status = device->CreatePixelShader(cursor_ps_normalize_white_hlsl->GetBufferPointer(), cursor_ps_normalize_white_hlsl->GetBufferSize(), nullptr, &cursor_ps);
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Failed to create cursor blending (normalized white) pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use a 300 nit target for the mouse cursor. We should really get
|
||||
// the user's SDR white level in nits, but there is no API that
|
||||
// provides that information to Win32 apps.
|
||||
float white_multiplier_data[16 / sizeof(float)] { 300.0f / 80.f }; // aligned to 16-byte
|
||||
auto white_multiplier = make_buffer(device.get(), white_multiplier_data);
|
||||
if (!white_multiplier) {
|
||||
BOOST_LOG(warning) << "Failed to create cursor blending (normalized white) white multiplier constant buffer";
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx->PSSetConstantBuffers(1, 1, &white_multiplier);
|
||||
}
|
||||
else {
|
||||
status = device->CreatePixelShader(cursor_ps_hlsl->GetBufferPointer(), cursor_ps_hlsl->GetBufferSize(), nullptr, &cursor_ps);
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Failed to create cursor blending pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
blend_alpha = make_blend(device.get(), true, false);
|
||||
blend_invert = make_blend(device.get(), true, true);
|
||||
blend_disable = make_blend(device.get(), false, false);
|
||||
|
||||
if (!blend_disable || !blend_alpha || !blend_invert) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
|
||||
device_ctx->PSSetSamplers(0, 1, &sampler_linear);
|
||||
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
display_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name)) {
|
||||
@ -1410,6 +1496,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>();
|
||||
|
||||
336
src/platform/windows/display_wgc.cpp
Normal file
336
src/platform/windows/display_wgc.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* @file src/platform/windows/display_wgc.cpp
|
||||
* @brief Definitions for WinRT Windows.Graphics.Capture API
|
||||
*/
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
// Gross hack to work around MINGW-packages#22160
|
||||
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
|
||||
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.foundation.metadata.h>
|
||||
#include <winrt/windows.graphics.directx.direct3d11.h>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Metadata;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the Windows.Graphics.Capture backend.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
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 {
|
||||
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
|
||||
capture_session.IsBorderRequired(false);
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 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 whether to capture the cursor
|
||||
*/
|
||||
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
|
||||
@ -1766,4 +1766,55 @@ namespace platf {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
class win32_high_precision_timer: public high_precision_timer {
|
||||
public:
|
||||
win32_high_precision_timer() {
|
||||
// Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+)
|
||||
timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (!timer) {
|
||||
timer = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS);
|
||||
if (!timer) {
|
||||
BOOST_LOG(error) << "Unable to create high_precision_timer, CreateWaitableTimerEx() failed: " << GetLastError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~win32_high_precision_timer() {
|
||||
if (timer) CloseHandle(timer);
|
||||
}
|
||||
|
||||
void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
if (!timer) {
|
||||
BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with uninitialized timer";
|
||||
return;
|
||||
}
|
||||
if (duration < 0s) {
|
||||
BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with negative duration";
|
||||
return;
|
||||
}
|
||||
if (duration > 5s) {
|
||||
BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with unexpectedly large duration (>5s)";
|
||||
return;
|
||||
}
|
||||
|
||||
LARGE_INTEGER due_time;
|
||||
due_time.QuadPart = duration.count() / -100;
|
||||
SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
}
|
||||
|
||||
operator bool() override {
|
||||
return timer != NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE timer = NULL;
|
||||
};
|
||||
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer() {
|
||||
return std::make_unique<win32_high_precision_timer>();
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
@ -5,7 +5,7 @@ project(sunshine_tools)
|
||||
include_directories("${CMAKE_SOURCE_DIR}")
|
||||
|
||||
add_executable(ddprobe ddprobe.cpp)
|
||||
set_target_properties(ddprobe PROPERTIES CXX_STANDARD 17)
|
||||
set_target_properties(ddprobe PROPERTIES CXX_STANDARD 20)
|
||||
target_link_libraries(ddprobe
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
dxgi
|
||||
|
||||
Loading…
Reference in New Issue
Block a user