Execute a VisionSequence on the GPU through an explicit context set.
contexts is caller-supplied rather than the instance's own lazily-built m_contexts. Reused across calls with no reset needed; construction is the caller's responsibility and never happens inside this function.
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
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) {
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
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);
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));
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);
608 continue;
609 }
610 case VisionOp::Confine: {
612 continue;
613 }
614 case VisionOp::Open:
615 case VisionOp::Close: {
617 continue;
618 }
619 case VisionOp::Canny: {
620 auto done =
op_canny(contexts, step.params, std::get<CannyParams>(step.params));
622 continue;
623 }
624 case VisionOp::HarrisResponse: {
626 continue;
627 }
628 case VisionOp::ExtractPeaks: {
630 continue;
631 }
632 case VisionOp::ConnectedComponents: {
633 if (!contexts.pass.current) {
634 continue;
635 }
637 continue;
638 }
639 case VisionOp::FindContours: {
641 std::get<Kinesis::Vision::FindContoursParams>(step.params)))
642 return VisionResult {};
643 continue;
644 }
645 case VisionOp::TrackKeypoints: {
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: {
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 };
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();
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
746 }
747
748 return std::move(contexts.pass.result);
749}
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > & gaussian_kernel_2d(uint32_t radius, float sigma)
2D Gaussian kernel for convolution, cached by (radius, sigma bit pattern).
Kinesis::Vision::GpuVisionPass::Completed op_canny(VisionGpuContexts &contexts, const Kinesis::Vision::VisionParams ¶ms, const Kinesis::Vision::CannyParams &p)
Detect edges in the current image with the Canny pipeline.
static void op_snapshot(VisionGpuContexts &contexts)
Copy the working image into this snapshot's own image and append it to VisionResult::snapshots.
bool op_dense_flow(VisionGpuContexts &contexts, const Kinesis::Vision::VisionStep &step)
Dense optical flow from the previous frame to the current one.
static GpuComputeConfig config(Kinesis::Vision::VisionOp op, const Kinesis::Vision::VisionParams ¶ms)
GpuComputeConfig for a given VisionOp and its parameters.
bool op_track_keypoints(VisionGpuContexts &contexts, const Kinesis::Vision::VisionStep &step)
Track the previous frame's keypoints into the current frame.
Kinesis::Vision::GpuVisionPass::Completed op_harris_response(VisionGpuContexts &contexts, const Kinesis::Vision::HarrisParams &p)
Compute the Harris response of the current image.
void op_connected_components(VisionGpuContexts &contexts, const Kinesis::Vision::ConnectedComponentsParams &p)
Label components in the current image on the GPU.
Kinesis::Vision::GpuVisionPass::Completed op_open_close(VisionGpuContexts &contexts, Kinesis::Vision::VisionOp op, const Kinesis::Vision::MorphParams &p)
Apply morphological opening or closing to the current image.
static void after_step(VisionGpuContexts &contexts, size_t index)
Run any work a finished step owes the flow context.
Kinesis::Vision::GpuVisionPass::Completed op_threshold_otsu(VisionGpuContexts &contexts, const Kinesis::Vision::OtsuParams &p)
Apply an Otsu threshold to the current image.
bool op_find_contours(VisionGpuContexts &contexts, const Kinesis::Vision::FindContoursParams &p)
Trace contours from the immediately preceding component labels.
Kinesis::Vision::GpuVisionPass::Completed op_confine(VisionGpuContexts &contexts, const Kinesis::Vision::ConfineParams &p)
Arbitrary rectangular sub-region of the current image, optionally resized back to the frame's own siz...
void op_extract_peaks(VisionGpuContexts &contexts, const Kinesis::Vision::ExtractPeaksParams &p)
Extract and rank keypoints from the current response image.
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
size_t hash_vision_step(VisionOp op, const VisionParams ¶ms)
Hash a VisionStep's op and parameters together.
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
std::shared_ptr< Vruta::Routine > sequence(std::vector< std::pair< double, std::function< void()> > > sequence, Vruta::ProcessingToken token)
Creates a temporal sequence that executes callbacks at specified time offsets.
constexpr ShaderID INVALID_SHADER
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.