From a47ceb3186ac2ca31fc00917346a676ccae2eac8 Mon Sep 17 00:00:00 2001 From: Xavier Ruiz Date: Thu, 26 Feb 2026 15:28:33 -0500 Subject: [PATCH] Use cv::swap and copyTo to eliminate redundant frame copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace clone() with cv::swap() in video_tick for both background and enhance filters. Swap is O(1) — it transfers buffer ownership to the tick thread instead of deep-copying ~8 MB per frame. In the render thread, use copyTo() instead of clone() for the stage surface data. copyTo reuses the existing cv::Mat allocation when dimensions match, avoiding a fresh heap allocation every frame. Add a newFrameAvailable flag (protected by inputBGRALock) so the tick thread only processes genuinely new frames and doesn't re-process stale data after swap empties the shared buffer. Signed-off-by: Xavier Ruiz --- src/FilterData.hpp | 1 + src/background-filter.cpp | 6 +++--- src/enhance-filter.cpp | 10 +++++----- src/obs-utils/obs-utils.cpp | 7 ++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/FilterData.hpp b/src/FilterData.hpp index 0cd585ac..6cdab689 100644 --- a/src/FilterData.hpp +++ b/src/FilterData.hpp @@ -31,6 +31,7 @@ struct filter_data : public ORTModelData, public std::enable_shared_from_this newFrameAvailable{false}; std::atomic isDisabled{false}; diff --git a/src/background-filter.cpp b/src/background-filter.cpp index f1652b0a..b9c7373b 100644 --- a/src/background-filter.cpp +++ b/src/background-filter.cpp @@ -536,11 +536,11 @@ void background_filter_video_tick(void *data, float seconds) // No data to process return; } - if (tf->inputBGRA.empty()) { - // No data to process + if (!tf->newFrameAvailable) { return; } - imageBGRA = tf->inputBGRA.clone(); + cv::swap(imageBGRA, tf->inputBGRA); + tf->newFrameAvailable = false; } if (tf->enableImageSimilarity) { diff --git a/src/enhance-filter.cpp b/src/enhance-filter.cpp index 7bb39e07..70f7a808 100644 --- a/src/enhance-filter.cpp +++ b/src/enhance-filter.cpp @@ -247,10 +247,6 @@ void enhance_filter_video_tick(void *data, float seconds) return; } - if (tf->inputBGRA.empty()) { - return; - } - // Get input image from source rendering pipeline cv::Mat imageBGRA; { @@ -258,7 +254,11 @@ void enhance_filter_video_tick(void *data, float seconds) if (!lock.owns_lock()) { return; } - imageBGRA = tf->inputBGRA.clone(); + if (!tf->newFrameAvailable) { + return; + } + cv::swap(imageBGRA, tf->inputBGRA); + tf->newFrameAvailable = false; } cv::Mat outputImage; diff --git a/src/obs-utils/obs-utils.cpp b/src/obs-utils/obs-utils.cpp index 0ebd21a9..6bd0df97 100644 --- a/src/obs-utils/obs-utils.cpp +++ b/src/obs-utils/obs-utils.cpp @@ -67,9 +67,10 @@ bool getRGBAFromStageSurface(filter_data *tf, uint32_t &width, uint32_t &height) std::lock_guard lock(tf->inputBGRALock); // Create a temporary Mat that wraps the video_data pointer cv::Mat temp(height, width, CV_8UC4, video_data, linesize); - // Clone the data to ensure tf->inputBGRA has its own copy - // This prevents use-after-unmap race condition - tf->inputBGRA = temp.clone(); + // Copy frame data into tf->inputBGRA, reusing its allocation + // when dimensions match. Ensures we own the pixels before unmap. + temp.copyTo(tf->inputBGRA); + tf->newFrameAvailable = true; } gs_stagesurface_unmap(tf->stagesurface); return true;