From 67cbf31e8b79053f9f00335a49c5358e62b6edcf Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Mon, 21 Sep 2026 15:22:36 +0200 Subject: [PATCH] Run colour segmentation and vertical shells in parallel Same output, ~2.6 min for the LOTR map plate, was ~2.9. --- src/libslic3r/MultiMaterialSegmentation.cpp | 65 ++++++++++++--------- src/libslic3r/PrintObject.cpp | 33 ++++++++++- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 6f80f7b759..57da8570ee 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1378,13 +1378,40 @@ static inline std::vector> segmentation_top_and_bottom_l return out; }; + // Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in + // `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left. + // The per-layer ClipperLib work is independent once the trimmed slices and the offsets have been walked in order, + // so it runs in parallel; the offsets are accumulated exactly as the sequential walk did. + const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector &shell_layers, + const LayerColorStat &stat, std::vector &dst, size_t dst_offset) { + std::vector trimmed(shell_layers.size()); + std::vector offsets(shell_layers.size()); + ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; + float offset = 0.f; + for (size_t i = 0; i < shell_layers.size(); ++i) { + //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line + offset -= (stat.extrusion_spacing + stat.extrusion_width); + offsets[i] = offset; + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[shell_layers[i]]); + trimmed[i] = layer_slices_trimmed; + } + std::vector shells(shell_layers.size()); + tbb::parallel_for(size_t(0), shell_layers.size(), [&](size_t i) { + shells[i] = opening_ex(intersection_ex(ex, offset_ex(trimmed[i], offsets[i])), stat.small_region_threshold); + }); + for (size_t i = 0; i < shell_layers.size() && !shells[i].empty(); ++i) + append(dst[shell_layers[i] + dst_offset], std::move(shells[i])); + }; + tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top, - &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom, + &throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells, &shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) { size_t group_idx = range.begin() / granularity; size_t layer_idx_offset = (group_idx & 1) * num_layers; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) { + // Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face + // projects onto a single layer, which otherwise did all of its colours on one thread. + tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) { throw_on_cancel_callback(); LayerColorStat stat = layer_color_stat(layer_idx, color_idx); if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) @@ -1393,18 +1420,10 @@ static inline std::vector> segmentation_top_and_bottom_l top_ex = opening_ex(top_ex, stat.small_region_threshold); if (! top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); - float offset = 0.f; - ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; - for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) { - //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line - //offset -= stat.extrusion_width ; - offset -= (stat.extrusion_spacing + stat.extrusion_width); - layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); - if (last.empty()) - break; - append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); - } + std::vector shell_layers; + for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) + shell_layers.emplace_back(size_t(last_idx)); + project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx], layer_idx_offset); } } if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) @@ -1413,21 +1432,13 @@ static inline std::vector> segmentation_top_and_bottom_l bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); - float offset = 0.f; - ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; - for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) { - //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line - //offset -= stat.extrusion_width; - offset -= (stat.extrusion_spacing + stat.extrusion_width); - layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); - ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); - if (last.empty()) - break; - append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); - } + std::vector shell_layers; + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) + shell_layers.emplace_back(last_idx); + project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx], layer_idx_offset); } } - } + }); } }); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 3dadd4c9c0..7101e1d4d7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -2304,6 +2305,18 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } + // With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing + // region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier + // one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom + // surfaces, and a multi-material print has a region per filament. + struct ShellAccumulation + { + std::array key; + Polygons shell; + Polygons holes; + }; + std::vector> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0); + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { const PrintRegion ®ion = this->printing_region(region_id); if (region.config().ensure_vertical_shell_thickness.value != evstAll ) @@ -2348,7 +2361,7 @@ void PrintObject::discover_vertical_shells() grain_size = 1; tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), - [this, region_id, &cache_top_botom_regions] + [this, region_id, &cache_top_botom_regions, &shell_accumulations] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { @@ -2398,6 +2411,21 @@ void PrintObject::discover_vertical_shells() } } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + const std::array accumulation_key{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value, + double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value, + double(layerm->flow(frExternalPerimeter).scaled_spacing()) }; + std::vector *accumulations = shell_accumulations.empty() ? nullptr : &shell_accumulations[idx_layer]; + const auto reused = accumulations == nullptr ? nullptr : + [&]() -> const ShellAccumulation * { + for (const ShellAccumulation &a : *accumulations) + if (a.key == accumulation_key) + return &a; + return nullptr; + }(); + if (reused != nullptr) { + shell = reused->shell; + holes = reused->holes; + } else { polygons_append(holes, cache_top_botom_regions[idx_layer].holes); auto combine_holes = [&holes](const Polygons &holes2) { if (holes.empty() || holes2.empty()) @@ -2472,6 +2500,9 @@ void PrintObject::discover_vertical_shells() (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) combine_holes(cache_top_botom_regions[i].holes); } + if (accumulations != nullptr) + accumulations->push_back({ accumulation_key, shell, holes }); + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));