Dense optical flow from the previous frame to the current one.
Needs an earlier RgbaToGray step, whose gray frame the pyramid hook has already turned into the current atlas. The whole solve is one dependency sequence: for each level from coarsest to finest, the previous frame's structure tensor and then a number of warp, box sum and solve iterations. The flow of a finer level starts from the coarser one upsampled and doubled, seeded inside the first warp of the level.
The first frame after a reset has no previous frame, so it only ends the frame and produces no flow.
504{
505 auto& state = contexts.flow_state;
506 auto& flow = contexts.flow;
508
509 if (!state.curr_ready || !state.atlas[0]) {
511 "run_gpu: OpticalFlowDense requires an earlier RgbaToGray step to supply the gray frame");
512 return false;
513 }
514
515 const auto& p = std::get<OpticalFlowDenseParams>(step.params);
516 const auto& layout = state.layout;
517 const uint32_t w = contexts.pass.w;
518 const uint32_t
h = contexts.pass.h;
519
520 if (w != layout.level[0].w ||
h != layout.level[0].h) {
522 "run_gpu: OpticalFlowDense image is {}x{} but the gray frame was captured at {}x{}",
523 w,
h, layout.level[0].w, layout.level[0].h);
524 return false;
525 }
526
527 if (!state.have_prev) {
528 commit_flow_frame(contexts);
529 return false;
530 }
531
532 if (!ensure_dense_images(contexts)) {
534 "run_gpu: OpticalFlowDense failed to allocate its flow images");
535 return false;
536 }
537
538 struct DenseImages {
539 std::shared_ptr<Core::VKImage> atlas_a;
540 std::shared_ptr<Core::VKImage> atlas_b;
541 std::shared_ptr<Core::VKImage> flow_out_a;
542 std::shared_ptr<Core::VKImage> flow_out_b;
543 std::shared_ptr<Core::VKImage> flow_lvl;
544 std::shared_ptr<Core::VKImage> scratch_a;
545 std::shared_ptr<Core::VKImage> scratch_b;
546 std::shared_ptr<Core::VKImage> tensor;
547 std::shared_ptr<Core::VKImage> vis;
548 };
549
550 const auto images = std::make_shared<const DenseImages>(DenseImages {
551 .atlas_a = state.atlas[0],
552 .atlas_b = state.atlas[1],
553 .flow_out_a = state.flow_out[0],
554 .flow_out_b = state.flow_out[1],
555 .flow_lvl = state.flow_lvl,
556 .scratch_a = state.dense_a,
557 .scratch_b = state.dense_b,
558 .tensor = state.dense_tensor,
559 .vis = state.flow_vis,
560 });
561
562 const uint32_t out_parity = state.curr;
564 const uint32_t iterations = std::max(p.iterations, 1U);
565
566 const auto image_hazard = [](
const std::shared_ptr<Core::VKImage>&
image, uint32_t binding) {
567 return HazardResource {
569 .image =
image->get_image(),
570 };
571 };
572
573 const auto scratch_a_hazard = image_hazard(images->scratch_a, 8U);
574 const auto scratch_b_hazard = image_hazard(images->scratch_b, 9U);
575 const auto tensor_hazard = image_hazard(images->tensor, 10U);
576 const auto coarse_hazard = image_hazard(images->flow_lvl, 7U);
577 const auto out_hazard = image_hazard(out_parity == 0 ? images->flow_out_a : images->flow_out_b, 5U + out_parity);
578
579 std::vector<DependencyStage> stages;
580 stages.reserve(static_cast<size_t>(layout.levels) * (2U + 3U * iterations) + 1U);
581
582 const auto push_stage = [&](const FlowDensePC& stage_pc, const PyramidLevel& region, std::vector<HazardResource> hazards) {
583 stages.push_back({
584 .config = { .shader_path =
"flow_dense.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(FlowDensePC) },
585 .stage_fn = [images, stage_pc](GpuDispatchCore& ctx) {
587 ctx.stage_image_at(3, images->atlas_a, Kind::IMAGE_STORAGE);
588 ctx.stage_image_at(4, images->atlas_b, Kind::IMAGE_STORAGE);
589 ctx.stage_image_at(5, images->flow_out_a, Kind::IMAGE_STORAGE);
590 ctx.stage_image_at(6, images->flow_out_b, Kind::IMAGE_STORAGE);
591 ctx.stage_image_at(7, images->flow_lvl, Kind::IMAGE_STORAGE);
592 ctx.stage_image_at(8, images->scratch_a, Kind::IMAGE_STORAGE);
593 ctx.stage_image_at(9, images->scratch_b, Kind::IMAGE_STORAGE);
594 ctx.stage_image_at(10, images->tensor, Kind::IMAGE_STORAGE);
595 ctx.stage_image_at(11, images->vis, Kind::IMAGE_STORAGE);
596 ctx.set_push_constants(stage_pc); },
597 .hazard_fn = [hazards = std::move(hazards)](GpuDispatchCore&) { return hazards; },
598 .explicit_groups = std::array<uint32_t, 3> { (region.w +
k_wg2d[0] - 1U) /
k_wg2d[0], (region.h +
k_wg2d[1] - 1U) /
k_wg2d[1], 1U },
599 });
600 };
601
602 for (uint32_t level = layout.levels; level-- > 0;) {
603 const PyramidLevel lv = layout.level[level];
604 const bool coarsest = level + 1U == layout.levels;
605 const PyramidLevel coarse = coarsest ? PyramidLevel {} : layout.level[level + 1];
606 const auto& flow_hazard = level == 0 ? out_hazard : coarse_hazard;
607
608 FlowDensePC pc {
609 .phase = 0,
610 .curr_atlas = state.curr,
611 .out_parity = out_parity,
612 .level = level,
613 .lvl_ox = lv.ox,
614 .lvl_oy = lv.oy,
615 .lvl_w = lv.w,
616 .lvl_h = lv.h,
617 .crs_ox = coarse.ox,
618 .crs_oy = coarse.oy,
619 .crs_w = coarse.w,
620 .crs_h = coarse.h,
621 .has_coarse = coarsest ? 0
U : 1U,
623 .eigen_threshold = p.eigen_threshold,
624 .max_step = p.max_step,
625 .visual_range = p.visual_range,
626 .visual_min_motion = p.visual_min_motion,
627 .last_iteration = 0,
628 };
629
630 const auto add = [&](
FlowDensePhase phase, std::vector<HazardResource> hazards) {
631 pc.phase = static_cast<uint32_t>(phase);
632 push_stage(pc, lv, std::move(hazards));
633 };
634
635 add(FlowDensePhase::TENSOR_H, { scratch_a_hazard });
636 add(FlowDensePhase::TENSOR_V, { tensor_hazard, scratch_a_hazard });
637 for (uint32_t iteration = 0; iteration < iterations; ++iteration) {
638 add(iteration == 0 ? FlowDensePhase::WARP_INIT :
FlowDensePhase::
WARP, { scratch_a_hazard, flow_hazard });
639 add(FlowDensePhase::BOX_H, { scratch_b_hazard, scratch_a_hazard });
640 pc.last_iteration = iteration + 1U == iterations ? 1U : 0
U;
641 add(FlowDensePhase::SOLVE, { flow_hazard, scratch_b_hazard });
642 }
643 }
644
645 if (p.visualize) {
646 const PyramidLevel finest = layout.level[0];
647 const FlowDensePC vis_pc {
648 .phase = static_cast<uint32_t>(FlowDensePhase::VISUALIZE),
649 .curr_atlas = state.curr,
650 .out_parity = out_parity,
651 .level = 0,
652 .lvl_ox = finest.ox,
653 .lvl_oy = finest.oy,
654 .lvl_w = finest.w,
655 .lvl_h = finest.h,
656 .crs_ox = 0,
657 .crs_oy = 0,
658 .crs_w = 0,
659 .crs_h = 0,
660 .has_coarse = 0,
662 .eigen_threshold = p.eigen_threshold,
663 .max_step = p.max_step,
664 .visual_range = p.visual_range,
665 .visual_min_motion = p.visual_min_motion,
666 .last_iteration = 0,
667 };
668 push_stage(vis_pc, finest, { out_hazard, image_hazard(images->vis, 11U) });
669 }
670
671 ExecutionContext dense_ctx;
673 DependencyParams dense_params;
674 dense_params.stages = stages;
675 dense_params.async = true;
676 dense_ctx.parameters = dense_params;
677 const auto dense_dispatch_result = flow.execute(Datum<> {}, dense_ctx);
681 "run_gpu: OpticalFlowDense failed to submit its dispatch sequence");
682 return false;
683 }
684
685 if (step.deferred) {
686 contexts.suspended.fence =
fence;
687 contexts.suspended.finalize = [visualize = p.visualize](VisionGpuContexts& c) { finish_dense(c, visualize); };
688 return true;
689 }
690
691 foundry.wait_for_fence(
fence);
692 foundry.release_fence(
fence);
693 finish_dense(contexts, p.visualize);
694 return false;
695}
#define MF_ERROR(comp, ctx,...)
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr std::array< uint32_t, 3 > k_wg2d
Standard 2D workgroup used by all pixel-to-pixel vision shaders.
constexpr uint32_t k_flow_dense_max_radius
Largest summation window radius of the dense solver.
@ DEPENDENCY
Part of dependency graph.
ElementType
Element type the shader expects in this binding.