252 TextureExecutionContext::OutputMode::SCALAR,
254 std::vector<GpuBufferBinding> {
255 { .set = 0, .binding = 1, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
256 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
257 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
258 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
259 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
260 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
262 GpuBufferBinding::ElementType::IMAGE_STORAGE,
268 TextureExecutionContext::OutputMode::IMAGE,
270 std::vector<GpuBufferBinding> {
271 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
272 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::OUTPUT, .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE },
274 GpuBufferBinding::ElementType::IMAGE_STORAGE,
279 TextureExecutionContext::OutputMode::IMAGE,
281 std::vector<GpuBufferBinding> {
282 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
283 { .set = 0, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
284 { .set = 0, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
285 { .set = 0, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
286 { .set = 0, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
287 { .set = 0, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
288 { .set = 0, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
289 { .set = 0, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
290 { .set = 0, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
291 { .set = 1, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
292 { .set = 1, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
293 { .set = 1, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
294 { .set = 1, .binding = 3, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
295 { .set = 1, .binding = 4, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
296 { .set = 1, .binding = 5, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
297 { .set = 1, .binding = 6, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
298 { .set = 1, .binding = 7, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
299 { .set = 1, .binding = 8, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
300 { .set = 1, .binding = 9, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
301 { .set = 1, .binding = 10, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 },
302 { .set = 1, .binding = 11, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
303 { .set = 2, .binding = 0, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
304 { .set = 2, .binding = 1, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::FLOAT32 },
306 GpuBufferBinding::ElementType::IMAGE_STORAGE,
310 structured.set_output_size(1,
sizeof(uint32_t));
311 structured.set_output_size(2,
static_cast<size_t>(4096) * 4 *
sizeof(
float));
312 structured.set_output_size(3,
static_cast<size_t>(256) *
sizeof(uint32_t));
313 structured.set_output_size(4,
sizeof(uint32_t));
443 const std::shared_ptr<Core::VKImage>&
image,
444 uint32_t w, uint32_t
h)
446 auto& pixel_ctx = contexts.
pixel;
448 auto& label_ctx = contexts.
labels;
459 std::string last_shader_path;
460 std::shared_ptr<Core::VKImage> last_staged;
461 uint32_t last_w = 0, last_h = 0;
462 std::unordered_map<size_t, CompletedOp> completed_ops;
464 pixel_ctx.prepare_output_image(w,
h);
468 for (
const auto& step : sequence.steps) {
469 const auto cfg = config(step.op, step.params);
473 "run_gpu: no GPU implementation for VisionOp {}",
474 static_cast<int>(step.op));
478 if (cfg.shader_id != last_shader_id || cfg.shader_path != last_shader_path) {
479 pixel_ctx.swap_shader(cfg);
480 last_shader_id = cfg.shader_id;
481 last_shader_path = cfg.shader_path;
484 pixel_ctx.stage_image(
current);
487 if (w != last_w ||
h != last_h) {
488 pixel_ctx.prepare_output_image(w,
h);
492 pixel_ctx.set_output_dimensions(w,
h);
495 case VisionOp::Downsample2x: {
496 const uint32_t new_w = std::max(1U, w / 2);
497 const uint32_t new_h = std::max(1U,
h / 2);
499 pixel_ctx.prepare_output_image(new_w, new_h);
500 pixel_ctx.set_output_dimensions(new_w, new_h);
502 const auto f = pixel_ctx.dispatch_async({});
503 foundry.wait_for_fence(f);
504 foundry.release_fence(f);
506 pixel_ctx.clear_output_dimensions();
508 auto downsampled = pixel_ctx.get_output_image(0);
522 case VisionOp::Threshold:
523 pixel_ctx.set_push_constants(ThresholdPC {
524 .value = std::get<ThresholdParams>(step.params).value });
526 case VisionOp::NormalizeRange: {
527 const auto& p = std::get<NormalizeRangeParams>(step.params);
528 const float scale = (p.hi > p.lo) ? 1.0F / (p.hi - p.lo) : 1.0F;
529 const float off = (p.hi > p.lo) ? -p.lo / (p.hi - p.lo) : 0.0F;
530 pixel_ctx.set_push_constants(NormalizePC { .scale =
scale, .offset = off });
533 case VisionOp::RgbaToGray:
534 pixel_ctx.set_push_constants(RgbaToGrayPC {
535 .wr = 0.299F, .wg = 0.587F, .wb = 0.114F, .wa = 0.0F });
537 case VisionOp::GaussianBlur: {
538 const auto& p = std::get<GaussianBlurParams>(step.params);
539 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
540 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
541 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
542 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
545 case VisionOp::Erode:
546 case VisionOp::Dilate:
547 case VisionOp::MorphGradient:
548 pixel_ctx.set_push_constants(MorphPC {
549 .radius = std::get<MorphParams>(step.params).radius });
551 case VisionOp::ThresholdAdaptive: {
552 const auto& p = std::get<ThresholdAdaptiveParams>(step.params);
553 pixel_ctx.set_push_constants(ThresholdAdaptivePC { .block_size = p.block_size, .offset = p.offset });
556 case VisionOp::ThresholdOtsu: {
557 structured_ctx.swap_shader({
558 .shader_path =
"otsu_histogram.comp.spv",
559 .workgroup_size = k_wg2d,
560 .push_constant_size =
sizeof(OtsuHistPC),
562 std::vector<uint32_t> zeros(256, 0);
563 structured_ctx.set_binding_data(3, std::span<const uint32_t>(zeros));
564 structured_ctx.stage_image(
current);
565 structured_ctx.set_push_constants(OtsuHistPC { .width = w, .height =
h });
566 structured_ctx.set_output_dimensions(w,
h);
568 const auto f = structured_ctx.dispatch_async({});
569 structured_ctx.clear_output_dimensions();
570 foundry.wait_for_fence(f);
571 foundry.release_fence(f);
574 const auto hist_check = structured_ctx.collect_result();
575 std::vector<uint32_t> hist_readback(256, 0);
576 if (
auto it = hist_check.aux.find(3); it != hist_check.aux.end())
577 std::memcpy(hist_readback.data(), it->second.data(), 256 *
sizeof(uint32_t));
579 structured_ctx.swap_shader({
580 .shader_path =
"otsu_select.comp.spv",
581 .workgroup_size = { 256, 1, 1 },
583 structured_ctx.set_binding_data(3, std::span<const uint32_t>(hist_readback));
584 structured_ctx.set_output_dimensions(256, 1);
587 const auto f = structured_ctx.dispatch_async({});
588 structured_ctx.clear_output_dimensions();
589 foundry.wait_for_fence(f);
590 foundry.release_fence(f);
593 const auto sel_result = structured_ctx.collect_result();
594 uint32_t best_bin = 0;
595 if (
auto it = sel_result.aux.find(4); it != sel_result.aux.end())
596 std::memcpy(&best_bin, it->second.data(),
sizeof(uint32_t));
597 const float t_norm =
static_cast<float>(best_bin) / 255.0F;
602 .storage_image(
"src", BindingDirection::Input)
604 .op(KernelOp::CompareGE)
605 .workgroup(k_wg2d[0], k_wg2d[1])
607 pixel_ctx.swap_shader(apply_cfg);
608 pixel_ctx.stage_image(
current);
609 pixel_ctx.set_push_constants(ThresholdPC { .value = t_norm });
610 pixel_ctx.prepare_output_image(w,
h);
612 const auto f = pixel_ctx.dispatch_async({});
613 foundry.wait_for_fence(f);
614 foundry.release_fence(f);
616 auto thresholded = pixel_ctx.get_output_image(0);
626 case VisionOp::Close: {
627 const auto radius = std::get<MorphParams>(step.params).radius;
628 const bool is_open = (step.op == VisionOp::Open);
631 .
shader_path = is_open ?
"erode.comp.spv" :
"dilate.comp.spv",
632 .workgroup_size = k_wg2d,
633 .push_constant_size =
sizeof(MorphPC),
635 pixel_ctx.swap_shader(first_cfg);
636 pixel_ctx.stage_image(
current);
637 pixel_ctx.set_push_constants(MorphPC { .radius =
radius });
638 pixel_ctx.prepare_output_image(w,
h);
639 pixel_ctx.set_output_dimensions(w,
h);
641 const auto f = pixel_ctx.dispatch_async({});
642 foundry.wait_for_fence(f);
643 foundry.release_fence(f);
645 auto intermediate = pixel_ctx.get_output_image(0);
648 .
shader_path = is_open ?
"dilate.comp.spv" :
"erode.comp.spv",
649 .workgroup_size = k_wg2d,
650 .push_constant_size =
sizeof(MorphPC),
652 pixel_ctx.swap_shader(second_cfg);
653 pixel_ctx.stage_image(intermediate);
654 pixel_ctx.set_push_constants(MorphPC { .radius =
radius });
655 pixel_ctx.prepare_output_image(w,
h);
656 pixel_ctx.set_output_dimensions(w,
h);
658 const auto f = pixel_ctx.dispatch_async({});
659 foundry.wait_for_fence(f);
660 foundry.release_fence(f);
663 auto opened_closed = pixel_ctx.get_output_image(0);
670 case VisionOp::Canny: {
671 const auto& p = std::get<CannyParams>(step.params);
676 std::shared_ptr<Core::VKImage> blurred;
677 if (
auto it = completed_ops.find(blur_key);
678 it != completed_ops.end() && it->second.input == canny_input) {
679 blurred = it->second.output;
681 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
682 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
684 pixel_ctx.swap_shader(blur_cfg);
685 pixel_ctx.stage_image(canny_input);
686 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
687 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
688 pixel_ctx.prepare_output_image(w,
h);
690 const auto f = pixel_ctx.dispatch_async({});
691 foundry.wait_for_fence(f);
692 foundry.release_fence(f);
694 blurred = pixel_ctx.get_output_image(0);
695 completed_ops[blur_key] = { .output = blurred, .input = canny_input };
699 std::shared_ptr<Core::VKImage> grad;
700 if (
auto it = completed_ops.find(sobel_key);
701 it != completed_ops.end() && it->second.input == blurred) {
702 grad = it->second.output;
704 const auto sobel_cfg = config(VisionOp::Sobel, std::monostate {});
705 pixel_ctx.swap_shader(sobel_cfg);
706 pixel_ctx.stage_image(blurred);
707 pixel_ctx.prepare_output_image(w,
h);
709 const auto f = pixel_ctx.dispatch_async({});
710 foundry.wait_for_fence(f);
711 foundry.release_fence(f);
713 grad = pixel_ctx.get_output_image(0);
714 completed_ops[sobel_key] = { .output = grad, .input = blurred };
719 .workgroup_size = k_wg2d,
721 pixel_ctx.swap_shader(nms_cfg);
722 pixel_ctx.stage_image(grad);
723 pixel_ctx.prepare_output_image(w,
h);
725 const auto f = pixel_ctx.dispatch_async({});
726 foundry.wait_for_fence(f);
727 foundry.release_fence(f);
729 auto suppressed = pixel_ctx.get_output_image(0);
731 const auto classify_cfg = config(VisionOp::Canny, step.params);
732 pixel_ctx.swap_shader(classify_cfg);
733 pixel_ctx.stage_image(suppressed);
734 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.low_threshold, .value = 0.5F });
735 pixel_ctx.prepare_output_image(w,
h);
737 const auto f = pixel_ctx.dispatch_async({});
738 foundry.wait_for_fence(f);
739 foundry.release_fence(f);
741 auto classified_weak = pixel_ctx.get_output_image(0);
743 pixel_ctx.stage_image(classified_weak);
744 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.high_threshold, .value = 1.0F });
745 pixel_ctx.prepare_output_image(w,
h);
747 const auto f = pixel_ctx.dispatch_async({});
748 foundry.wait_for_fence(f);
749 foundry.release_fence(f);
751 auto classified = pixel_ctx.get_output_image(0);
753 constexpr uint32_t k_max_hysteresis_rounds = 64;
754 label_ctx.set_output_size(2,
sizeof(uint32_t));
755 label_ctx.set_output_dimensions(w,
h);
756 label_ctx.swap_shader({
757 .shader_path =
"canny_hysteresis.comp.spv",
758 .workgroup_size = k_wg2d,
759 .push_constant_size =
sizeof(HysteresisPC),
761 label_ctx.stage_image_at(0, classified, GpuBufferBinding::ElementType::IMAGE_STORAGE);
762 label_ctx.slot_binding(0).direction = GpuBufferBinding::Direction::INPUT_OUTPUT;
765 label_ctx.set_binding_data(2, std::span<const uint32_t>(&zero, 1));
766 const HysteresisPC hpc { .width = w, .height =
h };
768 chained_ctx.
mode = ExecutionMode::CHAINED;
771 .pc_updater = [hpc](uint32_t,
void* dst) { std::memcpy(dst, &hpc,
sizeof(HysteresisPC)); },
772 .passes_per_batch = k_max_hysteresis_rounds,
774 label_ctx.execute(
Datum<> {}, chained_ctx);
776 label_ctx.slot_binding(0).direction = GpuBufferBinding::Direction::OUTPUT;
777 auto hysteresis_result = classified;
782 .storage_image(
"src", BindingDirection::Input)
784 .op(KernelOp::CompareGE)
785 .workgroup(k_wg2d[0], k_wg2d[1])
787 pixel_ctx.swap_shader(finalize_cfg);
788 pixel_ctx.stage_image(hysteresis_result);
789 pixel_ctx.set_push_constants(FinalizePC { .threshold = 1.0F });
790 pixel_ctx.prepare_output_image(w,
h);
792 const auto f = pixel_ctx.dispatch_async({});
793 foundry.wait_for_fence(f);
794 foundry.release_fence(f);
796 auto finalized = pixel_ctx.get_output_image(0);
806 case VisionOp::HarrisResponse: {
807 const auto& p = std::get<HarrisParams>(step.params);
808 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
809 const auto& weights = gaussian_kernel_1d(
radius, p.sigma);
812 pixel_ctx.swap_shader({ .shader_path =
"harris_grad_pack.comp.spv", .workgroup_size = k_wg2d });
813 pixel_ctx.stage_image(
current);
814 pixel_ctx.prepare_output_image(w,
h);
816 const auto f = pixel_ctx.dispatch_async({});
817 foundry.wait_for_fence(f);
818 foundry.release_fence(f);
820 auto packed = pixel_ctx.get_output_image(0);
823 pixel_ctx.swap_shader(blur_cfg);
824 pixel_ctx.stage_image(packed);
825 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
826 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
827 pixel_ctx.prepare_output_image(w,
h);
829 const auto f = pixel_ctx.dispatch_async({});
830 foundry.wait_for_fence(f);
831 foundry.release_fence(f);
833 auto smoothed = pixel_ctx.get_output_image(0);
837 .workgroup_size = k_wg2d,
838 .push_constant_size =
sizeof(HarrisPC),
840 pixel_ctx.swap_shader(harris_resp_cfg);
841 pixel_ctx.stage_image(smoothed);
842 pixel_ctx.set_push_constants(HarrisPC { .k = p.k, .pass = 0U, .width = w, .height =
h });
843 pixel_ctx.prepare_output_image(w,
h);
845 const auto f = pixel_ctx.dispatch_async({});
846 foundry.wait_for_fence(f);
847 foundry.release_fence(f);
850 pixel_ctx.set_push_constants(HarrisPC { .k = p.k, .pass = 1U, .width = w, .height =
h });
852 const auto f = pixel_ctx.dispatch_async({});
853 foundry.wait_for_fence(f);
854 foundry.release_fence(f);
856 current = pixel_ctx.get_output_image(0);
862 case VisionOp::ExtractPeaks: {
863 const auto& p = std::get<ExtractPeaksParams>(step.params);
864 constexpr uint32_t k_max_kp = 4096;
866 structured_ctx.swap_shader({
867 .shader_path =
"extract_peaks.comp.spv",
868 .workgroup_size = { 8, 8, 1 },
869 .push_constant_size =
sizeof(ExtractPeaksPC),
872 structured_ctx.set_output_size(1,
sizeof(uint32_t));
873 structured_ctx.set_output_size(2,
static_cast<size_t>(k_max_kp) * 4 *
sizeof(float));
875 structured_ctx.stage_image(
current);
876 structured_ctx.set_push_constants(ExtractPeaksPC {
877 .threshold = p.threshold,
878 .nms_radius = p.nms_radius,
881 .max_keypoints = k_max_kp,
884 structured_ctx.set_output_dimensions(w,
h);
885 const auto fence = structured_ctx.dispatch_async({});
886 structured_ctx.clear_output_dimensions();
887 foundry.wait_for_fence(
fence);
888 foundry.release_fence(
fence);
890 if (sequence.track_follows_peaks) {
897 const auto gpu_result = structured_ctx.collect_result();
900 if (
auto it = gpu_result.aux.find(1); it != gpu_result.aux.end())
901 std::memcpy(&
count, it->second.data(),
sizeof(uint32_t));
905 float x, y, response, pad;
907 std::vector<GpuKp> raw(
count);
909 if (
auto it = gpu_result.aux.find(2); it != gpu_result.aux.end())
910 std::memcpy(raw.data(), it->second.data(),
count *
sizeof(GpuKp));
913 std::vector<Kinesis::Vision::Keypoint> kpts;
915 for (
const auto& kp : raw) {
916 kpts.push_back({ .position = { kp.x, kp.y },
917 .response = kp.response,
921 std::ranges::sort(kpts, [](
const auto&
a,
const auto&
b) {
return a.response >
b.response; });
928 case VisionOp::ConnectedComponents: {
929 const auto& p = std::get<Kinesis::Vision::ConnectedComponentsParams>(step.params);
935 const auto seed_input =
current;
939 const double block_diagonal = std::sqrt(
941 const auto k_compress_passes =
static_cast<uint32_t
>(std::ceil(std::log2(std::max(2.0, block_diagonal))));
943 cc_pipeline.ensure_shared_buffer(0, 2,
static_cast<size_t>(
block_width) *
block_height, GpuBufferBinding::ElementType::UINT32);
944 cc_pipeline.ensure_shared_buffer(0, 3, 1, GpuBufferBinding::ElementType::UINT32);
945 cc_pipeline.ensure_shared_buffer(0, 4,
static_cast<size_t>(
block_width) *
block_height, GpuBufferBinding::ElementType::UINT32);
946 cc_pipeline.ensure_shared_buffer(0, 5, 1, GpuBufferBinding::ElementType::UINT32);
947 cc_pipeline.ensure_shared_buffer(0, 6,
static_cast<size_t>(w) *
h, GpuBufferBinding::ElementType::UINT32);
948 cc_pipeline.ensure_shared_buffer(0, 7,
static_cast<size_t>(k_max_components) * 2, GpuBufferBinding::ElementType::UINT32);
949 cc_pipeline.ensure_shared_buffer(0, 8,
static_cast<size_t>(k_max_components) * 2, GpuBufferBinding::ElementType::UINT32);
950 cc_pipeline.ensure_shared_buffer(0, 9, k_max_components, GpuBufferBinding::ElementType::UINT32);
954 const CCFinalLabelPC final_pc { .width = w, .height =
h, .block_width =
block_width, .block_height =
block_height, .max_components = k_max_components, .export_labels = 1U };
956 cc_pipeline.swap_shader({ .shader_path =
"cc_reset.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(CCResetPC) });
957 cc_pipeline.set_push_constants(CCResetPC {
959 .max_components = k_max_components,
963 const auto reset_fence = cc_pipeline.dispatch_async({});
964 foundry.wait_for_fence(reset_fence);
965 foundry.release_fence(reset_fence);
967 cc_pipeline.clear_output_dimensions();
969 const std::array<uint32_t, 3> block_groups {
975 std::vector<DependencyStage> cc_stages;
977 cc_stages.push_back({
978 .config = { .shader_path =
"cc_block_init.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCBlockInitPC) },
980 cc_pipeline.stage_image_at(1, seed_input, GpuBufferBinding::ElementType::IMAGE_STORAGE);
981 ctx.set_push_constants(init_pc);
983 .hazard_fn = [&](
GpuDispatchCore& ctx) -> std::vector<Portal::Graphics::HazardResource> {
985 ctx.shared_buffer_hazard(
986 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 }),
989 .explicit_groups = block_groups,
992 cc_stages.push_back({
993 .config = { .shader_path =
"cc_merge.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCMergePC) },
995 cc_pipeline.stage_image_at(1, seed_input, GpuBufferBinding::ElementType::IMAGE_STORAGE);
996 ctx.set_push_constants(merge_pc);
998 .hazard_fn = [&](
GpuDispatchCore& ctx) -> std::vector<Portal::Graphics::HazardResource> {
1000 ctx.shared_buffer_hazard(
1001 { .set = 0, .binding = 2, .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 }),
1004 .explicit_groups = block_groups,
1008 cc_ctx.
mode = ExecutionMode::DEPENDENCY;
1010 params.
stages = cc_stages;
1012 cc_pipeline.execute(
Datum<> {}, cc_ctx);
1014 cc_pipeline.ensure_shared_buffer(0, 10, 3, GpuBufferBinding::ElementType::UINT32,
1016 const std::array<uint32_t, 3> full_grid_indirect {
1021 cc_pipeline.upload_shared_raw(0, 10,
reinterpret_cast<const uint8_t*
>(full_grid_indirect.data()), full_grid_indirect.size() *
sizeof(uint32_t));
1023 cc_pipeline.swap_shader({ .shader_path =
"cc_compress.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCCompressPC) });
1027 const auto fence = cc_pipeline.dispatch_async({});
1028 foundry.wait_for_fence(
fence);
1029 foundry.release_fence(
fence);
1031 cc_pipeline.clear_output_dimensions();
1033 cc_pipeline.swap_shader({ .shader_path =
"cc_final_label.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(CCFinalLabelPC) });
1034 cc_pipeline.stage_image_at(1, seed_input, GpuBufferBinding::ElementType::IMAGE_STORAGE);
1035 cc_pipeline.set_push_constants(final_pc);
1036 cc_pipeline.set_output_dimensions(w,
h);
1037 cc_pipeline.prepare_output_image(w,
h);
1039 const auto fence = cc_pipeline.dispatch_async({});
1040 foundry.wait_for_fence(
fence);
1041 foundry.release_fence(
fence);
1043 cc_pipeline.clear_output_dimensions();
1045 result.
debug_labels = p.with_colors ? cc_pipeline.get_output_image(0) :
nullptr;
1047 uint32_t compact_count = 0;
1048 cc_pipeline.download_shared(0, 5, &compact_count,
sizeof(uint32_t));
1049 compact_count = std::min(compact_count, k_max_components);
1052 cc_result.
count = compact_count;
1053 cc_result.
boxes.reserve(compact_count);
1055 if (compact_count > 0) {
1056 std::vector<glm::uvec2> bmin(compact_count);
1057 std::vector<glm::uvec2> bmax(compact_count);
1058 std::vector<uint32_t> bcount(compact_count);
1059 cc_pipeline.download_shared(0, 7, bmin.data(), bmin.size() *
sizeof(glm::uvec2));
1060 cc_pipeline.download_shared(0, 8, bmax.data(), bmax.size() *
sizeof(glm::uvec2));
1061 cc_pipeline.download_shared(0, 9, bcount.data(), bcount.size() *
sizeof(uint32_t));
1063 const float inv_w = 1.0F /
static_cast<float>(w);
1064 const float inv_h = 1.0F /
static_cast<float>(
h);
1066 for (uint32_t i = 0; i < compact_count; ++i) {
1069 const float x =
static_cast<float>(bmin[i].x) * inv_w;
1070 const float y =
static_cast<float>(bmin[i].y) * inv_h;
1071 const float bw =
static_cast<float>(bmax[i].x - bmin[i].x + 1) * inv_w;
1072 const float bh =
static_cast<float>(bmax[i].y - bmin[i].y + 1) * inv_h;
1073 cc_result.
boxes.push_back({ .x = x, .y = y, .w = bw, .h = bh, .confidence = 1.0F, .label_id = i + 1 });
1082 case VisionOp::FindContours: {
1083 if (!sequence.contours_follow_cc) {
1085 "run_gpu: FindContours requires ConnectedComponents(export_labels=true) as the immediately preceding step");
1089 const auto& p = std::get<Kinesis::Vision::FindContoursParams>(step.params);
1091 cc_pipeline.ensure_shared_buffer(1, 4,
static_cast<size_t>(k_max_components) + 1U, GpuBufferBinding::ElementType::UINT32);
1092 cc_pipeline.ensure_shared_buffer(1, 5,
static_cast<size_t>(k_max_components) * k_max_holes_per_label, GpuBufferBinding::ElementType::UINT32);
1093 cc_pipeline.ensure_shared_buffer(1, 6,
static_cast<size_t>(k_max_trace_slots) * 2U, GpuBufferBinding::ElementType::UINT32);
1094 cc_pipeline.ensure_shared_buffer(1, 7, 1U, GpuBufferBinding::ElementType::UINT32);
1095 cc_pipeline.ensure_shared_buffer(1, 8,
static_cast<size_t>(k_max_trace_slots) * k_max_points_per_contour * 2U, GpuBufferBinding::ElementType::FLOAT32);
1096 cc_pipeline.ensure_shared_buffer(1, 9, 1U, GpuBufferBinding::ElementType::UINT32);
1097 cc_pipeline.ensure_shared_buffer(1, 10,
static_cast<size_t>(k_max_trace_slots) * 4U, GpuBufferBinding::ElementType::UINT32);
1098 cc_pipeline.ensure_shared_buffer(1, 11,
static_cast<size_t>(k_max_trace_slots) * 2U, GpuBufferBinding::ElementType::FLOAT32);
1099 cc_pipeline.ensure_shared_buffer(2, 0, k_max_components, GpuBufferBinding::ElementType::FLOAT32);
1100 cc_pipeline.ensure_shared_buffer(2, 1, k_max_components, GpuBufferBinding::ElementType::FLOAT32);
1103 std::vector<uint32_t> owner_reset(
static_cast<size_t>(k_max_components) + 1U, CC_UNCLAIMED_HOST);
1104 cc_pipeline.upload_shared_raw(1, 4,
reinterpret_cast<const uint8_t*
>(owner_reset.data()), owner_reset.size() *
sizeof(uint32_t));
1106 std::vector<uint32_t> hole_owner_reset(
static_cast<size_t>(k_max_components) * k_max_holes_per_label, CC_UNCLAIMED_HOST);
1107 cc_pipeline.upload_shared_raw(1, 5,
reinterpret_cast<const uint8_t*
>(hole_owner_reset.data()), hole_owner_reset.size() *
sizeof(uint32_t));
1109 const uint32_t zero = 0;
1110 cc_pipeline.upload_shared_raw(1, 7,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
1111 cc_pipeline.upload_shared_raw(1, 9,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
1114 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;
1115 cc_pipeline.swap_shader({ .shader_path =
"contour_march.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ContourMarchPC) });
1116 cc_pipeline.stage_image_at(1,
image, GpuBufferBinding::ElementType::IMAGE_SAMPLED);
1117 cc_pipeline.prepare_output_image(w,
h);
1118 cc_pipeline.set_push_constants(ContourMarchPC {
1121 .max_components = k_max_components,
1122 .max_points_per_contour = max_points,
1123 .max_holes_per_label = k_max_holes_per_label,
1125 .min_area = p.min_area,
1126 .compacted_count = 0U });
1128 cc_pipeline.set_output_dimensions(w,
h);
1130 const auto fence = cc_pipeline.dispatch_async({});
1131 foundry.wait_for_fence(
fence);
1132 foundry.release_fence(
fence);
1135 cc_pipeline.set_push_constants(ContourMarchPC {
1138 .max_components = k_max_components,
1139 .max_points_per_contour = max_points,
1140 .max_holes_per_label = k_max_holes_per_label,
1142 .min_area = p.min_area,
1143 .compacted_count = 0U });
1145 const auto fence = cc_pipeline.dispatch_async({});
1146 foundry.wait_for_fence(
fence);
1147 foundry.release_fence(
fence);
1150 cc_pipeline.clear_output_dimensions();
1152 cc_pipeline.swap_shader({ .shader_path =
"contour_compact.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourCompactPC) });
1153 cc_pipeline.set_push_constants(ContourCompactPC { .max_components = k_max_components, .max_holes_per_label = k_max_holes_per_label });
1154 const uint32_t total_owner_slots = k_max_components * (1U + k_max_holes_per_label);
1155 cc_pipeline.set_output_dimensions(total_owner_slots, 1);
1157 const auto fence = cc_pipeline.dispatch_async({});
1158 foundry.wait_for_fence(
fence);
1159 foundry.release_fence(
fence);
1161 cc_pipeline.clear_output_dimensions();
1164 cc_pipeline.download_shared(1, 7, &
compacted_count,
sizeof(uint32_t));
1166 cc_pipeline.swap_shader({ .shader_path =
"contour_march.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourMarchPC) });
1167 cc_pipeline.set_push_constants(ContourMarchPC {
1170 .max_components = k_max_components,
1171 .max_points_per_contour = max_points,
1172 .max_holes_per_label = k_max_holes_per_label,
1174 .min_area = p.min_area,
1178 const auto fence = cc_pipeline.dispatch_async({});
1179 foundry.wait_for_fence(
fence);
1180 foundry.release_fence(
fence);
1182 cc_pipeline.clear_output_dimensions();
1184 if (p.max_contours > 0U) {
1185 constexpr uint32_t
k = 12U;
1186 constexpr uint32_t total_passes =
k * (
k + 1U) / 2U;
1190 .
tmpl(KernelTemplate::BitonicSort)
1201 cc_pipeline.set_output_dimensions(k_max_components, 1U);
1204 bitonic_ctx.
mode = ExecutionMode::CHAINED;
1207 .pc_updater = [
k](uint32_t p_idx,
void* pc_ptr) {
1208 uint32_t stage = 0,
pass = 0, remaining = p_idx;
1209 for (uint32_t s = 0; s <
k; ++s) {
1210 if (remaining <= s) {
1215 remaining -= (s + 1);
1218 uint32_t stage,
pass,
count, descending;
1220 *
static_cast<PC*
>(pc_ptr) = { .stage = stage, .pass =
pass, .count = k_max_components, .descending = 1U };
1224 cc_pipeline.execute(
Datum<std::vector<Kakshya::DataVariant>> {}, bitonic_ctx);
1225 cc_pipeline.clear_output_dimensions();
1229 cc_pipeline.swap_shader({ .shader_path =
"contour_render_clear.comp.spv", .workgroup_size = k_wg2d, .push_constant_size =
sizeof(ContourClearPC) });
1230 cc_pipeline.prepare_output_image(w,
h);
1231 cc_pipeline.set_push_constants(ContourClearPC { .width = w, .height =
h });
1232 cc_pipeline.set_output_dimensions(w,
h);
1234 const auto fence = cc_pipeline.dispatch_async({});
1235 foundry.wait_for_fence(
fence);
1236 foundry.release_fence(
fence);
1238 cc_pipeline.clear_output_dimensions();
1240 cc_pipeline.swap_shader({ .shader_path =
"contour_render.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourRenderPC) });
1241 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 });
1242 const uint32_t render_slots = p.max_contours > 0U ? std::min(p.max_contours, k_max_components) : k_max_trace_slots;
1243 cc_pipeline.set_output_dimensions(render_slots * k_max_points_per_contour, 1U);
1245 const auto fence = cc_pipeline.dispatch_async({});
1246 foundry.wait_for_fence(
fence);
1247 foundry.release_fence(
fence);
1249 cc_pipeline.clear_output_dimensions();
1259 cc_pipeline.download_shared(1, 10, meta.data(),
compacted_count *
sizeof(glm::uvec4));
1261 cc_pipeline.download_shared(1, 11, area_perim.data(),
compacted_count *
sizeof(glm::vec2));
1262 uint32_t points_written = 0;
1263 cc_pipeline.download_shared(1, 9, &points_written,
sizeof(uint32_t));
1264 std::vector<glm::vec2> flat_points_full(points_written);
1266 if (points_written > 0)
1267 cc_pipeline.download_shared(1, 8, flat_points_full.data(),
static_cast<size_t>(points_written) *
sizeof(glm::vec2));
1269 std::vector<uint32_t> order;
1270 if (p.max_contours > 0U) {
1272 std::vector<float> sorted_indices(take);
1273 cc_pipeline.download_shared(2, 1, sorted_indices.data(), take *
sizeof(
float));
1274 order.reserve(take);
1275 for (
float f : sorted_indices)
1276 order.push_back(
static_cast<uint32_t
>(f));
1283 std::vector<Kinesis::Vision::Contour> out_contours;
1284 out_contours.reserve(order.size());
1286 for (uint32_t idx : order) {
1289 const auto& m = meta[idx];
1293 std::vector<glm::vec2> pts(
1294 flat_points_full.begin() + m.x,
1295 flat_points_full.begin() + m.x + m.y);
1296 const glm::vec2 ap = area_perim[idx];
1297 out_contours.push_back({ .points = std::move(pts), .area = ap.x, .perimeter = ap.y, .parent_label = m.z });
1309 auto dispatch_input =
current;
1310 const auto fence = pixel_ctx.dispatch_async({});
1311 foundry.wait_for_fence(
fence);
1312 foundry.release_fence(
fence);
1314 pixel_ctx.clear_output_dimensions();
1315 current = pixel_ctx.get_output_image(0);