MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ImageCVProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
6
8
10
13
14namespace MayaFlux::Buffers {
15
16/**
17 * @class ImageCVProcessor
18 * @brief BufferProcessor executing a Kinesis::Vision pipeline on a GpuImageSource buffer.
19 *
20 * Renamed from ImageCVProcessor. ImageCVProcessor makes the domain
21 * (computer vision on image data) explicit and avoids collision with
22 * VisionProcessor (the Kakshya DataProcessor over pixel containers).
23 *
24 * m_gpu_staging is a persistent host-visible VKBuffer allocated once in
25 * on_attach, sized to the image footprint. Passed to download_and_normalise
26 * on every call so TextureLoom::download_data uses the fenced path rather
27 * than waitIdle, avoiding graphics queue stalls on each frame download.
28 *
29 * @tparam T A VKBuffer subclass satisfying GpuImageSource.
30 */
31template <GpuImageSource T>
33public:
34 /**
35 * @brief Construct with the vision pipeline to execute each processing_function call.
36 * @param sequence Ordered VisionSteps describing the pipeline.
37 */
38 explicit ImageCVProcessor(Kinesis::Vision::VisionSequence sequence, bool force_cpu = false)
39 : m_sequence(std::move(sequence))
40 , m_force_cpu(force_cpu)
41 {
43 if (!m_force_cpu) {
44 m_executor = std::make_unique<Yantra::VisionGpuExecutor>();
45 }
46 }
47
48 ~ImageCVProcessor() override = default;
49
50 /**
51 * @brief Validate the buffer type and reset executor state.
52 *
53 * Throws std::invalid_argument if the buffer cannot be cast to T.
54 * Called automatically by BufferProcessingChain::add_processor.
55 *
56 * @param buffer The Buffer to attach to.
57 */
58 void on_attach(const std::shared_ptr<Buffer>& buffer) override
59 {
60 auto typed = std::dynamic_pointer_cast<T>(buffer);
61 if (!typed) {
62 error<std::invalid_argument>(
65 std::source_location::current(),
66 "ImageCVProcessor<T>: buffer is not the expected type");
67 }
68 m_buffer = typed;
69
70 if (m_force_cpu) {
72 } else {
73 if (!m_executor) {
74 m_executor = std::make_unique<Yantra::VisionGpuExecutor>();
75 }
76 }
77
78 if (!m_gpu_staging) {
79 constexpr size_t k_max_frame_bytes = 3840 * 2160 * 4;
80 m_gpu_staging = create_image_staging_buffer(k_max_frame_bytes);
81 }
82
84 "ImageCVProcessor attached");
85 }
86
87 /**
88 * @brief Clear state and reset executor.
89 * @param buffer The Buffer being detached.
90 */
91 void on_detach(const std::shared_ptr<Buffer>& /*buffer*/) override
92 {
93 m_buffer.reset();
94
95 if (m_force_cpu) {
97 }
98
99 m_gpu_staging.reset();
100 }
101
102 /**
103 * @brief Download the current GPU image, run the VisionSequence, store the result.
104 *
105 * No-op if the buffer has expired or the image is unavailable.
106 *
107 * @param buffer The GpuImageSource buffer to read from.
108 */
109 void processing_function(const std::shared_ptr<Buffer>& /*buffer*/) override
110 {
111 if (m_skipped_frames < m_eval_delta - 1) {
113 return;
114 }
116
117 auto typed = m_buffer.lock();
118 if (!typed)
119 return;
120
121 auto image = resolve_gpu_image(*typed);
122 if (!image || !image->is_initialized())
123 return;
124
125 m_is_processing.store(true, std::memory_order_release);
126
127 if (m_force_cpu) {
129 if (!frame.empty()) {
131 m_sequence, frame,
132 image->get_width(), image->get_height());
133 }
134 } else {
135 auto pass = m_executor->run(
137 image->get_width(), image->get_height());
138
139 if (!pass.is_ready()) {
140 m_is_processing.store(false, std::memory_order_release);
141 return;
142 }
143
144 m_result = std::move(pass);
145 }
146
147 if (m_result_source)
148 m_result_source->signal(m_result);
149
150 m_is_processing.store(false, std::memory_order_release);
151 }
152
153 /**
154 * @brief Replace the pipeline and reset inter-frame executor state.
155 *
156 * Not thread-safe relative to processing_function. Call only when idle.
157 *
158 * @param sequence Replacement VisionSequence.
159 */
161 {
162 m_sequence = std::move(sequence);
163 m_executor.reset();
164 }
165
166 /**
167 * @brief The result of the last successful processing_function call.
168 *
169 * Default-initialised until the first successful call completes.
170 *
171 * @return Most recent VisionResult.
172 */
173 [[nodiscard]] const Kinesis::Vision::VisionResult& get_result() const { return m_result; }
174
175 /**
176 * @brief Shared BroadcastSource signalled with each VisionResult after a
177 * successful processing_function call.
178 *
179 * Created on first call. Wire with Kriya::on_signal to consume results
180 * from a coroutine without polling get_result().
181 *
182 * @return Shared pointer to the BroadcastSource, never null after first call.
183 */
184 [[nodiscard]] std::shared_ptr<Vruta::BroadcastSource<Kinesis::Vision::VisionResult>>
186 {
187 if (!m_result_source) {
188 m_result_source = std::make_shared<
190 }
191 return m_result_source;
192 }
193
194 /**
195 * @brief Set how often this processor actually evaluates, relative to the
196 * engine's preferred frame rate.
197 *
198 * ImageCVProcessor is a deliberate exception to the engine's normal
199 * scheduling model. Every other processor registered with the engine is
200 * scheduled by string-keyed rate registration at the point of registration,
201 * and runs on that schedule without needing its own internal throttle.
202 * ImageCVProcessor throttles itself internally instead, because its result
203 * is not fenceable for the next evaluation cycle (a CV pass in flight
204 * cannot be safely interrupted or reissued for the following frame) and
205 * because the GPU vision dispatch is heavy enough that running it on every
206 * render frame is often wasted work. This self-throttle exists only for
207 * that reason and is not a pattern to copy into ordinary processors; a
208 * normal processor should be scheduled through the engine's registration
209 * mechanism, not by skipping frames internally.
210 *
211 * fps below the preferred frame rate throttles down: m_eval_delta becomes
212 * ceil(s_preferred_frame_rate / fps), rounded down here to match integer
213 * frame counting, so the processor runs roughly every m_eval_delta frames.
214 * fps at or above the preferred frame rate runs every frame (m_eval_delta = 1).
215 *
216 * @param fps Desired evaluation rate in frames per second.
217 */
218 void set_eval_rate(uint32_t fps)
219 {
221 ? s_preferred_frame_rate / std::max(fps, 1U)
222 : 1;
223 }
224
225 /**
226 * @brief Get the currently achieved evaluation rate in frames per second.
227 *
228 * Derived from m_eval_delta against s_preferred_frame_rate, not measured.
229 * @return Approximate evaluation rate in frames per second.
230 */
231 uint32_t get_eval_rate() const
232 {
234 }
235
236private:
238 std::unique_ptr<Yantra::VisionGpuExecutor> m_executor;
241
242 std::vector<uint8_t> m_raw_staging;
243 std::vector<float> m_float_work;
244 std::shared_ptr<VKBuffer> m_gpu_staging;
245
246 std::weak_ptr<T> m_buffer;
247 std::shared_ptr<Vruta::BroadcastSource<Kinesis::Vision::VisionResult>> m_result_source;
248 std::atomic<bool> m_is_processing { false };
249
250 bool m_force_cpu {};
251 uint32_t m_eval_delta { 1 };
252 uint32_t m_skipped_frames { 0 };
253};
254
255} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:64
Dispatch engine for VisionSequence execution.
uint32_t pass
GPU execution layer for Kinesis::Vision::VisionSequence.
Central computational transformation interface for continuous buffer processing.
void processing_function(const std::shared_ptr< Buffer > &) override
Download the current GPU image, run the VisionSequence, store the result.
~ImageCVProcessor() override=default
Kinesis::Vision::VisionSequence m_sequence
std::shared_ptr< Vruta::BroadcastSource< Kinesis::Vision::VisionResult > > m_result_source
std::shared_ptr< Vruta::BroadcastSource< Kinesis::Vision::VisionResult > > get_result_source()
Shared BroadcastSource signalled with each VisionResult after a successful processing_function call.
std::unique_ptr< Yantra::VisionGpuExecutor > m_executor
void set_eval_rate(uint32_t fps)
Set how often this processor actually evaluates, relative to the engine's preferred frame rate.
void on_detach(const std::shared_ptr< Buffer > &) override
Clear state and reset executor.
uint32_t get_eval_rate() const
Get the currently achieved evaluation rate in frames per second.
ImageCVProcessor(Kinesis::Vision::VisionSequence sequence, bool force_cpu=false)
Construct with the vision pipeline to execute each processing_function call.
Kinesis::Vision::VisionResult m_result
std::shared_ptr< VKBuffer > m_gpu_staging
const Kinesis::Vision::VisionResult & get_result() const
The result of the last successful processing_function call.
void set_sequence(Kinesis::Vision::VisionSequence sequence)
Replace the pipeline and reset inter-frame executor state.
Kinesis::Vision::VisionExecutor m_cpu_executor
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Validate the buffer type and reset executor state.
BufferProcessor executing a Kinesis::Vision pipeline on a GpuImageSource buffer.
void reset()
Clear stored inter-frame state.
VisionResult run(const VisionSequence &sequence, std::span< const float > frame, uint32_t w, uint32_t h)
Execute a VisionSequence on one frame.
Stateful executor for a VisionSequence.
Awaitable single-value broadcast channel for cross-thread signal delivery.
std::shared_ptr< VKBuffer > create_image_staging_buffer(size_t size)
Allocate a persistent host-visible staging buffer sized for repeated streaming uploads to an image of...
std::shared_ptr< Core::VKImage > resolve_gpu_image(const T &buffer)
Resolve the GPU-resident image from any GpuImageSource buffer.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
std::span< const float > download_and_normalise(const std::shared_ptr< Core::VKImage > &image, std::vector< uint8_t > &raw_staging, std::vector< float > &work, const std::shared_ptr< VKBuffer > &gpu_staging)
Download a VKImage to CPU and return a normalised float span.
uint32_t s_preferred_frame_rate
Global default frame rate.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
Result of executing a VisionSequence on one frame.
Ordered sequence of VisionSteps describing a complete vision pipeline.
Definition VisionOp.hpp:169