9using namespace Portal::Graphics;
10using namespace Kinesis::Vision;
17 constexpr uint32_t CC_BACKGROUND_HOST = 0xFFFFFFFFU;
18 constexpr uint32_t CC_UNCLAIMED_HOST = 0
U;
21 constexpr std::array<uint32_t, 3> k_wg2d { 8, 8, 1 };
23 constexpr uint32_t k_max_components = 4096;
25 constexpr uint32_t k_max_points_per_contour = 4096;
27 constexpr uint32_t k_max_holes_per_label = 4;
29 constexpr uint32_t k_max_trace_slots = k_max_components * (1U + k_max_holes_per_label);
34 struct ThresholdAdaptivePC {
76 std::shared_ptr<Core::VKImage>
output;
77 std::shared_ptr<Core::VKImage>
input;
90 struct ExtractPeaksPC {
97 struct CCBlockInitPC {
109 struct CCCompressPC {
113 struct CCFinalLabelPC {
125 struct ContourSegmentsPC {
130 struct ContourLinkPC {
135 struct ContourClearPC {
139 struct ContourRenderPC {
146 struct ContourMarchPC {
156 struct ContourCompactPC {
173 const std::vector<float>& gaussian_kernel_2d(uint32_t
radius,
float sigma)
175 static std::unordered_map<uint64_t, std::vector<float>> cache;
176 const uint64_t key = (
static_cast<uint64_t
>(std::bit_cast<uint32_t>(
sigma)) << 32)
178 auto it = cache.find(key);
179 if (it != cache.end())
182 const uint32_t diam = 2 *
radius + 1;
183 std::vector<float>
k(
static_cast<size_t>(diam) * diam);
185 for (uint32_t y = 0; y < diam; ++y) {
186 for (uint32_t x = 0; x < diam; ++x) {
187 const float fx =
static_cast<float>(x) -
static_cast<float>(
radius);
188 const float fy =
static_cast<float>(y) -
static_cast<float>(
radius);
189 const float v = std::exp(-(fx * fx + fy * fy) / (2.0F *
sigma *
sigma));
197 return cache.emplace(key, std::move(
k)).first->second;
213 const std::vector<float>& gaussian_kernel_1d(uint32_t
radius,
float sigma)
215 static std::unordered_map<uint64_t, std::vector<float>> cache;
216 const uint64_t key = (
static_cast<uint64_t
>(std::bit_cast<uint32_t>(
sigma)) << 32)
218 auto it = cache.find(key);
219 if (it != cache.end())
222 const uint32_t size = 2 *
radius + 1;
223 std::vector<float>
k(size);
225 for (uint32_t i = 0; i < size; ++i) {
226 const float x =
static_cast<float>(i) -
static_cast<float>(
radius);
227 k[i] = std::exp(-(x * x) / (2.0F *
sigma *
sigma));
233 return cache.emplace(key, std::move(
k)).first->second;
236 GpuVisionPass::Completed op_threshold_otsu(VisionGpuContexts& contexts)
238 auto& pixel_ctx = contexts.pixel;
239 auto& structured_ctx = contexts.structured;
240 auto w = contexts.pass.w;
241 auto h = contexts.pass.h;
244 const auto otsu_input = contexts.pass.current;
246 structured_ctx.swap_shader({
247 .shader_path =
"otsu_histogram.comp.spv",
248 .workgroup_size = k_wg2d,
249 .push_constant_size =
sizeof(OtsuHistPC),
251 std::vector<uint32_t> zeros(256, 0);
252 structured_ctx.set_binding_data(3, std::span<const uint32_t>(zeros));
253 structured_ctx.stage_image(contexts.pass.current);
254 structured_ctx.set_push_constants(OtsuHistPC { .width = w, .height =
h });
255 structured_ctx.set_output_dimensions(w,
h);
257 const auto f = structured_ctx.dispatch_async({});
258 structured_ctx.clear_output_dimensions();
259 foundry.wait_for_fence(f);
260 foundry.release_fence(f);
263 const auto hist_check = structured_ctx.collect_result();
264 std::vector<uint32_t> hist_readback(256, 0);
265 if (
auto it = hist_check.aux.find(3); it != hist_check.aux.end())
266 std::memcpy(hist_readback.data(), it->second.data(), 256 *
sizeof(uint32_t));
268 structured_ctx.swap_shader({
269 .shader_path =
"otsu_select.comp.spv",
270 .workgroup_size = { 256, 1, 1 },
272 structured_ctx.set_binding_data(3, std::span<const uint32_t>(hist_readback));
273 structured_ctx.set_output_dimensions(256, 1);
276 const auto f = structured_ctx.dispatch_async({});
277 structured_ctx.clear_output_dimensions();
278 foundry.wait_for_fence(f);
279 foundry.release_fence(f);
282 const auto sel_result = structured_ctx.collect_result();
283 uint32_t best_bin = 0;
284 if (
auto it = sel_result.aux.find(4); it != sel_result.aux.end())
285 std::memcpy(&best_bin, it->second.data(),
sizeof(uint32_t));
286 const float t_norm =
static_cast<float>(best_bin) / 255.0F;
289 ShaderSpec::Assemble {}
290 .storage_image(
"out", BindingDirection::Output)
291 .storage_image(
"src", BindingDirection::Input)
293 .op(KernelOp::CompareGE)
294 .workgroup(k_wg2d[0], k_wg2d[1])
296 pixel_ctx.swap_shader(apply_cfg);
297 pixel_ctx.stage_image(contexts.pass.current);
298 pixel_ctx.set_push_constants(ThresholdPC { .value = t_norm });
299 pixel_ctx.prepare_output_image(w,
h);
301 const auto f = pixel_ctx.dispatch_async({});
302 foundry.wait_for_fence(f);
303 foundry.release_fence(f);
305 auto thresholded = pixel_ctx.get_output_image(0);
307 contexts.pass.result.debug_labels = thresholded;
308 contexts.pass.current = thresholded;
309 contexts.pass.result.structured = std::monostate {};
311 contexts.bound_config = apply_cfg;
312 contexts.bound_staged = otsu_input;
314 return { .output = thresholded, .input = otsu_input };
317 GpuVisionPass::Completed op_open_close(
318 VisionGpuContexts& contexts,
320 const MorphParams& p)
322 auto& pixel_ctx = contexts.pixel;
323 auto w = contexts.pass.w;
324 auto h = contexts.pass.h;
327 const auto morph_input = contexts.pass.current;
328 const auto radius = p.radius;
329 const bool is_open = (op == VisionOp::Open);
332 .
shader_path = is_open ?
"erode.comp.spv" :
"dilate.comp.spv",
333 .workgroup_size = k_wg2d,
334 .push_constant_size =
sizeof(MorphPC),
336 pixel_ctx.swap_shader(first_cfg);
337 pixel_ctx.stage_image(contexts.pass.current);
338 pixel_ctx.set_push_constants(MorphPC { .radius =
radius });
339 pixel_ctx.prepare_output_image(w,
h);
340 pixel_ctx.set_output_dimensions(w,
h);
342 const auto f = pixel_ctx.dispatch_async({});
343 foundry.wait_for_fence(f);
344 foundry.release_fence(f);
346 auto intermediate = pixel_ctx.get_output_image(0);
349 .
shader_path = is_open ?
"dilate.comp.spv" :
"erode.comp.spv",
350 .workgroup_size = k_wg2d,
351 .push_constant_size =
sizeof(MorphPC),
353 pixel_ctx.swap_shader(second_cfg);
354 pixel_ctx.stage_image(intermediate);
355 pixel_ctx.set_push_constants(MorphPC { .radius =
radius });
356 pixel_ctx.prepare_output_image(w,
h);
357 pixel_ctx.set_output_dimensions(w,
h);
359 const auto f = pixel_ctx.dispatch_async({});
360 foundry.wait_for_fence(f);
361 foundry.release_fence(f);
364 auto opened_closed = pixel_ctx.get_output_image(0);
365 contexts.pass.current = opened_closed;
366 contexts.pass.result.structured = std::monostate {};
368 contexts.bound_config = second_cfg;
369 contexts.bound_staged = intermediate;
371 return { .output = opened_closed, .input = morph_input };
374 GpuVisionPass::Completed op_canny(
375 VisionGpuContexts& contexts,
377 const CannyParams& p)
379 auto& pixel_ctx = contexts.pixel;
380 auto& label_ctx = contexts.labels;
381 auto w = contexts.pass.w;
382 auto h = contexts.pass.h;
385 const auto canny_input = contexts.pass.current;
388 VisionOp::GaussianBlur, GaussianBlurParams { .sigma = p.sigma });
389 std::shared_ptr<Core::VKImage> blurred;
391 if (
auto it = contexts.pass.completed.find(blur_key);
392 it != contexts.pass.completed.end() && it->second.input == canny_input) {
393 blurred = it->second.output;
395 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
396 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
398 pixel_ctx.swap_shader(blur_cfg);
399 pixel_ctx.stage_image(canny_input);
400 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
401 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
402 pixel_ctx.prepare_output_image(w,
h);
404 const auto f = pixel_ctx.dispatch_async({});
405 foundry.wait_for_fence(f);
406 foundry.release_fence(f);
408 blurred = pixel_ctx.get_output_image(0);
409 contexts.pass.completed[blur_key] = { .output = blurred, .input = canny_input };
413 std::shared_ptr<Core::VKImage> grad;
414 if (
auto it = contexts.pass.completed.find(sobel_key);
415 it != contexts.pass.completed.end() && it->second.input == blurred) {
416 grad = it->second.output;
419 pixel_ctx.swap_shader(sobel_cfg);
420 pixel_ctx.stage_image(blurred);
421 pixel_ctx.prepare_output_image(w,
h);
423 const auto f = pixel_ctx.dispatch_async({});
424 foundry.wait_for_fence(f);
425 foundry.release_fence(f);
427 grad = pixel_ctx.get_output_image(0);
428 contexts.pass.completed[sobel_key] = { .output = grad, .input = blurred };
433 .workgroup_size = k_wg2d,
435 pixel_ctx.swap_shader(nms_cfg);
436 pixel_ctx.stage_image(grad);
437 pixel_ctx.prepare_output_image(w,
h);
439 const auto f = pixel_ctx.dispatch_async({});
440 foundry.wait_for_fence(f);
441 foundry.release_fence(f);
443 auto suppressed = pixel_ctx.get_output_image(0);
446 pixel_ctx.swap_shader(classify_cfg);
447 pixel_ctx.stage_image(suppressed);
448 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.low_threshold, .value = 0.5F });
449 pixel_ctx.prepare_output_image(w,
h);
451 const auto f = pixel_ctx.dispatch_async({});
452 foundry.wait_for_fence(f);
453 foundry.release_fence(f);
455 auto classified_weak = pixel_ctx.get_output_image(0);
457 pixel_ctx.stage_image(classified_weak);
458 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.high_threshold, .value = 1.0F });
459 pixel_ctx.prepare_output_image(w,
h);
461 const auto f = pixel_ctx.dispatch_async({});
462 foundry.wait_for_fence(f);
463 foundry.release_fence(f);
465 auto classified = pixel_ctx.get_output_image(0);
467 constexpr uint32_t k_max_hysteresis_rounds = 64;
468 label_ctx.set_output_size(2,
sizeof(uint32_t));
469 label_ctx.set_output_dimensions(w,
h);
470 label_ctx.swap_shader({
471 .shader_path =
"canny_hysteresis.comp.spv",
472 .workgroup_size = k_wg2d,
473 .push_constant_size =
sizeof(HysteresisPC),
479 label_ctx.set_binding_data(2, std::span<const uint32_t>(&zero, 1));
480 const HysteresisPC hpc { .width = w, .height =
h };
481 ExecutionContext chained_ctx;
483 chained_ctx.parameters = ChainedParams {
484 .pass_count = k_max_hysteresis_rounds,
485 .pc_updater = [hpc](uint32_t,
void* dst) { std::memcpy(dst, &hpc,
sizeof(HysteresisPC)); },
486 .passes_per_batch = k_max_hysteresis_rounds,
488 label_ctx.execute(Datum<> {}, chained_ctx);
491 auto hysteresis_result = classified;
494 ShaderSpec::Assemble {}
495 .storage_image(
"out", BindingDirection::Output)
496 .storage_image(
"src", BindingDirection::Input)
498 .op(KernelOp::CompareGE)
499 .workgroup(k_wg2d[0], k_wg2d[1])
501 pixel_ctx.swap_shader(finalize_cfg);
502 pixel_ctx.stage_image(hysteresis_result);
503 pixel_ctx.set_push_constants(FinalizePC { .threshold = 1.0F });
504 pixel_ctx.prepare_output_image(w,
h);
506 const auto f = pixel_ctx.dispatch_async({});
507 foundry.wait_for_fence(f);
508 foundry.release_fence(f);
510 auto finalized = pixel_ctx.get_output_image(0);
512 contexts.pass.result.debug_labels = finalized;
513 contexts.pass.current = finalized;
514 contexts.pass.result.structured = std::monostate {};
516 contexts.bound_config = finalize_cfg;
517 contexts.bound_staged = hysteresis_result;
519 return { .output = finalized, .input = canny_input };
522 GpuVisionPass::Completed op_harris_response(
523 VisionGpuContexts& contexts,
524 const HarrisParams& p)
526 auto& pixel_ctx = contexts.pixel;
527 auto w = contexts.pass.w;
528 auto h = contexts.pass.h;
531 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
532 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
534 const auto harris_input = contexts.pass.current;
536 pixel_ctx.swap_shader({ .shader_path =
"harris_grad_pack.comp.spv", .workgroup_size = k_wg2d });
537 pixel_ctx.stage_image(harris_input);
538 pixel_ctx.prepare_output_image(w,
h);
540 const auto f = pixel_ctx.dispatch_async({});
541 foundry.wait_for_fence(f);
542 foundry.release_fence(f);
544 auto packed = pixel_ctx.get_output_image(0);
547 pixel_ctx.swap_shader(blur_cfg);
548 pixel_ctx.stage_image(packed);
549 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
550 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
551 pixel_ctx.prepare_output_image(w,
h);
553 const auto f = pixel_ctx.dispatch_async({});
554 foundry.wait_for_fence(f);
555 foundry.release_fence(f);
557 auto smoothed = pixel_ctx.get_output_image(0);
561 .workgroup_size = k_wg2d,
562 .push_constant_size =
sizeof(HarrisPC),
564 pixel_ctx.swap_shader(harris_resp_cfg);
565 pixel_ctx.stage_image(smoothed);
569 const uint32_t peak_reset = 0
U;
570 pixel_ctx.set_binding_data(2, std::span<const uint32_t>(&peak_reset, 1));
571 pixel_ctx.set_push_constants(HarrisPC { .k = p.k, .pass = 0
U, .width = w, .height =
h });
572 pixel_ctx.prepare_output_image(w,
h);
574 const auto f = pixel_ctx.dispatch_async({});
575 foundry.wait_for_fence(f);
576 foundry.release_fence(f);
579 pixel_ctx.set_binding_data(2, std::span<const uint32_t>(&peak_reset, 0));
580 pixel_ctx.set_push_constants(HarrisPC { .k = p.k, .pass = 1U, .width = w, .height =
h });
582 const auto f = pixel_ctx.dispatch_async({});
583 foundry.wait_for_fence(f);
584 foundry.release_fence(f);
587 contexts.pass.current = pixel_ctx.get_output_image(0);
588 contexts.pass.result.structured = std::monostate {};
590 contexts.bound_config = harris_resp_cfg;
591 contexts.bound_staged = smoothed;
593 return { .output = contexts.pass.current, .input = harris_input };
596 void op_extract_peaks(
597 VisionGpuContexts& contexts,
598 const ExtractPeaksParams& p)
600 auto& structured_ctx = contexts.structured;
601 auto w = contexts.pass.w;
602 auto h = contexts.pass.h;
605 constexpr uint32_t k_max_kp = 4096;
607 structured_ctx.swap_shader({
608 .shader_path =
"extract_peaks.comp.spv",
609 .workgroup_size = { 8, 8, 1 },
610 .push_constant_size =
sizeof(ExtractPeaksPC),
613 structured_ctx.set_output_size(1,
sizeof(uint32_t));
614 structured_ctx.set_output_size(2,
static_cast<size_t>(k_max_kp) * 4 *
sizeof(float));
616 structured_ctx.stage_image(contexts.pass.current);
617 structured_ctx.set_push_constants(ExtractPeaksPC {
618 .threshold = p.threshold,
619 .nms_radius = p.nms_radius,
622 .max_keypoints = k_max_kp,
625 structured_ctx.set_output_dimensions(w,
h);
626 const auto fence = structured_ctx.dispatch_async({});
627 structured_ctx.clear_output_dimensions();
628 foundry.wait_for_fence(
fence);
629 foundry.release_fence(
fence);
631 const auto* next = contexts.pass.ahead();
632 if (next && next->op == VisionOp::TrackKeypoints) {
633 contexts.pass.result.structured = std::monostate {};
634 contexts.pass.result.w = w;
635 contexts.pass.result.h =
h;
639 const auto gpu_result = structured_ctx.collect_result();
642 if (
auto it = gpu_result.aux.find(1); it != gpu_result.aux.end())
643 std::memcpy(&
count, it->second.data(),
sizeof(uint32_t));
647 float x, y, response, pad;
649 std::vector<GpuKp> raw(
count);
651 if (
auto it = gpu_result.aux.find(2); it != gpu_result.aux.end())
652 std::memcpy(raw.data(), it->second.data(),
count *
sizeof(GpuKp));
655 std::vector<Kinesis::Vision::Keypoint> kpts;
657 for (
const auto& kp : raw) {
658 kpts.push_back({ .position = { kp.x, kp.y },
659 .response = kp.response,
663 std::ranges::sort(kpts, [](
const auto&
a,
const auto&
b) {
return a.response >
b.response; });
665 contexts.pass.result.structured = std::move(kpts);
666 contexts.pass.result.w = 0;
667 contexts.pass.result.h = 0;
670 void op_connected_components(
671 VisionGpuContexts& contexts,
672 const ConnectedComponentsParams& p)
674 auto& cc_pipeline = contexts.cc_pipeline;
675 auto w = contexts.pass.w;
676 auto h = contexts.pass.h;
679 const auto seed_input = contexts.pass.current;
683 const double block_diagonal = std::sqrt(
685 const auto k_compress_passes =
static_cast<uint32_t
>(std::ceil(std::log2(std::max(2.0, block_diagonal))));
698 const auto* next = contexts.pass.ahead();
699 const bool contours_follow = next && next->op == VisionOp::FindContours;
700 const uint32_t
export_labels = (p.export_labels || contours_follow) ? 1U : 0
U;
702 const CCFinalLabelPC final_pc {
707 .max_components = k_max_components,
711 cc_pipeline.swap_shader({ .shader_path =
"cc_reset.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(CCResetPC) });
712 cc_pipeline.set_push_constants(CCResetPC {
714 .max_components = k_max_components,
718 const auto reset_fence = cc_pipeline.dispatch_async({});
719 foundry.wait_for_fence(reset_fence);
720 foundry.release_fence(reset_fence);
722 cc_pipeline.clear_output_dimensions();
724 const std::array<uint32_t, 3> block_groups {
730 std::vector<DependencyStage> cc_stages;
732 cc_stages.push_back({
733 .config = { .shader_path =
"cc_block_init.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCBlockInitPC) },
734 .stage_fn = [&](GpuDispatchCore& ctx) {
736 ctx.set_push_constants(init_pc);
738 .hazard_fn = [&](GpuDispatchCore& ctx) -> std::vector<Portal::Graphics::HazardResource> {
740 ctx.shared_buffer_hazard(
744 .explicit_groups = block_groups,
747 cc_stages.push_back({
748 .config = { .shader_path =
"cc_merge.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCMergePC) },
749 .stage_fn = [&](GpuDispatchCore& ctx) {
751 ctx.set_push_constants(merge_pc);
753 .hazard_fn = [&](GpuDispatchCore& ctx) -> std::vector<Portal::Graphics::HazardResource> {
755 ctx.shared_buffer_hazard(
759 .explicit_groups = block_groups,
762 ExecutionContext cc_ctx;
764 DependencyParams params;
765 params.stages = cc_stages;
766 cc_ctx.parameters = params;
767 cc_pipeline.execute(Datum<> {}, cc_ctx);
771 const std::array<uint32_t, 3> full_grid_indirect {
776 cc_pipeline.upload_shared_raw(0, 10,
reinterpret_cast<const uint8_t*
>(full_grid_indirect.data()), full_grid_indirect.size() *
sizeof(uint32_t));
778 cc_pipeline.swap_shader({ .shader_path =
"cc_compress.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCCompressPC) });
782 const auto fence = cc_pipeline.dispatch_async({});
783 foundry.wait_for_fence(
fence);
784 foundry.release_fence(
fence);
786 cc_pipeline.clear_output_dimensions();
788 cc_pipeline.swap_shader({ .shader_path =
"cc_final_label.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCFinalLabelPC) });
790 cc_pipeline.set_push_constants(final_pc);
791 cc_pipeline.set_output_dimensions(w,
h);
792 cc_pipeline.prepare_output_image(w,
h);
794 const auto fence = cc_pipeline.dispatch_async({});
795 foundry.wait_for_fence(
fence);
796 foundry.release_fence(
fence);
798 cc_pipeline.clear_output_dimensions();
800 contexts.pass.result.debug_labels = p.with_colors ? cc_pipeline.get_output_image(0) :
nullptr;
802 uint32_t compact_count = 0;
803 cc_pipeline.download_shared(0, 5, &compact_count,
sizeof(uint32_t));
804 compact_count = std::min(compact_count, k_max_components);
806 Kinesis::Vision::ComponentResult cc_result;
807 cc_result.count = compact_count;
808 cc_result.boxes.reserve(compact_count);
810 if (compact_count > 0) {
811 std::vector<glm::uvec2> bmin(compact_count);
812 std::vector<glm::uvec2> bmax(compact_count);
813 std::vector<uint32_t> bcount(compact_count);
814 cc_pipeline.download_shared(0, 7, bmin.data(), bmin.size() *
sizeof(glm::uvec2));
815 cc_pipeline.download_shared(0, 8, bmax.data(), bmax.size() *
sizeof(glm::uvec2));
816 cc_pipeline.download_shared(0, 9, bcount.data(), bcount.size() *
sizeof(uint32_t));
818 const float inv_w = 1.0F /
static_cast<float>(w);
819 const float inv_h = 1.0F /
static_cast<float>(
h);
821 for (uint32_t i = 0; i < compact_count; ++i) {
824 const float x =
static_cast<float>(bmin[i].x) * inv_w;
825 const float y =
static_cast<float>(bmin[i].y) * inv_h;
826 const float bw =
static_cast<float>(bmax[i].x - bmin[i].x + 1) * inv_w;
827 const float bh =
static_cast<float>(bmax[i].y - bmin[i].y + 1) * inv_h;
828 cc_result.boxes.push_back({ .x = x, .y = y, .w = bw, .h = bh, .confidence = 1.0F, .label_id = i + 1 });
832 contexts.pass.result.structured = std::move(cc_result);
833 contexts.pass.result.w = 0;
834 contexts.pass.result.h = 0;
837 bool op_find_contours(
838 VisionGpuContexts& contexts,
839 const FindContoursParams& p)
841 const auto* prev = contexts.pass.behind();
842 if (!prev || prev->op != VisionOp::ConnectedComponents) {
844 "run_gpu: FindContours requires ConnectedComponents as the immediately preceding step");
848 auto& cc_pipeline = contexts.cc_pipeline;
849 auto w = contexts.pass.w;
850 auto h = contexts.pass.h;
865 std::vector<uint32_t> owner_reset(
static_cast<size_t>(k_max_components) + 1U, CC_UNCLAIMED_HOST);
866 cc_pipeline.upload_shared_raw(1, 4,
reinterpret_cast<const uint8_t*
>(owner_reset.data()), owner_reset.size() *
sizeof(uint32_t));
868 std::vector<uint32_t> hole_owner_reset(
static_cast<size_t>(k_max_components) * k_max_holes_per_label, CC_UNCLAIMED_HOST);
869 cc_pipeline.upload_shared_raw(1, 5,
reinterpret_cast<const uint8_t*
>(hole_owner_reset.data()), hole_owner_reset.size() *
sizeof(uint32_t));
871 const uint32_t zero = 0;
872 cc_pipeline.upload_shared_raw(1, 7,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
873 cc_pipeline.upload_shared_raw(1, 9,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
876 auto max_points = p.max_points_per_contour > 0 ? std::min<uint32_t>(p.max_points_per_contour, k_max_points_per_contour) : k_max_points_per_contour;
877 cc_pipeline.swap_shader({ .shader_path =
"contour_march.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ContourMarchPC) });
879 cc_pipeline.prepare_output_image(w,
h);
880 cc_pipeline.set_push_constants(ContourMarchPC {
883 .max_components = k_max_components,
884 .max_points_per_contour = max_points,
885 .max_holes_per_label = k_max_holes_per_label,
887 .min_area = p.min_area,
888 .compacted_count = 0
U });
890 cc_pipeline.set_output_dimensions(w,
h);
892 const auto fence = cc_pipeline.dispatch_async({});
893 foundry.wait_for_fence(
fence);
894 foundry.release_fence(
fence);
897 cc_pipeline.set_push_constants(ContourMarchPC {
900 .max_components = k_max_components,
901 .max_points_per_contour = max_points,
902 .max_holes_per_label = k_max_holes_per_label,
904 .min_area = p.min_area,
905 .compacted_count = 0
U });
907 const auto fence = cc_pipeline.dispatch_async({});
908 foundry.wait_for_fence(
fence);
909 foundry.release_fence(
fence);
912 cc_pipeline.clear_output_dimensions();
914 cc_pipeline.swap_shader({ .shader_path =
"contour_compact.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourCompactPC) });
915 cc_pipeline.set_push_constants(ContourCompactPC { .max_components = k_max_components, .max_holes_per_label = k_max_holes_per_label });
916 const uint32_t total_owner_slots = k_max_components * (1U + k_max_holes_per_label);
917 cc_pipeline.set_output_dimensions(total_owner_slots, 1);
919 const auto fence = cc_pipeline.dispatch_async({});
920 foundry.wait_for_fence(
fence);
921 foundry.release_fence(
fence);
923 cc_pipeline.clear_output_dimensions();
928 cc_pipeline.swap_shader({ .shader_path =
"contour_march.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourMarchPC) });
929 cc_pipeline.set_push_constants(ContourMarchPC {
932 .max_components = k_max_components,
933 .max_points_per_contour = max_points,
934 .max_holes_per_label = k_max_holes_per_label,
936 .min_area = p.min_area,
940 const auto fence = cc_pipeline.dispatch_async({});
941 foundry.wait_for_fence(
fence);
942 foundry.release_fence(
fence);
944 cc_pipeline.clear_output_dimensions();
946 if (p.max_contours > 0U) {
947 constexpr uint32_t
k = 12U;
948 constexpr uint32_t total_passes =
k * (
k + 1U) / 2U;
951 ShaderSpec::Assemble {}
952 .tmpl(KernelTemplate::BitonicSort)
963 cc_pipeline.set_output_dimensions(k_max_components, 1U);
965 ExecutionContext bitonic_ctx;
967 bitonic_ctx.parameters = ChainedParams {
968 .pass_count = total_passes,
969 .pc_updater = [
k](uint32_t p_idx,
void* pc_ptr) {
970 uint32_t stage = 0,
pass = 0, remaining = p_idx;
971 for (uint32_t s = 0; s <
k; ++s) {
972 if (remaining <= s) {
977 remaining -= (s + 1);
982 *
static_cast<PC*
>(pc_ptr) = { .stage = stage, .pass =
pass, .count = k_max_components, .descending = 1U };
986 cc_pipeline.execute(Datum<std::vector<Kakshya::DataVariant>> {}, bitonic_ctx);
987 cc_pipeline.clear_output_dimensions();
991 cc_pipeline.swap_shader({ .shader_path =
"contour_render_clear.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ContourClearPC) });
992 cc_pipeline.prepare_output_image(w,
h);
993 cc_pipeline.set_push_constants(ContourClearPC { .width = w, .height =
h });
994 cc_pipeline.set_output_dimensions(w,
h);
996 const auto fence = cc_pipeline.dispatch_async({});
997 foundry.wait_for_fence(
fence);
998 foundry.release_fence(
fence);
1000 cc_pipeline.clear_output_dimensions();
1002 cc_pipeline.swap_shader({ .shader_path =
"contour_render.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourRenderPC) });
1003 cc_pipeline.set_push_constants(ContourRenderPC { .width = w, .height =
h, .max_components = k_max_components, .max_points_per_contour = k_max_points_per_contour, .max_contours = p.max_contours });
1004 const uint32_t render_slots = p.max_contours > 0
U ? std::min(p.max_contours, k_max_components) : k_max_trace_slots;
1005 cc_pipeline.set_output_dimensions(render_slots * k_max_points_per_contour, 1U);
1007 const auto fence = cc_pipeline.dispatch_async({});
1008 foundry.wait_for_fence(
fence);
1009 foundry.release_fence(
fence);
1011 cc_pipeline.clear_output_dimensions();
1013 contexts.pass.result.debug_contours = cc_pipeline.get_output_image(0);
1014 contexts.pass.result.structured = std::monostate {};
1015 contexts.pass.result.w = 0;
1016 contexts.pass.result.h = 0;
1021 cc_pipeline.download_shared(1, 10, meta.data(),
compacted_count *
sizeof(glm::uvec4));
1023 cc_pipeline.download_shared(1, 11, area_perim.data(),
compacted_count *
sizeof(glm::vec2));
1024 uint32_t points_written = 0;
1025 cc_pipeline.download_shared(1, 9, &points_written,
sizeof(uint32_t));
1026 std::vector<glm::vec2> flat_points_full(points_written);
1028 if (points_written > 0)
1029 cc_pipeline.download_shared(1, 8, flat_points_full.data(),
static_cast<size_t>(points_written) *
sizeof(glm::vec2));
1031 std::vector<uint32_t> order;
1032 if (p.max_contours > 0U) {
1034 std::vector<float> sorted_indices(take);
1035 cc_pipeline.download_shared(2, 1, sorted_indices.data(), take *
sizeof(
float));
1036 order.reserve(take);
1037 for (
float f : sorted_indices)
1038 order.push_back(static_cast<uint32_t>(f));
1045 std::vector<Kinesis::Vision::Contour> out_contours;
1046 out_contours.reserve(order.size());
1048 for (uint32_t idx : order) {
1051 const auto& m = meta[idx];
1054 if (m.x > points_written || m.y > points_written - m.x)
1057 std::vector<glm::vec2> pts(
1058 flat_points_full.begin() + m.x,
1059 flat_points_full.begin() + m.x + m.y);
1060 const glm::vec2 ap = area_perim[idx];
1061 out_contours.push_back({ .points = std::move(pts), .area = ap.x, .perimeter = ap.y, .parent_label = m.z });
1064 contexts.pass.result.structured = std::move(out_contours);
1065 contexts.pass.result.w = 0;
1066 contexts.pass.result.h = 0;
1073 void reap_ingest_fences(VisionGpuContexts& contexts)
1076 for (
auto* slot : { &contexts.ingest_fence, &contexts.ingest_barrier_fence }) {
1078 foundry.wait_for_fence(*slot);
1079 foundry.release_fence(*slot);
1094 std::shared_ptr<Core::VKImage> op_ingest(
1095 VisionGpuContexts& contexts,
1096 const std::shared_ptr<Core::VKImage>& frame,
1097 uint32_t w, uint32_t
h)
1099 if (!frame || !frame->is_initialized())
1101 if (
static_cast<bool>(frame->get_usage_flags() & vk::ImageUsageFlagBits::eStorage))
1105 auto& ingest = contexts.ingest;
1107 ingest.swap_shader({
1108 .shader_path =
"vision_ingest.comp.spv",
1109 .workgroup_size = k_wg2d,
1110 .push_constant_size =
sizeof(IngestPC),
1112 ingest.stage_image(frame);
1113 ingest.set_push_constants(IngestPC { .width = w, .height =
h });
1114 ingest.prepare_output_image(w,
h);
1115 ingest.set_output_dimensions(w,
h);
1116 contexts.ingest_fence = ingest.dispatch_async({});
1117 ingest.clear_output_dimensions();
1119 auto out = ingest.get_output_image(0);
1122 const auto bcmd = foundry.begin_commands(
1124 foundry.image_barrier(bcmd, out->get_image(),
1125 vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral,
1126 vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead,
1127 vk::PipelineStageFlagBits::eComputeShader,
1128 vk::PipelineStageFlagBits::eComputeShader);
1129 contexts.ingest_barrier_fence = foundry.submit_async(bcmd);
1149 GpuComputeConfig {},
1151 TextureExecutionContext::OutputMode::SCALAR,
1153 std::vector<GpuBufferBinding> {
1154 { .set = 0, .binding = 1, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1155 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1156 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1157 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1158 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1159 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1161 GpuBufferBinding::ElementType::IMAGE_STORAGE,
1167 TextureExecutionContext::OutputMode::IMAGE,
1169 std::vector<GpuBufferBinding> {
1170 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1171 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
1173 GpuBufferBinding::ElementType::IMAGE_STORAGE,
1178 TextureExecutionContext::OutputMode::IMAGE,
1180 std::vector<GpuBufferBinding> {
1181 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1182 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1183 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1184 { .set = 0, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1185 { .set = 0, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1186 { .set = 0, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1187 { .set = 0, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1188 { .set = 0, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1189 { .set = 0, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1190 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1191 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1192 { .set = 1, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1193 { .set = 1, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1194 { .set = 1, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1195 { .set = 1, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1196 { .set = 1, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1197 { .set = 1, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1198 { .set = 1, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1199 { .set = 1, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1200 { .set = 1, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
1201 { .set = 1, .binding = 11, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1202 { .set = 2, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1203 { .set = 2, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
1205 GpuBufferBinding::ElementType::IMAGE_STORAGE,
1211 TextureExecutionContext::OutputMode::IMAGE,
1213 std::vector<GpuBufferBinding> {},
1214 GpuBufferBinding::ElementType::IMAGE_SAMPLED,
1218 structured.set_output_size(1,
sizeof(uint32_t));
1219 structured.set_output_size(2,
static_cast<size_t>(4096) * 4 *
sizeof(
float));
1220 structured.set_output_size(3,
static_cast<size_t>(256) *
sizeof(uint32_t));
1221 structured.set_output_size(4,
sizeof(uint32_t));
1231 case VisionOp::Threshold: {
1236 .
op(KernelOp::CompareGE)
1241 case VisionOp::RgbaToGray: {
1249 .
op(KernelOp::ChannelDot)
1254 case VisionOp::GrayToRgba: {
1258 .
op(KernelOp::ChannelReplicate)
1263 case VisionOp::GaussianBlur: {
1265 .
tmpl(KernelTemplate::Convolve2D)
1276 case VisionOp::NormalizeRange: {
1282 .
op(KernelOp::ScaleOffset)
1287 case VisionOp::NormalizeInplace: {
1291 .
op(KernelOp::Scale)
1296 case VisionOp::Canny: {
1302 .
op(KernelOp::CompareGEPreserve)
1307 case VisionOp::RgbaToHsv:
1308 return { .shader_path =
"rgba_to_hsv.comp.spv", .workgroup_size = k_wg2d };
1309 case VisionOp::Downsample2x:
1310 return { .shader_path =
"downsample_2x.comp.spv", .workgroup_size = k_wg2d };
1311 case VisionOp::FilterSeparable:
1312 return { .shader_path =
"filter_separable.comp.spv", .workgroup_size = k_wg2d };
1313 case VisionOp::Sobel:
1314 return { .shader_path =
"sobel.comp.spv", .workgroup_size = k_wg2d };
1315 case VisionOp::Scharr:
1316 return { .shader_path =
"scharr.comp.spv", .workgroup_size = k_wg2d };
1317 case VisionOp::Erode:
1318 return { .shader_path =
"erode.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(MorphPC) };
1319 case VisionOp::Dilate:
1320 return { .shader_path =
"dilate.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(MorphPC) };
1321 case VisionOp::Open:
1322 return { .shader_path =
"open.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(MorphPC) };
1323 case VisionOp::Close:
1324 return { .shader_path =
"close.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(MorphPC) };
1325 case VisionOp::MorphGradient:
1326 return { .shader_path =
"morph_gradient.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(MorphPC) };
1327 case VisionOp::HarrisResponse:
1328 return { .shader_path =
"harris_response.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(HarrisPC) };
1329 case VisionOp::ExtractPeaks:
1330 return { .shader_path =
"extract_peaks.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ExtractPeaksPC) };
1331 case VisionOp::ConnectedComponents:
1332 return { .shader_path =
"cc_colorize.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(uint32_t) * 2 };
1333 case VisionOp::FindContours:
1334 return { .shader_path =
"contour_segments.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ContourSegmentsPC) };
1335 case VisionOp::ThresholdAdaptive:
1336 return { .shader_path =
"threshold_adaptive.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ThresholdAdaptivePC) };
1337 case VisionOp::ThresholdOtsu:
1338 return { .shader_path =
"threshold_otsu.comp.spv", .workgroup_size = { 256, 1, 1 } };
1344void VisionGpuExecutor::reset()
1349 auto& contexts = *m_contexts;
1351 reap_ingest_fences(contexts);
1353 if (contexts.suspended.is_active()) {
1355 foundry.wait_for_fence(contexts.suspended.fence);
1356 foundry.release_fence(contexts.suspended.fence);
1360 contexts.pass.sequence =
nullptr;
1361 contexts.pass.index = 0;
1363 contexts.pass.current.reset();
1364 contexts.pass.forget();
1365 contexts.pass.completed.clear();
1367 contexts.source.reset();
1368 contexts.bound_staged.reset();
1378 const std::shared_ptr<Core::VKImage>&
image,
1379 uint32_t w, uint32_t
h)
1381 auto& pixel_ctx = contexts.
pixel;
1383 auto& label_ctx = contexts.
labels;
1387 std::unordered_map<size_t, CompletedOp> completed_ops;
1393 "run_gpu: polling a suspension with a different sequence; "
1394 "the walk continues on the sequence the run started from");
1399 pending.
status = VisionStatus::SUSPENDED;
1409 reap_ingest_fences(contexts);
1411 const auto seed = op_ingest(contexts,
image, w,
h);
1417 const auto& step = sequence.steps[contexts.
pass.
index];
1419 const uint32_t w = contexts.
pass.
w;
1420 const uint32_t
h = contexts.
pass.
h;
1422 const auto cfg = config(step.op, step.params);
1426 "run_gpu: no GPU implementation for VisionOp {}",
1427 static_cast<int>(step.op));
1433 pixel_ctx.swap_shader(cfg);
1441 pixel_ctx.prepare_output_image(w,
h);
1445 pixel_ctx.set_output_dimensions(w,
h);
1448 case VisionOp::Downsample2x: {
1449 const uint32_t new_w = std::max(1U, w / 2);
1450 const uint32_t new_h = std::max(1U,
h / 2);
1452 pixel_ctx.prepare_output_image(new_w, new_h);
1453 pixel_ctx.set_output_dimensions(new_w, new_h);
1455 const auto f = pixel_ctx.dispatch_async({});
1456 foundry.wait_for_fence(f);
1457 foundry.release_fence(f);
1459 pixel_ctx.clear_output_dimensions();
1461 auto downsampled = pixel_ctx.get_output_image(0);
1472 case VisionOp::Threshold:
1473 pixel_ctx.set_push_constants(ThresholdPC {
1474 .value = std::get<ThresholdParams>(step.params).value });
1476 case VisionOp::NormalizeRange: {
1477 const auto& p = std::get<NormalizeRangeParams>(step.params);
1478 const float scale = (p.hi > p.lo) ? 1.0F / (p.hi - p.lo) : 1.0F;
1479 const float off = (p.hi > p.lo) ? -p.lo / (p.hi - p.lo) : 0.0F;
1480 pixel_ctx.set_push_constants(NormalizePC { .scale =
scale, .offset = off });
1483 case VisionOp::RgbaToGray:
1484 pixel_ctx.set_push_constants(RgbaToGrayPC {
1485 .wr = 0.299F, .wg = 0.587F, .wb = 0.114F, .wa = 0.0F });
1487 case VisionOp::GaussianBlur: {
1488 const auto& p = std::get<GaussianBlurParams>(step.params);
1489 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
1490 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
1491 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
1492 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
1495 case VisionOp::Erode:
1496 case VisionOp::Dilate:
1497 case VisionOp::MorphGradient:
1498 pixel_ctx.set_push_constants(MorphPC {
1499 .radius = std::get<MorphParams>(step.params).radius });
1501 case VisionOp::ThresholdAdaptive: {
1502 const auto& p = std::get<ThresholdAdaptiveParams>(step.params);
1503 pixel_ctx.set_push_constants(ThresholdAdaptivePC { .block_size = p.block_size, .offset = p.offset });
1506 case VisionOp::ThresholdOtsu: {
1510 case VisionOp::Open:
1511 case VisionOp::Close: {
1515 case VisionOp::Canny: {
1516 auto done = op_canny(contexts, step.params, std::get<CannyParams>(step.params));
1520 case VisionOp::HarrisResponse: {
1524 case VisionOp::ExtractPeaks: {
1525 op_extract_peaks(contexts, std::get<ExtractPeaksParams>(step.params));
1528 case VisionOp::ConnectedComponents: {
1532 op_connected_components(contexts, std::get<Kinesis::Vision::ConnectedComponentsParams>(step.params));
1535 case VisionOp::FindContours: {
1536 if (!op_find_contours(contexts,
1537 std::get<Kinesis::Vision::FindContoursParams>(step.params)))
1546 const auto fence = pixel_ctx.dispatch_async({});
1548 if (step.deferred) {
1549 pixel_ctx.clear_output_dimensions();
1550 contexts.
pass.
current = pixel_ctx.get_output_image(0);
1553 const Kinesis::Vision::GpuVisionPass::Completed done {
1555 .input = dispatch_input
1562 pending.
status = VisionStatus::SUSPENDED;
1567 foundry.wait_for_fence(
fence);
1568 foundry.release_fence(
fence);
1570 pixel_ctx.clear_output_dimensions();
1571 contexts.
pass.
current = pixel_ctx.get_output_image(0);
1581 const std::shared_ptr<Core::VKImage>&
image,
1582 uint32_t w, uint32_t
h)
1585 m_contexts = std::make_unique<VisionGpuContexts>();
1587 return run(*m_contexts, sequence,
image, w,
h);
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
Core::GlobalInputConfig input
uint32_t max_holes_per_label
std::shared_ptr< Core::VKImage > output
uint32_t max_points_per_contour
GPU execution layer for Kinesis::Vision::VisionSequence.
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.
static GpuComputeConfig config(Kinesis::Vision::VisionOp op, const Kinesis::Vision::VisionParams ¶ms)
GpuComputeConfig for a given VisionOp and its parameters.
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
std::vector< double > sum(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Sum per window.
size_t hash_vision_step(VisionOp op, const VisionParams ¶ms)
Hash a VisionStep's op and parameters together.
std::variant< std::monostate, ThresholdParams, ThresholdAdaptiveParams, NormalizeRangeParams, GaussianBlurParams, FilterSeparableParams, CannyParams, MorphParams, HarrisParams, ExtractPeaksParams, TrackKeypointsParams, ConnectedComponentsParams, FindContoursParams > VisionParams
Parameter variant covering all ops that carry parameters.
VisionOp
Named operations available in a VisionSequence.
constexpr ShaderID INVALID_SHADER
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
ImageFormat
User-friendly image format enum.
@ RGBA32F
Four channel 32-bit float.
@ INDIRECT
Indirect draw/dispatch buffer (device-local)
Portal::Graphics::GpuComputeConfig GpuComputeConfig
@ CHAINED
Part of a sequential chain.
@ DEPENDENCY
Part of dependency graph.
GpuComputeConfig config_from_spec(const Portal::Graphics::ShaderSpec &spec)
Derive a GpuComputeConfig from a ShaderSpec.
static constexpr DomainSpec Graphics
Domain constant for Graphics domain.
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
const VisionSequence * sequence
Result of executing a VisionSequence on one frame.
Ordered sequence of VisionSteps describing a complete vision pipeline.
uint32_t set
Descriptor set index.
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.
Portal::Graphics::FenceID fence
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...
TextureExecutionContext cc_pipeline
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.
TextureExecutionContext structured
Buffer-only readback.
Fixed set of TextureExecutionContexts covering every GPU-implemented VisionOp shape.