MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VisionGpuDispatch.cpp
Go to the documentation of this file.
1#include "DispatchLayouts.hpp"
2
3namespace MayaFlux::Yantra {
4
5using namespace VisionInternal;
6
7using namespace Portal::Graphics;
8using namespace Kinesis::Vision;
9
10namespace {
11
12 constexpr size_t k_snapshot_cache_budget_bytes = size_t { 256 } << 20;
13
14 /**
15 * @brief Release fences of submissions that were not awaited: the ingest
16 * pair and the flow pyramid build. Waits when one is still pending,
17 * so any descriptor those submissions used is safe to rewrite once
18 * this returns.
19 */
20 void reap_fences(VisionGpuContexts& contexts)
21 {
23 for (auto* slot : { &contexts.ingest_fence, &contexts.ingest_barrier_fence, &contexts.flow_state.build_fence }) {
25 foundry.wait_for_fence(*slot);
26 foundry.release_fence(*slot);
28 }
29 }
30 }
31
32 /**
33 * @brief Convert a non-storage seed frame to an rgba32f storage image.
34 *
35 * Frames already carrying storage usage pass through. Otherwise
36 * vision_ingest.comp samples the frame as a texture (sampler does the
37 * unorm/sRGB decode) into contexts.ingest's rgba32f output. The dispatch
38 * is not awaited — a trailing compute barrier gives the memory
39 * dependency, fences are reaped on the next run.
40 */
41 std::shared_ptr<Core::VKImage> op_ingest(
42 VisionGpuContexts& contexts,
43 const std::shared_ptr<Core::VKImage>& frame,
44 uint32_t w, uint32_t h)
45 {
46 if (!frame || !frame->is_initialized())
47 return frame;
48 if (static_cast<bool>(frame->get_usage_flags() & vk::ImageUsageFlagBits::eStorage))
49 return frame;
50
52 auto& ingest = contexts.ingest;
53
54 ingest.swap_shader({
55 .shader_path = "vision_ingest.comp.spv",
56 .workgroup_size = k_wg2d,
57 .push_constant_size = sizeof(IngestPC),
58 });
59 ingest.stage_image(frame);
60 ingest.set_push_constants(IngestPC { .width = w, .height = h });
61 ingest.prepare_output_image(w, h);
62 ingest.set_output_dimensions(w, h);
63 contexts.ingest_fence = ingest.dispatch_async({});
64 ingest.clear_output_dimensions();
65
66 auto out = ingest.get_output_image(0);
67
68 if (out) {
69 const auto bcmd = foundry.begin_commands(
71 foundry.image_barrier(bcmd, out->get_image(),
72 vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral,
73 vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead,
74 vk::PipelineStageFlagBits::eComputeShader,
75 vk::PipelineStageFlagBits::eComputeShader);
76 contexts.ingest_barrier_fence = foundry.submit_async(bcmd);
77 }
78
79 return out;
80 }
81
82} // namespace
83
85 : pixel {
87 Portal::Graphics::ImageFormat::RGBA32F,
88 TextureExecutionContext::OutputMode::IMAGE,
89 1,
90 std::vector<GpuBufferBinding> {
91 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
92 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
93 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
94 },
96 }
97 , structured {
98 GpuComputeConfig {},
99 Portal::Graphics::ImageFormat::RGBA32F,
100 TextureExecutionContext::OutputMode::SCALAR,
101 0,
102 std::vector<GpuBufferBinding> {
103 { .set = 0, .binding = 1, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
104 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
105 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
106 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
107 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
108 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
109 },
110 GpuBufferBinding::ElementType::IMAGE_STORAGE,
111 0,
112 }
113 , labels {
115 Portal::Graphics::ImageFormat::RGBA32F,
116 TextureExecutionContext::OutputMode::IMAGE,
117 1,
118 std::vector<GpuBufferBinding> {
119 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
120 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
121 },
122 GpuBufferBinding::ElementType::IMAGE_STORAGE,
123 }
124 , component_contours {
126 Portal::Graphics::ImageFormat::RGBA32F,
127 TextureExecutionContext::OutputMode::IMAGE,
128 1,
129 std::vector<GpuBufferBinding> {
130 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
131 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
132 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
133 { .set = 0, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
134 { .set = 0, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
135 { .set = 0, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
136 { .set = 0, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
137 { .set = 0, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
138 { .set = 0, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
139 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
140 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
141 { .set = 1, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
142 { .set = 1, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
143 { .set = 1, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
144 { .set = 1, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
145 { .set = 1, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
146 { .set = 1, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
147 { .set = 1, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
148 { .set = 1, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
149 { .set = 1, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
150 { .set = 1, .binding = 11, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
151 { .set = 2, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
152 { .set = 2, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
153 },
154 GpuBufferBinding::ElementType::IMAGE_STORAGE,
155 0,
156 }
157 , ingest {
159 Portal::Graphics::ImageFormat::RGBA32F,
160 TextureExecutionContext::OutputMode::IMAGE,
161 1,
162 std::vector<GpuBufferBinding> {},
163 GpuBufferBinding::ElementType::IMAGE_SAMPLED,
164 0,
165 }
166 , flow {
168 std::vector<GpuBufferBinding> {
169 { .set = 0, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
170 { .set = 0, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
171 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
172 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
173 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
174 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
175 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
176 { .set = 1, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
177 { .set = 1, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
178 { .set = 1, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
179 { .set = 1, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
180 { .set = 1, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
181 { .set = 1, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
182 { .set = 1, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
183 { .set = 1, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
184 { .set = 1, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
185 { .set = 0, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
186 { .set = 0, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
187 { .set = 0, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
188 { .set = 0, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
189 { .set = 0, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
190 { .set = 0, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
191 { .set = 0, .binding = 11, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
192 },
193 TextureExecutionContext::OutputMode::SCALAR,
194 }
195{
196 pixel.set_avoid_output_alias(true);
197 structured.set_output_size(1, sizeof(uint32_t));
198 structured.set_output_size(2, static_cast<size_t>(4096) * 4 * sizeof(float));
199 structured.set_output_size(3, static_cast<size_t>(256) * sizeof(uint32_t));
200 structured.set_output_size(4, sizeof(uint32_t));
201}
202
203// ============================================================================
204// vision_gpu_config
205// ============================================================================
206
207GpuComputeConfig VisionGpuExecutor::config(VisionOp op, const VisionParams& params)
208{
209 switch (op) {
210 case VisionOp::Threshold: {
211 if (const auto* p = std::get_if<ThresholdParams>(&params); p && p->channels != ChannelMask::NONE)
212 return { .shader_path = "threshold_bands.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ThresholdBandsPC) };
213
214 const auto spec = ShaderSpec::Assemble {}
215 .storage_image("out", BindingDirection::Output)
216 .storage_image("src", BindingDirection::Input)
217 .pc("threshold")
218 .op(KernelOp::CompareGE)
219 .workgroup(k_wg2d[0], k_wg2d[1])
220 .build();
221 return config_from_spec(spec);
222 }
223 case VisionOp::RgbaToGray: {
224 const auto spec = ShaderSpec::Assemble {}
225 .storage_image("out", BindingDirection::Output)
226 .storage_image("src", BindingDirection::Input)
227 .pc("wr")
228 .pc("wg")
229 .pc("wb")
230 .pc("wa")
231 .op(KernelOp::ChannelDot)
232 .workgroup(k_wg2d[0], k_wg2d[1])
233 .build();
234 return config_from_spec(spec);
235 }
236 case VisionOp::GrayToRgba: {
237 const auto spec = ShaderSpec::Assemble {}
238 .storage_image("out", BindingDirection::Output)
239 .storage_image("src", BindingDirection::Input)
240 .op(KernelOp::ChannelReplicate)
241 .workgroup(k_wg2d[0], k_wg2d[1])
242 .build();
243 return config_from_spec(spec);
244 }
245 case VisionOp::GaussianBlur: {
246 const auto spec = ShaderSpec::Assemble {}
247 .tmpl(KernelTemplate::Convolve2D)
248 .storage_image("out", BindingDirection::Output)
249 .storage_image("src", BindingDirection::Input)
250 .ssbo("kernel", BindingDirection::Input, Kakshya::GpuDataFormat::FLOAT32)
254 .workgroup(k_wg2d[0], k_wg2d[1])
255 .build();
256 return config_from_spec(spec);
257 }
258 case VisionOp::NormalizeRange: {
259 const auto spec = ShaderSpec::Assemble {}
260 .storage_image("out", BindingDirection::Output)
261 .storage_image("src", BindingDirection::Input)
262 .pc("scale")
263 .pc("offset")
264 .op(KernelOp::ScaleOffset)
265 .workgroup(k_wg2d[0], k_wg2d[1])
266 .build();
267 return config_from_spec(spec);
268 }
269 case VisionOp::NormalizeInplace: {
270 const auto spec = ShaderSpec::Assemble {}
271 .storage_image("out", BindingDirection::Output)
272 .storage_image("src", BindingDirection::Input)
273 .op(KernelOp::Scale)
274 .workgroup(k_wg2d[0], k_wg2d[1])
275 .build();
276 return config_from_spec(spec);
277 }
278 case VisionOp::Canny: {
279 const auto spec = ShaderSpec::Assemble {}
280 .storage_image("out", BindingDirection::Output)
281 .storage_image("src", BindingDirection::Input)
282 .pc("threshold")
283 .pc("value")
284 .op(KernelOp::CompareGEPreserve)
285 .workgroup(k_wg2d[0], k_wg2d[1])
286 .build();
287 return config_from_spec(spec);
288 }
289 case VisionOp::RgbaToHsv:
290 return { .shader_path = "rgba_to_hsv.comp.spv", .workgroup_size = k_wg2d };
291 case VisionOp::Downsample2x:
292 return { .shader_path = "downsample_2x.comp.spv", .workgroup_size = k_wg2d };
293 case VisionOp::FilterSeparable:
294 return { .shader_path = "filter_separable.comp.spv", .workgroup_size = k_wg2d };
295 case VisionOp::Sobel:
296 return { .shader_path = "sobel.comp.spv", .workgroup_size = k_wg2d };
297 case VisionOp::Scharr:
298 return { .shader_path = "scharr.comp.spv", .workgroup_size = k_wg2d };
299 case VisionOp::Erode:
300 return { .shader_path = "erode.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(MorphPC) };
301 case VisionOp::Dilate:
302 return { .shader_path = "dilate.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(MorphPC) };
303 case VisionOp::Open:
304 return { .shader_path = "open.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(MorphPC) };
305 case VisionOp::Close:
306 return { .shader_path = "close.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(MorphPC) };
307 case VisionOp::MorphGradient:
308 return { .shader_path = "morph_gradient.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(MorphPC) };
309 case VisionOp::HarrisResponse:
310 return { .shader_path = "harris_response.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(HarrisPC) };
311 case VisionOp::ExtractPeaks:
312 return { .shader_path = "extract_peaks.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ExtractPeaksPC) };
313 case VisionOp::ConnectedComponents:
314 return { .shader_path = "cc_colorize.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(uint32_t) * 2 };
315 case VisionOp::FindContours:
316 return { .shader_path = "contour_segments.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ContourSegmentsPC) };
317 case VisionOp::ThresholdAdaptive:
318 return { .shader_path = "threshold_adaptive.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ThresholdAdaptivePC) };
319 case VisionOp::ThresholdOtsu:
320 return { .shader_path = "threshold_otsu.comp.spv", .workgroup_size = { 256, 1, 1 } };
321 case VisionOp::TrackKeypoints:
322 return { .shader_path = "flow_lk.comp.spv", .workgroup_size = { 64, 1, 1 }, .push_constant_size = sizeof(FlowLkPC) };
323 case VisionOp::OpticalFlowDense:
324 return { .shader_path = "flow_dense.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(FlowDensePC) };
325 case VisionOp::Confine:
326 return { .shader_path = "vision_confine.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ConfinePC) };
327 default:
329 }
330}
331
332void VisionGpuExecutor::reset()
333{
334 if (!m_contexts)
335 return;
336
337 auto& contexts = *m_contexts;
338
339 reap_fences(contexts);
340
341 if (contexts.suspended.is_active()) {
342 auto& foundry = Portal::Graphics::get_shader_foundry();
343 foundry.wait_for_fence(contexts.suspended.fence);
344 foundry.release_fence(contexts.suspended.fence);
345 contexts.suspended.fence = Portal::Graphics::INVALID_FENCE;
346 contexts.suspended.finalize = nullptr;
347 }
348
349 contexts.flow_state.have_prev = false;
350 contexts.flow_state.curr_ready = false;
351 contexts.flow_state.last_flow.reset();
352 contexts.flow_state.last_tracks.clear();
353 contexts.flow_state.last_track_count = 0;
354 contexts.flow_state.last_export.reset();
355 contexts.flow_state.export_pending = false;
356
357 contexts.pass.sequence = nullptr;
358 contexts.pass.index = 0;
359 contexts.pass.result = Kinesis::Vision::VisionResult {};
360 contexts.pass.current.reset();
361 contexts.pass.forget();
362 contexts.pass.completed.clear();
363
364 contexts.source.reset();
365 contexts.bound_staged.reset();
366}
367
368void VisionGpuExecutor::after_step(VisionGpuContexts& contexts, size_t index)
369{
370 const auto& steps = contexts.pass.sequence->steps;
371 const VisionOp source_op = steps[index].op;
372 if (source_op != VisionOp::RgbaToGray && source_op != VisionOp::RgbaToHsv)
373 return;
374
375 const uint32_t src_channel = source_op == VisionOp::RgbaToHsv ? 2U : 0U;
376
377 uint32_t levels = 0;
378 bool needs_flow = false;
379 bool confine_seen = false;
380 bool warned = false;
381 for (size_t i = index + 1; i < steps.size(); ++i) {
382 if (steps[i].op == VisionOp::Confine)
383 confine_seen = true;
384
385 const bool is_flow = steps[i].op == VisionOp::TrackKeypoints || steps[i].op == VisionOp::OpticalFlowDense;
386 if (is_flow && confine_seen && !warned) {
388 "run_gpu: Confine sits between RgbaToGray and a flow step; the flow pyramid is "
389 "built from the unconfined gray frame, so results are wrong or rejected. "
390 "Place Confine before RgbaToGray");
391 warned = true;
392 }
393
394 if (steps[i].op == VisionOp::TrackKeypoints) {
395 if (const auto* p = std::get_if<TrackKeypointsParams>(&steps[i].params)) {
396 needs_flow = true;
397 levels = std::max(levels, p->levels);
398 }
399 } else if (steps[i].op == VisionOp::OpticalFlowDense) {
400 if (const auto* p = std::get_if<OpticalFlowDenseParams>(&steps[i].params)) {
401 needs_flow = true;
402 levels = std::max(levels, p->levels);
403 }
404 }
405 }
406
407 if (needs_flow)
408 build_flow_pyramid(contexts, levels, src_channel);
409}
410
411void VisionGpuExecutor::op_snapshot(VisionGpuContexts& contexts)
412{
413 const auto& current = contexts.pass.current;
414 if (!current)
415 return;
416
417 const size_t ordinal = contexts.pass.result.snapshots.size();
418 while (contexts.snapshot_images.size() <= ordinal)
419 contexts.snapshot_images.emplace_back(k_snapshot_cache_budget_bytes);
420
421 const uint32_t w = current->get_width();
422 const uint32_t h = current->get_height();
424 auto image = contexts.snapshot_images[ordinal].acquire(loom, {
425 .width = w,
426 .height = h,
427 .format = Portal::Graphics::ImageFormat::RGBA32F,
429 });
430
431 constexpr auto filter = Portal::Graphics::FilterMode::NEAREST;
432 if (!image || !loom.can_blit(current, image, filter) || !loom.blit_layer(current, image, { .filter = filter })) {
434 "run_gpu: Snapshot {} could not copy the working image", ordinal);
435 return;
436 }
437
438 contexts.pass.result.snapshots.push_back({
439 .image = std::move(image),
440 .w = w,
441 .h = h,
442 .channels = contexts.pass.channels,
443 });
444}
445
446// ============================================================================
447// run_gpu
448// ============================================================================
449
450VisionResult VisionGpuExecutor::run(
451 VisionGpuContexts& contexts,
452 const VisionSequence& sequence,
453 const std::shared_ptr<Core::VKImage>& image,
454 uint32_t w, uint32_t h)
455{
456 auto& pixel_ctx = contexts.pixel;
457 auto& structured_ctx = contexts.structured;
458 auto& label_ctx = contexts.labels;
459 auto& cc_pipeline = contexts.component_contours;
460
461 auto& foundry = Portal::Graphics::get_shader_foundry();
462 std::unordered_map<size_t, CompletedOp> completed_ops;
463 size_t begin = 0;
464
465 if (contexts.suspended.is_active()) {
466 if (&sequence != contexts.pass.sequence) {
468 "run_gpu: polling a suspension with a different sequence; "
469 "the walk continues on the sequence the run started from");
470 }
471
472 if (!foundry.is_fence_signaled(contexts.suspended.fence)) {
473 VisionResult pending;
474 pending.status = VisionStatus::SUSPENDED;
475 pending.suspended_at = contexts.pass.index;
476 return pending;
477 }
478
479 foundry.release_fence(contexts.suspended.fence);
481
482 if (auto finalize = std::move(contexts.suspended.finalize)) {
483 contexts.suspended.finalize = nullptr;
484 finalize(contexts);
485 }
486
487 begin = contexts.pass.index + 1;
488 } else {
489 reap_fences(contexts);
490 contexts.flow_state.curr_ready = false;
491 contexts.pass.begin(sequence, w, h);
492 contexts.pass.storage_w = 0;
493 contexts.pass.storage_h = 0;
494 contexts.bound_staged.reset();
495 const auto seed = op_ingest(contexts, image, w, h);
496 contexts.pass.current = seed;
497 contexts.source = seed;
498 }
499
500 for (contexts.pass.index = begin; contexts.pass.index < sequence.steps.size(); ++contexts.pass.index) {
501 const auto& step = sequence.steps[contexts.pass.index];
502
503 if (step.op == VisionOp::Snapshot) {
504 op_snapshot(contexts);
505 continue;
506 }
507
508 const uint32_t w = contexts.pass.w;
509 const uint32_t h = contexts.pass.h;
510
511 const auto cfg = config(step.op, step.params);
512
513 if (cfg.shader_id == Portal::Graphics::INVALID_SHADER && cfg.shader_path.empty()) {
515 "run_gpu: no GPU implementation for VisionOp {}",
516 static_cast<int>(step.op));
517 return VisionResult {};
518 }
519
520 if (cfg.shader_id != contexts.bound_config.shader_id
521 || cfg.shader_path != contexts.bound_config.shader_path) {
522 pixel_ctx.swap_shader(cfg);
523 contexts.bound_config = cfg;
524 }
525 if (contexts.pass.current != contexts.bound_staged) {
526 pixel_ctx.stage_image(contexts.pass.current);
527 contexts.bound_staged = contexts.pass.current;
528 }
529 if (w != contexts.pass.storage_w || h != contexts.pass.storage_h
530 || (contexts.pass.current && contexts.pass.current == pixel_ctx.get_output_image(0))) {
531 pixel_ctx.prepare_output_image(w, h);
532 contexts.pass.storage_w = w;
533 contexts.pass.storage_h = h;
534 }
535 pixel_ctx.set_output_dimensions(w, h);
536
537 switch (step.op) {
538 case VisionOp::Downsample2x: {
539 const uint32_t new_w = std::max(1U, w / 2);
540 const uint32_t new_h = std::max(1U, h / 2);
541
542 pixel_ctx.prepare_output_image(new_w, new_h);
543 pixel_ctx.set_output_dimensions(new_w, new_h);
544 {
545 const auto f = pixel_ctx.dispatch_async({});
546 foundry.wait_for_fence(f);
547 foundry.release_fence(f);
548 }
549 pixel_ctx.clear_output_dimensions();
550
551 auto downsampled = pixel_ctx.get_output_image(0);
552 completed_ops[Kinesis::Vision::hash_vision_step(step.op, step.params)] = { .output = downsampled, .input = contexts.pass.current };
553 contexts.pass.result.images.downsample_2x = downsampled;
554 contexts.pass.current = downsampled;
555 contexts.bound_staged.reset();
556
557 contexts.pass.set_geometry(new_w, new_h);
558 contexts.pass.storage_w = new_w;
559 contexts.pass.storage_h = new_h;
560
561 continue;
562 }
563 case VisionOp::Threshold: {
564 const auto& p = std::get<ThresholdParams>(step.params);
565 if (p.channels == ChannelMask::NONE) {
566 pixel_ctx.set_push_constants(ThresholdPC { .value = p.value });
567 } else {
568 pixel_ctx.set_push_constants(ThresholdBandsPC {
569 .lo0 = p.bands[0].lo, .lo1 = p.bands[1].lo, .lo2 = p.bands[2].lo,
570 .hi0 = p.bands[0].hi, .hi1 = p.bands[1].hi, .hi2 = p.bands[2].hi,
571 .channels = static_cast<uint32_t>(p.channels) });
572 }
573 break;
574 }
575 case VisionOp::NormalizeRange: {
576 const auto& p = std::get<NormalizeRangeParams>(step.params);
577 const float scale = (p.hi > p.lo) ? 1.0F / (p.hi - p.lo) : 1.0F;
578 const float off = (p.hi > p.lo) ? -p.lo / (p.hi - p.lo) : 0.0F;
579 pixel_ctx.set_push_constants(NormalizePC { .scale = scale, .offset = off });
580 break;
581 }
582 case VisionOp::RgbaToGray:
583 pixel_ctx.set_push_constants(RgbaToGrayPC {
584 .wr = 0.299F, .wg = 0.587F, .wb = 0.114F, .wa = 0.0F });
585 break;
586 case VisionOp::GaussianBlur: {
587 const auto& p = std::get<GaussianBlurParams>(step.params);
588 const auto radius = static_cast<uint32_t>(std::ceil(p.sigma * 3.0F));
589 const auto& weights = gaussian_kernel_2d(radius, p.sigma);
590 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
591 pixel_ctx.set_push_constants(GaussianPC { .radius = radius, .width = w, .height = h });
592 break;
593 }
594 case VisionOp::Erode:
595 case VisionOp::Dilate:
596 case VisionOp::MorphGradient:
597 pixel_ctx.set_push_constants(MorphPC {
598 .radius = std::get<MorphParams>(step.params).radius });
599 break;
600 case VisionOp::ThresholdAdaptive: {
601 const auto& p = std::get<ThresholdAdaptiveParams>(step.params);
602 pixel_ctx.set_push_constants(ThresholdAdaptivePC { .block_size = p.block_size, .offset = p.offset, .channels = static_cast<uint32_t>(p.channels) });
603 break;
604 }
605 case VisionOp::ThresholdOtsu: {
606 const auto* otsu = std::get_if<OtsuParams>(&step.params);
607 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = op_threshold_otsu(contexts, otsu ? *otsu : OtsuParams {});
608 continue;
609 }
610 case VisionOp::Confine: {
611 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = op_confine(contexts, std::get<ConfineParams>(step.params));
612 continue;
613 }
614 case VisionOp::Open:
615 case VisionOp::Close: {
616 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = op_open_close(contexts, step.op, std::get<MorphParams>(step.params));
617 continue;
618 }
619 case VisionOp::Canny: {
620 auto done = op_canny(contexts, step.params, std::get<CannyParams>(step.params));
621 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = done;
622 continue;
623 }
624 case VisionOp::HarrisResponse: {
625 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = op_harris_response(contexts, std::get<HarrisParams>(step.params));
626 continue;
627 }
628 case VisionOp::ExtractPeaks: {
629 op_extract_peaks(contexts, std::get<ExtractPeaksParams>(step.params));
630 continue;
631 }
632 case VisionOp::ConnectedComponents: {
633 if (!contexts.pass.current) {
634 continue;
635 }
636 op_connected_components(contexts, std::get<Kinesis::Vision::ConnectedComponentsParams>(step.params));
637 continue;
638 }
639 case VisionOp::FindContours: {
640 if (!op_find_contours(contexts,
641 std::get<Kinesis::Vision::FindContoursParams>(step.params)))
642 return VisionResult {};
643 continue;
644 }
645 case VisionOp::TrackKeypoints: {
646 if (op_track_keypoints(contexts, step)) {
647 VisionResult pending;
648 pending.status = VisionStatus::SUSPENDED;
649 pending.suspended_at = contexts.pass.index;
650 return pending;
651 }
652 continue;
653 }
654 case VisionOp::OpticalFlowDense: {
655 if (op_dense_flow(contexts, step)) {
656 VisionResult pending;
657 pending.status = VisionStatus::SUSPENDED;
658 pending.suspended_at = contexts.pass.index;
659 return pending;
660 }
661 continue;
662 }
663 default:
664 break;
665 }
666
667 auto dispatch_input = contexts.pass.current;
668 const auto fence = pixel_ctx.dispatch_async({});
669
670 if (step.deferred) {
671 pixel_ctx.clear_output_dimensions();
672 contexts.pass.current = pixel_ctx.get_output_image(0);
673 contexts.bound_staged.reset();
674
675 const Kinesis::Vision::GpuVisionPass::Completed done {
676 .output = contexts.pass.current,
677 .input = dispatch_input
678 };
679 contexts.pass.completed[Kinesis::Vision::hash_vision_step(step.op, step.params)] = done;
680
681 contexts.suspended.fence = fence;
682 contexts.suspended.finalize = [](VisionGpuContexts& c) { after_step(c, c.pass.index); };
683
684 VisionResult pending;
685 pending.status = VisionStatus::SUSPENDED;
686 pending.suspended_at = contexts.pass.index;
687 return pending;
688 }
689
690 foundry.wait_for_fence(fence);
691 foundry.release_fence(fence);
692
693 pixel_ctx.clear_output_dimensions();
694 contexts.pass.current = pixel_ctx.get_output_image(0);
695 contexts.bound_staged.reset();
696 completed_ops[Kinesis::Vision::hash_vision_step(step.op, step.params)] = { .output = contexts.pass.current, .input = dispatch_input };
697
698 switch (step.op) {
699 case VisionOp::RgbaToGray:
700 contexts.pass.result.gray = contexts.pass.current;
701 break;
702 case VisionOp::RgbaToHsv:
703 contexts.pass.result.images.rgba_to_hsv = contexts.pass.current;
704 break;
705 case VisionOp::GrayToRgba:
706 contexts.pass.result.images.gray_to_rgba = contexts.pass.current;
707 break;
708 case VisionOp::Threshold:
709 contexts.pass.result.images.threshold = contexts.pass.current;
710 break;
711 case VisionOp::ThresholdAdaptive:
712 contexts.pass.result.images.threshold_adaptive = contexts.pass.current;
713 break;
714 case VisionOp::NormalizeInplace:
715 contexts.pass.result.images.normalize_inplace = contexts.pass.current;
716 break;
717 case VisionOp::NormalizeRange:
718 contexts.pass.result.images.normalize_range = contexts.pass.current;
719 break;
720 case VisionOp::GaussianBlur:
721 contexts.pass.result.images.gaussian_blur = contexts.pass.current;
722 break;
723 case VisionOp::FilterSeparable:
724 contexts.pass.result.images.filter_separable = contexts.pass.current;
725 break;
726 case VisionOp::Sobel:
727 contexts.pass.result.images.sobel = contexts.pass.current;
728 break;
729 case VisionOp::Scharr:
730 contexts.pass.result.images.scharr = contexts.pass.current;
731 break;
732 case VisionOp::Erode:
733 contexts.pass.result.images.erode = contexts.pass.current;
734 break;
735 case VisionOp::Dilate:
736 contexts.pass.result.images.dilate = contexts.pass.current;
737 break;
738 case VisionOp::MorphGradient:
739 contexts.pass.result.images.morph_gradient = contexts.pass.current;
740 break;
741 default:
742 break;
743 }
744
745 after_step(contexts, contexts.pass.index);
746 }
747
748 return std::move(contexts.pass.result);
749}
750
751VisionResult VisionGpuExecutor::run(
752 const VisionSequence& sequence,
753 const std::shared_ptr<Core::VKImage>& image,
754 uint32_t w, uint32_t h)
755{
756 if (!m_contexts)
757 m_contexts = std::make_unique<VisionGpuContexts>();
758
759 return run(*m_contexts, sequence, image, w, h);
760}
761
762} // namespace MayaFlux::Yantra
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
vk::Fence fence
size_t pixel
Definition Contours.cpp:15
IO::ImageData image
Definition Decoder.cpp:47
glm::vec2 current
float radius
uint32_t h
Definition InkPress.cpp:29
bool done
Definition Picker.cpp:24
std::vector< float > * out
uint32_t index
Definition VKDevice.cpp:122
Assemble & op(KernelOp o)
Set the named operation the emitter will lower to SPIR-V.
ShaderSpec build()
Finalise and return the ShaderSpec.
Assemble & pc(std::string name, Kakshya::GpuDataFormat format)
Declare a push constant field with explicit format.
Assemble & ssbo(std::string name, BindingDirection direction, Kakshya::GpuDataFormat format, Kakshya::DataModality modality=Kakshya::DataModality::SCALAR_F32)
Declare an SSBO binding.
Assemble & workgroup(uint32_t x, uint32_t y=1, uint32_t z=1)
Override workgroup size.
Assemble & tmpl(KernelTemplate t)
Set the kernel template.
Assemble & storage_image(std::string name, BindingDirection direction=BindingDirection::Output)
Declare a storage image binding (image2D).
Fluent assembler producing a ShaderSpec.
GpuExecutionContext specialisation for image compute shaders.
void run()
Definition main.cpp:21
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
ImageFormat
User-friendly image format enum.
Definition ImageData.hpp:12
@ FLOAT32
32-bit float (standard GPU)
size_t hash_vision_step(VisionOp op, const VisionParams &params)
Hash a VisionStep's op and parameters together.
Definition VisionOp.hpp:621
VisionOp
Named operations available in a VisionSequence.
Definition VisionOp.hpp:30
std::variant< std::monostate, ThresholdParams, ThresholdAdaptiveParams, OtsuParams, NormalizeRangeParams, GaussianBlurParams, FilterSeparableParams, CannyParams, MorphParams, HarrisParams, ExtractPeaksParams, TrackKeypointsParams, ConnectedComponentsParams, FindContoursParams, OpticalFlowDenseParams, ConfineParams > VisionParams
Parameter variant covering all ops that carry parameters.
Definition VisionOp.hpp:358
constexpr ShaderID INVALID_SHADER
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
@ NEAREST
Nearest neighbor (pixelated)
constexpr std::array< uint32_t, 3 > k_wg2d
Standard 2D workgroup used by all pixel-to-pixel vision shaders.
Portal::Graphics::GpuComputeConfig GpuComputeConfig
GpuComputeConfig config_from_spec(const Portal::Graphics::ShaderSpec &spec)
Derive a GpuComputeConfig from a ShaderSpec.
static constexpr DomainSpec Graphics
Domain constant for Graphics domain.
Definition Creator.hpp:444
std::shared_ptr< Core::VKImage > scharr
std::shared_ptr< Core::VKImage > filter_separable
std::shared_ptr< Core::VKImage > threshold_adaptive
std::shared_ptr< Core::VKImage > rgba_to_hsv
std::shared_ptr< Core::VKImage > gaussian_blur
std::shared_ptr< Core::VKImage > threshold
std::shared_ptr< Core::VKImage > normalize_range
std::shared_ptr< Core::VKImage > downsample_2x
std::shared_ptr< Core::VKImage > sobel
std::shared_ptr< Core::VKImage > dilate
std::shared_ptr< Core::VKImage > normalize_inplace
std::shared_ptr< Core::VKImage > erode
std::shared_ptr< Core::VKImage > gray_to_rgba
std::shared_ptr< Core::VKImage > morph_gradient
void begin(const VisionSequence &seq, uint32_t width, uint32_t height)
Reset the walk band for a fresh run.
std::unordered_map< size_t, Completed > completed
void set_geometry(uint32_t width, uint32_t height) noexcept
std::vector< SnapshotEntry > snapshots
std::shared_ptr< Core::VKImage > gray
Output of a RgbaToGray step in this sequence, or null.
ImageOutputs images
Image output of whichever image-producing ops ran in this sequence, one named slot per op.
Result of executing a VisionSequence on one frame.
Ordered VisionSteps and the backend chosen to execute them.
Definition VisionOp.hpp:381
enum MayaFlux::Portal::Graphics::GpuBufferBinding::Direction INPUT
enum MayaFlux::Portal::Graphics::GpuBufferBinding::ElementType FLOAT32
Declares a single storage buffer or image binding a compute shader expects.
Plain-data description of the compute shader to dispatch.
std::function< void(VisionGpuContexts &)> finalize
Work to run once the fence has signaled, before the walk resumes.
TextureExecutionContext component_contours
Shared image and buffer pipeline for ConnectedComponents and FindContours.
GpuComputeConfig bound_config
Shader currently bound on pixel, and the image staged into it.
TextureExecutionContext labels
Image + aux SSBO.
VisionGpuContexts()
Construct all three contexts in place with the one correct binding layout for every currently GPU-imp...
Kinesis::Vision::GpuVisionPass pass
Walk state for the current run: sequence position, geometry, working image, and the result under cons...
std::shared_ptr< Core::VKImage > source
Input image the current walk started from.
std::shared_ptr< Core::VKImage > bound_staged
TextureExecutionContext pixel
Image pipeline.
std::vector< Portal::Graphics::ImageCacheSet > snapshot_images
One image cache per Snapshot step, indexed by the step's order among the snapshots of a run.
TextureExecutionContext structured
Buffer-only readback.
Fixed set of TextureExecutionContexts covering every GPU-implemented VisionOp shape.