15 auto w = contexts.
pass.
w;
21 const uint32_t block_width = (w + 1U) / 2U;
22 const uint32_t block_height = (
h + 1U) / 2U;
35 const CCBlockInitPC init_pc { .
width = w, .height =
h, .block_width = block_width, .block_height = block_height };
36 const CCMergePC merge_pc { .
width = w, .height =
h, .block_width = block_width, .block_height = block_height };
38 const bool contours_follow = next && next->
op == VisionOp::FindContours;
39 const uint32_t export_labels = (p.
export_labels || contours_follow) ? 1U : 0U;
44 .block_width = block_width,
45 .block_height = block_height,
47 .export_labels = export_labels
50 const std::array<uint32_t, 3> block_groups {
56 const uint32_t reset_elements = std::max(block_width * block_height,
k_max_components);
57 const auto buffer_hazards = [](
GpuDispatchCore& ctx, std::initializer_list<size_t> bindings) {
58 std::vector<HazardResource> hazards;
59 hazards.reserve(bindings.size());
60 for (
const size_t binding : bindings) {
62 { .set = 0, .binding = static_cast<uint32_t>(binding), .direction = GpuBufferBinding::Direction::INPUT_OUTPUT, .element_type = GpuBufferBinding::ElementType::UINT32 }));
67 std::shared_ptr<Core::VKImage> cc_image;
68 std::vector<DependencyStage> cc_stages;
72 .config = { .shader_path =
"cc_reset.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(
CCResetPC) },
74 .hazard_fn = [=](
GpuDispatchCore& ctx) {
return buffer_hazards(ctx, { 4, 5, 7, 8, 9 }); },
75 .explicit_groups = std::array<uint32_t, 3> { (reset_elements + 255U) / 256U, 1U, 1U },
79 .config = { .shader_path =
"cc_block_init.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(
CCBlockInitPC) },
83 .hazard_fn = [=](
GpuDispatchCore& ctx) {
return buffer_hazards(ctx, { 2 }); },
84 .explicit_groups = block_groups,
88 .config = { .shader_path =
"cc_merge.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(
CCMergePC) },
92 .hazard_fn = [=](
GpuDispatchCore& ctx) {
return buffer_hazards(ctx, { 2 }); },
93 .explicit_groups = block_groups,
97 .config = { .shader_path =
"cc_compress.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(
CCCompressPC) },
99 .hazard_fn = [=](
GpuDispatchCore& ctx) {
return buffer_hazards(ctx, { 2, 4 }); },
100 .explicit_groups = block_groups,
103 cc_stages.push_back({
104 .config = { .shader_path =
"cc_final_label.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(
CCFinalLabelPC) },
105 .stage_fn = [seed_input, final_pc, w,
h, &cc_pipeline, &cc_image](
GpuDispatchCore& ctx) {
108 cc_pipeline.prepare_output_image(w,
h);
109 cc_image = cc_pipeline.get_output_image(0);
117 cc_params.
stages = cc_stages;
118 cc_params.
async =
true;
120 const auto cc_dispatch_result = cc_pipeline.execute(
Datum<> {}, cc_ctx);
122 foundry.wait_for_fence(
fence);
123 foundry.release_fence(
fence);
124 cc_pipeline.clear_output_dimensions();
130 cc_pipeline.shared_buffer_handle(0, 6));
136 uint32_t compact_count = 0;
137 cc_pipeline.download_shared(0, 5, &compact_count,
sizeof(uint32_t));
141 cc_result.
count = compact_count;
142 cc_result.
boxes.reserve(compact_count);
144 if (compact_count > 0) {
145 std::vector<glm::uvec2> bmin(compact_count);
146 std::vector<glm::uvec2> bmax(compact_count);
147 std::vector<uint32_t> bcount(compact_count);
148 cc_pipeline.download_shared(0, 7, bmin.data(), bmin.size() *
sizeof(glm::uvec2));
149 cc_pipeline.download_shared(0, 8, bmax.data(), bmax.size() *
sizeof(glm::uvec2));
150 cc_pipeline.download_shared(0, 9, bcount.data(), bcount.size() *
sizeof(uint32_t));
152 const float inv_w = 1.0F /
static_cast<float>(w);
153 const float inv_h = 1.0F /
static_cast<float>(
h);
155 for (uint32_t i = 0; i < compact_count; ++i) {
158 const float x =
static_cast<float>(bmin[i].x) * inv_w;
159 const float y =
static_cast<float>(bmin[i].y) * inv_h;
160 const float bw =
static_cast<float>(bmax[i].x - bmin[i].x + 1) * inv_w;
161 const float bh =
static_cast<float>(bmax[i].y - bmin[i].y + 1) * inv_h;
162 cc_result.
boxes.push_back({ .x = x, .y = y, .w = bw, .h = bh, .confidence = 1.0F, .label_id = i + 1 });
181 if (!prev || prev->op != VisionOp::ConnectedComponents) {
183 "run_gpu: FindContours requires ConnectedComponents as the immediately preceding step");
188 auto w = contexts.
pass.
w;
206 component_contours.upload_shared_raw(1, 4,
reinterpret_cast<const uint8_t*
>(owner_reset.data()), owner_reset.size() *
sizeof(uint32_t));
209 component_contours.upload_shared_raw(1, 5,
reinterpret_cast<const uint8_t*
>(hole_owner_reset.data()), hole_owner_reset.size() *
sizeof(uint32_t));
211 const uint32_t zero = 0;
212 component_contours.upload_shared_raw(1, 7,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
213 component_contours.upload_shared_raw(1, 9,
reinterpret_cast<const uint8_t*
>(&zero),
sizeof(uint32_t));
217 component_contours.swap_shader({ .shader_path =
"contour_march.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(
ContourMarchPC) });
219 component_contours.prepare_output_image(w,
h);
224 .max_points_per_contour = max_points,
228 .compacted_count = 0U });
230 component_contours.set_output_dimensions(w,
h);
232 const auto fence = component_contours.dispatch_async({});
233 foundry.wait_for_fence(
fence);
234 foundry.release_fence(
fence);
241 .max_points_per_contour = max_points,
245 .compacted_count = 0U });
247 const auto fence = component_contours.dispatch_async({});
248 foundry.wait_for_fence(
fence);
249 foundry.release_fence(
fence);
252 component_contours.clear_output_dimensions();
256 return std::vector<HazardResource> {
261 const auto trace_pc = [&](uint32_t phase) {
266 .max_points_per_contour = max_points,
269 .min_area = p.min_area,
270 .compacted_count = 0U
273 const GpuComputeConfig march_config { .
shader_path =
"contour_march.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(ContourMarchPC) };
275 std::vector<DependencyStage> trace_stages;
276 trace_stages.push_back({
277 .config = { .shader_path =
"contour_compact.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourCompactPC) },
278 .stage_fn = [](GpuDispatchCore& ctx) {
281 .hazard_fn = owner_hazards,
282 .explicit_groups = std::array<uint32_t, 3> { (total_owner_slots + 255U) / 256U, 1U, 1U },
284 trace_stages.push_back({
285 .config = march_config,
286 .stage_fn = [pc = trace_pc(3U)](GpuDispatchCore& ctx) { ctx.set_push_constants(pc); },
287 .explicit_groups = std::array<uint32_t, 3> { 1U, 1U, 1U },
289 const bool sort_requested = p.max_contours > 0
U;
293 const auto sort_hazards = [contour_hazard](GpuDispatchCore& ctx) {
294 return std::vector<HazardResource> {
300 DependencyStage trace_stage {
301 .config = march_config,
302 .stage_fn = [pc = trace_pc(2U)](GpuDispatchCore& ctx) { ctx.set_push_constants(pc); },
303 .explicit_groups = std::array<uint32_t, 3> { 1U, 1U, 1U },
304 .indirect_groups = IndirectGroupsSource { .set = 0, .binding = 10, .offset_bytes = 3U *
sizeof(uint32_t) },
306 if (sort_requested || p.as_image) {
307 trace_stage.hazard_fn = [contour_hazard](GpuDispatchCore& ctx) {
308 return std::vector<HazardResource> {
316 trace_stages.push_back(std::move(trace_stage));
318 if (sort_requested) {
322 .workgroup_size = { 256, 1, 1 },
323 .push_constant_size =
sizeof(ContourTopKPC),
326 for (uint32_t round = 0; round < rounds; ++round) {
328 trace_stages.push_back({
329 .config = topk_config,
330 .stage_fn = [topk_pc](GpuDispatchCore& ctx) { ctx.set_push_constants(topk_pc); },
331 .hazard_fn = sort_hazards,
332 .explicit_groups = std::array<uint32_t, 3> { 1U, 1U, 1U },
337 std::shared_ptr<Core::VKImage> contour_image;
340 const ContourClearPC clear_pc { .width = w, .height =
h };
341 const ContourRenderPC render_pc {
346 .max_contours = p.max_contours
349 trace_stages.push_back({
350 .config = { .shader_path =
"contour_render_clear.comp.spv", .workgroup_size =
k_wg2d, .push_constant_size =
sizeof(ContourClearPC) },
351 .stage_fn = [&component_contours, &contour_image, w,
h, clear_pc](GpuDispatchCore& ctx) {
352 component_contours.prepare_output_image(w,
h);
353 contour_image = component_contours.get_output_image(0);
354 ctx.set_push_constants(clear_pc); },
355 .hazard_fn = [&contour_image](GpuDispatchCore&) {
return std::vector<HazardResource> {
357 .image = contour_image->get_image() },
361 trace_stages.push_back({
362 .config = { .shader_path =
"contour_render.comp.spv", .workgroup_size = { 256, 1, 1 }, .push_constant_size =
sizeof(ContourRenderPC) },
363 .stage_fn = [&contour_image, render_pc](GpuDispatchCore& ctx) {
365 ctx.set_push_constants(render_pc); },
370 ExecutionContext trace_ctx;
372 DependencyParams trace_params;
373 trace_params.stages = trace_stages;
374 trace_ctx.parameters = trace_params;
375 component_contours.execute(Datum<> {}, trace_ctx);
378 contexts.pass.result.images.contour_image = contour_image;
379 contexts.pass.result.structured = std::monostate {};
380 contexts.pass.result.w = 0;
381 contexts.pass.result.h = 0;
385 uint32_t compacted_count = 0;
386 component_contours.download_shared(1, 7, &compacted_count,
sizeof(uint32_t));
388 std::vector<glm::uvec4> meta(compacted_count);
389 std::vector<glm::vec2> area_perim(compacted_count);
390 if (compacted_count > 0) {
391 component_contours.download_shared(1, 10, meta.data(), compacted_count *
sizeof(glm::uvec4));
392 component_contours.download_shared(1, 11, area_perim.data(), compacted_count *
sizeof(glm::vec2));
394 uint32_t points_written = 0;
395 component_contours.download_shared(1, 9, &points_written,
sizeof(uint32_t));
396 std::vector<glm::vec2> flat_points_full(points_written);
398 if (points_written > 0)
399 component_contours.download_shared(1, 8, flat_points_full.data(),
static_cast<size_t>(points_written) *
sizeof(glm::vec2));
401 if (p.export_contours_buffer) {
402 contexts.pass.result.buffers.contours = Kinesis::Vision::ContoursBufferView {
403 .count = compacted_count,
404 .points_written = points_written,
405 .meta = std::make_shared<Portal::Graphics::GpuBufferHandle>(component_contours.shared_buffer_handle(1, 10)),
406 .points = std::make_shared<Portal::Graphics::GpuBufferHandle>(component_contours.shared_buffer_handle(1, 8)),
407 .area_perim = std::make_shared<Portal::Graphics::GpuBufferHandle>(component_contours.shared_buffer_handle(1, 11)),
411 std::vector<uint32_t> order;
412 if (p.max_contours > 0U) {
413 const uint32_t take = std::min(p.max_contours, compacted_count);
414 std::vector<float> sorted_indices(take);
415 component_contours.download_shared(2, 1, sorted_indices.data(), take *
sizeof(
float));
417 for (
float f : sorted_indices)
418 order.push_back(static_cast<uint32_t>(f));
420 order.resize(compacted_count);
421 for (uint32_t i = 0; i < compacted_count; ++i)
425 std::vector<Kinesis::Vision::Contour> out_contours;
426 out_contours.reserve(order.size());
428 for (uint32_t idx : order) {
429 if (idx >= compacted_count)
431 const auto& m = meta[idx];
434 if (m.x > points_written || m.y > points_written - m.x)
437 std::vector<glm::vec2> pts(
438 flat_points_full.begin() + m.x,
439 flat_points_full.begin() + m.x + m.y);
440 const glm::vec2 ap = area_perim[idx];
441 out_contours.push_back({ .points = std::move(pts), .area = ap.x, .perimeter = ap.y, .parent_label = m.z, .label_id = m.w });
444 contexts.pass.result.structured = std::move(out_contours);
445 contexts.pass.result.w = 0;
446 contexts.pass.result.h = 0;