Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
safe::mail_t mail::man;
thread_pool_util::ThreadPool task_pool;
bool display_cursor = true;
std::atomic<bool> capture_input_activity{false};
std::atomic<platf::high_precision_timer *> active_capture_timer{nullptr};

#ifdef _WIN32
nvprefs::nvprefs_interface nvprefs_instance;
Expand Down
18 changes: 18 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
#pragma once

#include <atomic>

#include "entry_handler.h"
#include "thread_pool.h"
/**
Expand All @@ -21,6 +23,22 @@ extern thread_pool_util::ThreadPool task_pool;
*/
extern bool display_cursor;

/**
* @brief Atomic flag set by the input path to notify the capture thread that input has arrived.
* @details When set, the capture thread skips frame pacing sleep to reduce input-to-display latency.
*/
extern std::atomic<bool> capture_input_activity;

namespace platf {
struct high_precision_timer;
}

/**
* @brief Pointer to the active capture timer, used by the input path to interrupt frame pacing sleep.
* @details Set by the capture thread when starting, cleared when stopping. Thread-safe via atomic.
*/
extern std::atomic<platf::high_precision_timer *> active_capture_timer;

#ifdef _WIN32
// Declare global singleton used for NVIDIA control panel modifications
#include "platform/windows/nvprefs/nvprefs_interface.h"
Expand Down
8 changes: 8 additions & 0 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,14 @@ namespace input {
input->input_queue.push_back(std::move(input_data));
}
task_pool.push(passthrough_next_message, input);

// Signal the capture thread that input has arrived to reduce input-to-display latency.
// This wakes the capture thread from its frame pacing sleep so it can capture and
// encode the next frame as soon as the desktop updates from this input.
capture_input_activity.store(true, std::memory_order_release);
if (auto t = active_capture_timer.load(std::memory_order_acquire)) {
t->interrupt();
}
}

void
Expand Down
26 changes: 26 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,32 @@ namespace platf {
virtual void
sleep_for(const std::chrono::nanoseconds &duration) = 0;

/**
* @brief Sleep for the duration, but can be interrupted from another thread.
* @param duration Sleep duration.
* @return true if interrupted early, false if slept full duration.
*/
virtual bool
sleep_for_interruptible(const std::chrono::nanoseconds &duration) {
sleep_for(duration);
return false;
}

/**
* @brief Interrupt an in-progress sleep_for_interruptible() call from another thread.
*/
virtual void
interrupt() {}

/**
* @brief Get the platform-specific interrupt event handle for use with WaitForMultipleObjects.
* @return Opaque handle (HANDLE on Windows), or nullptr if not supported.
*/
virtual void *
get_interrupt_event_handle() const {
return nullptr;
}

/**
* @brief Check if platform-specific timer backend has been initialized successfully
* @return `true` on success, `false` on error
Expand Down
31 changes: 31 additions & 0 deletions src/platform/windows/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,15 @@ namespace platf::dxgi {
release_snapshot() = 0;
virtual int
complete_img(img_t *img, bool dummy) = 0;

/**
* @brief Get a frame event handle for event-driven capture backends (e.g., WGC).
* @return HANDLE to an event signaled on frame arrival, or nullptr for polling backends.
*/
virtual HANDLE
get_frame_event_handle() const {
return nullptr;
}
};

/**
Expand Down Expand Up @@ -428,6 +437,7 @@ namespace platf::dxgi {
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame { nullptr }, consumed_frame { nullptr };
SRWLOCK frame_lock = SRWLOCK_INIT;
CONDITION_VARIABLE frame_present_cv;
HANDLE frame_event = nullptr; // Manual-reset event signaled when a new frame is available
void
on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);

Expand All @@ -447,6 +457,15 @@ namespace platf::dxgi {
release_frame();
int
set_cursor_visible(bool);

/**
* @brief Get the frame event handle for use in WaitForMultipleObjects.
* @return HANDLE to a manual-reset event that is signaled when a frame is available.
*/
HANDLE
get_frame_event() const {
return frame_event;
}

/**
* @brief Check if the captured window is still valid.
Expand All @@ -471,6 +490,12 @@ namespace platf::dxgi {
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;

protected:
HANDLE
get_frame_event_handle() const override {
return dup.get_frame_event();
}
};

/**
Expand All @@ -488,6 +513,12 @@ namespace platf::dxgi {
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;

protected:
HANDLE
get_frame_event_handle() const override {
return dup.get_frame_event();
}
};

class amd_capture_t {
Expand Down
Loading
Loading