MayaFlux 1.0.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 and their executor backend.
37 */
39 : m_sequence(std::move(sequence))
40 {
43 m_executor = std::make_unique<Yantra::VisionGpuExecutor>();
44 }
45 }
46
47 ~ImageCVProcessor() override = default;
48
49 /**
50 * @brief Validate the buffer type and reset executor state.
51 *
52 * Throws std::invalid_argument if the buffer cannot be cast to T.
53 * Called automatically by BufferProcessingChain::add_processor.
54 *
55 * @param buffer The Buffer to attach to.
56 */
57 void on_attach(const std::shared_ptr<Buffer>& buffer) override
58 {
59 auto typed = std::dynamic_pointer_cast<T>(buffer);
60 if (!typed) {
61 error<std::invalid_argument>(
64 std::source_location::current(),
65 "ImageCVProcessor<T>: buffer is not the expected type");
66 }
67 m_buffer = typed;
68
71 } else {
72 if (!m_executor) {
73 m_executor = std::make_unique<Yantra::VisionGpuExecutor>();
74 }
75 }
76
77 if (!m_gpu_staging) {
78 constexpr size_t k_max_frame_bytes = 3840 * 2160 * 4;
79 m_gpu_staging = create_image_staging_buffer(k_max_frame_bytes);
80 }
81
83 "ImageCVProcessor attached");
84 }
85
86 /**
87 * @brief Clear state and reset executor.
88 * @param buffer The Buffer being detached.
89 */
90 void on_detach(const std::shared_ptr<Buffer>& /*buffer*/) override
91 {
92 m_buffer.reset();
93
96 }
97
98 m_gpu_staging.reset();
99 }
100
101 /**
102 * @brief Download the current GPU image, run the VisionSequence, store the result.
103 *
104 * No-op if the buffer has expired or the image is unavailable.
105 *
106 * @param buffer The GpuImageSource buffer to read from.
107 */
108 void processing_function(const std::shared_ptr<Buffer>& /*buffer*/) override
109 {
110 if (m_skipped_frames < m_eval_delta - 1) {
112 return;
113 }
115
116 auto typed = m_buffer.lock();
117 if (!typed)
118 return;
119
120 auto image = resolve_gpu_image(*typed);
121 if (!image || !image->is_initialized())
122 return;
123
124 m_is_processing.store(true, std::memory_order_release);
125
128 if (!frame.empty()) {
130 m_sequence, frame,
131 image->get_width(), image->get_height());
132 }
133 } else {
134 auto pass = m_executor->run(
136 image->get_width(), image->get_height());
137
138 if (!pass.is_ready()) {
139 m_is_processing.store(false, std::memory_order_release);
140 return;
141 }
142
143 m_result = std::move(pass);
144 }
145
146 if (m_result_source)
147 m_result_source->signal(m_result);
148
149 m_is_processing.store(false, std::memory_order_release);
150 }
151
152 /**
153 * @brief Replace the pipeline and reset inter-frame executor state.
154 *
155 * Not thread-safe relative to processing_function. Call only when idle.
156 *
157 * @param sequence Replacement VisionSequence.
158 */
160 {
161 m_sequence = std::move(sequence);
162 m_executor.reset();
164 m_result = {};
166 m_executor = std::make_unique<Yantra::VisionGpuExecutor>();
167 }
168
169 /**
170 * @brief The result of the last successful processing_function call.
171 *
172 * Default-initialised until the first successful call completes.
173 *
174 * @return Most recent VisionResult.
175 */
176 [[nodiscard]] const Kinesis::Vision::VisionResult& get_result() const { return m_result; }
177
178 /**
179 * @brief Shared BroadcastSource signalled with each VisionResult after a
180 * successful processing_function call.
181 *
182 * Created on first call. Wire with Kriya::on_signal to consume results
183 * from a coroutine without polling get_result().
184 *
185 * @return Shared pointer to the BroadcastSource, never null after first call.
186 */
187 [[nodiscard]] std::shared_ptr<Vruta::BroadcastSource<Kinesis::Vision::VisionResult>>
189 {
190 if (!m_result_source) {
191 m_result_source = std::make_shared<
193 }
194 return m_result_source;
195 }
196
197 /**
198 * @brief Set how often this processor actually evaluates, relative to the
199 * engine's preferred frame rate.
200 *
201 * ImageCVProcessor is a deliberate exception to the engine's normal
202 * scheduling model. Every other processor registered with the engine is
203 * scheduled by string-keyed rate registration at the point of registration,
204 * and runs on that schedule without needing its own internal throttle.
205 * ImageCVProcessor throttles itself internally instead, because its result
206 * is not fenceable for the next evaluation cycle (a CV pass in flight
207 * cannot be safely interrupted or reissued for the following frame) and
208 * because the GPU vision dispatch is heavy enough that running it on every
209 * render frame is often wasted work. This self-throttle exists only for
210 * that reason and is not a pattern to copy into ordinary processors; a
211 * normal processor should be scheduled through the engine's registration
212 * mechanism, not by skipping frames internally.
213 *
214 * fps below the preferred frame rate throttles down: m_eval_delta becomes
215 * ceil(s_preferred_frame_rate / fps), rounded down here to match integer
216 * frame counting, so the processor runs roughly every m_eval_delta frames.
217 * fps at or above the preferred frame rate runs every frame (m_eval_delta = 1).
218 *
219 * @param fps Desired evaluation rate in frames per second.
220 */
221 void set_eval_rate(uint32_t fps)
222 {
224 ? s_preferred_frame_rate / std::max(fps, 1U)
225 : 1;
226 }
227
228 /**
229 * @brief Get the currently achieved evaluation rate in frames per second.
230 *
231 * Derived from m_eval_delta against s_preferred_frame_rate, not measured.
232 * @return Approximate evaluation rate in frames per second.
233 */
234 uint32_t get_eval_rate() const
235 {
237 }
238
239private:
241 std::unique_ptr<Yantra::VisionGpuExecutor> m_executor;
244
245 std::vector<uint8_t> m_raw_staging;
246 std::vector<float> m_float_work;
247 std::shared_ptr<VKBuffer> m_gpu_staging;
248
249 std::weak_ptr<T> m_buffer;
250 std::shared_ptr<Vruta::BroadcastSource<Kinesis::Vision::VisionResult>> m_result_source;
251 std::atomic<bool> m_is_processing { false };
252
253 uint32_t m_eval_delta { 1 };
254 uint32_t m_skipped_frames { 0 };
255};
256
257} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:47
bool fps
std::shared_ptr< NetworkGeometryBuffer > buffer
Dispatch engine for VisionSequence execution.
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.
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
ImageCVProcessor(Kinesis::Vision::VisionSequence sequence)
Construct with the vision pipeline to execute each processing_function call.
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.
@ CPU
Execute the sequence with the Kinesis host executor.
@ GPU
Execute the sequence with the Yantra Vulkan executor.
Result of executing a VisionSequence on one frame.
VisionBackend backend
Backend used for every step.
Definition VisionOp.hpp:383
Ordered VisionSteps and the backend chosen to execute them.
Definition VisionOp.hpp:381