From 7f6383833c58b149f65a73d16b568c2949f2bb3d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 17 Jan 2023 08:09:15 -0600 Subject: [PATCH] Replace frame capture busy loop with waitable timer (#778) --- src/platform/windows/display.h | 13 +++--- src/platform/windows/display_base.cpp | 57 +++++++++++++++++++++++++++ src/platform/windows/display_ram.cpp | 31 --------------- src/platform/windows/display_vram.cpp | 31 --------------- 4 files changed, 63 insertions(+), 69 deletions(-) diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index a788df51..f52f1b31 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -116,6 +116,7 @@ public: class display_base_t : public display_t { public: int init(int framerate, const std::string &display_name); + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; std::chrono::nanoseconds delay; @@ -147,15 +148,14 @@ protected: const char *dxgi_format_to_string(DXGI_FORMAT format); - virtual int complete_img(img_t *img, bool dummy) = 0; - virtual std::vector get_supported_sdr_capture_formats() = 0; + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0; + virtual int complete_img(img_t *img, bool dummy) = 0; + virtual std::vector get_supported_sdr_capture_formats() = 0; }; class display_ram_t : public display_base_t { public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; std::shared_ptr alloc_img() override; int dummy_img(img_t *img) override; @@ -171,8 +171,7 @@ public: class display_vram_t : public display_base_t, public std::enable_shared_from_this { public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; std::shared_ptr alloc_img() override; int dummy_img(img_t *img_base) override; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 9f8c2705..6f518f7c 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -79,6 +79,63 @@ duplication_t::~duplication_t() { release_frame(); } +capture_e display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + // Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+) + HANDLE timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + if(!timer) { + timer = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS); + if(!timer) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to create timer: "sv << winerr; + return capture_e::error; + } + } + + auto close_timer = util::fail_guard([timer]() { + CloseHandle(timer); + }); + + while(img) { + auto wait_time_us = std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now()).count(); + + // If the wait time is between 1 us and 1 second, wait the specified time + // and offset the next frame time from the exact current frame time target. + if(wait_time_us > 0 && wait_time_us < 1000000) { + LARGE_INTEGER due_time { .QuadPart = -10LL * wait_time_us }; + SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false); + WaitForSingleObject(timer, INFINITE); + next_frame += delay; + } + else { + // If the wait time is negative (meaning the frame is past due) or the + // computed wait time is beyond a second (meaning possible clock issues), + // just capture the frame now and resynchronize the frame interval with + // the current time. + next_frame = std::chrono::steady_clock::now() + delay; + } + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + img = snapshot_cb(img, false); + break; + case platf::capture_e::ok: + img = snapshot_cb(img, true); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + int display_base_t::init(int framerate, const std::string &display_name) { std::once_flag windows_cpp_once_flag; diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index c5ce6dc3..29ebcefb 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -165,37 +165,6 @@ void blend_cursor(const cursor_t &cursor, img_t &img) { } } -capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - img = snapshot_cb(img, false); - std::this_thread::sleep_for(1ms); - break; - case platf::capture_e::ok: - img = snapshot_cb(img, true); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { auto img = (img_t *)img_base; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 22d65be3..f3203353 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -698,37 +698,6 @@ public: device_ctx_t device_ctx; }; -capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - img = snapshot_cb(img, false); - std::this_thread::sleep_for(1ms); - break; - case platf::capture_e::ok: - img = snapshot_cb(img, true); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) { // This cursor image may not be used if(cursor_img.size() == 0) {