This commit is contained in:
Nicolas Predella 2026-09-26 21:22:01 +03:00
parent ba64eae122
commit bed38bfa57
4 changed files with 558 additions and 319 deletions

View file

@ -40,7 +40,7 @@ set_target_properties(cudaslicer_offset PROPERTIES
CUDA_SEPARABLE_COMPILATION OFF CUDA_SEPARABLE_COMPILATION OFF
) )
target_compile_definitions(cudaslicer_offset PRIVATE GPU_OFFSET_EXPORTS) target_compile_definitions(cudaslicer_offset PRIVATE GPU_OFFSET_EXPORTS)
target_link_libraries(cudaslicer_offset PUBLIC cudart) target_link_libraries(cudaslicer_offset PUBLIC cudart OpenMP::OpenMP_CXX)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 2. Standalone C/C++ Static Library # 2. Standalone C/C++ Static Library
@ -54,7 +54,7 @@ set_target_properties(cudaslicer_offset_static PROPERTIES
OUTPUT_NAME "cudaslicer_offset" OUTPUT_NAME "cudaslicer_offset"
CUDA_SEPARABLE_COMPILATION OFF CUDA_SEPARABLE_COMPILATION OFF
) )
target_link_libraries(cudaslicer_offset_static PUBLIC cudart) target_link_libraries(cudaslicer_offset_static PUBLIC cudart OpenMP::OpenMP_CXX)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 3. C++ Benchmark Binary # 3. C++ Benchmark Binary
@ -108,7 +108,7 @@ set_target_properties(_cudaslicer_backend PROPERTIES
SUFFIX "${_ext_suffix}" SUFFIX "${_ext_suffix}"
) )
target_include_directories(_cudaslicer_backend PRIVATE "${_python3_include_dir}" "${_pybind11_include_dir}") target_include_directories(_cudaslicer_backend PRIVATE "${_python3_include_dir}" "${_pybind11_include_dir}")
target_link_libraries(_cudaslicer_backend PRIVATE cudaslicer_offset_static cudart) target_link_libraries(_cudaslicer_backend PRIVATE cudaslicer_offset_static cudart OpenMP::OpenMP_CXX)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Output Directories # Output Directories

View file

@ -79,10 +79,14 @@ py::array_t<uint8_t> py_offset_masks(
} }
GpuOffsetStats stats = {0}; GpuOffsetStats stats = {0};
int ret = gpu_offset_compute_masks( int ret;
{
py::gil_scoped_release release;
ret = gpu_offset_compute_masks(
ptr_in, width, height, num_layers, pitch_mm, ptr_in, width, height, num_layers, pitch_mm,
resolved_offsets.data(), num_offsets, ptr_out, &stats resolved_offsets.data(), num_offsets, ptr_out, &stats
); );
}
if (!backend.empty()) { if (!backend.empty()) {
gpu_offset_set_backend(prev_backend); gpu_offset_set_backend(prev_backend);
@ -169,11 +173,15 @@ py::list py_offset_polygon_layers(
GpuLayerResult* results = nullptr; GpuLayerResult* results = nullptr;
GpuOffsetStats stats = {0}; GpuOffsetStats stats = {0};
int ret = gpu_offset_polygon_layers( int ret;
{
py::gil_scoped_release release;
ret = gpu_offset_polygon_layers(
c_layers.data(), num_layers, c_layers.data(), num_layers,
min_x, min_y, max_x, max_y, min_x, min_y, max_x, max_y,
&config, &results, &stats &config, &results, &stats
); );
}
if (ret == GPU_OFFSET_ERR_NO_CUDA) { if (ret == GPU_OFFSET_ERR_NO_CUDA) {
throw std::runtime_error("CUDA backend was requested, but no functional CUDA-capable device was found."); throw std::runtime_error("CUDA backend was requested, but no functional CUDA-capable device was found.");
@ -326,12 +334,16 @@ py::list py_slice_mesh_and_offset(
size_t out_num_layers = 0; size_t out_num_layers = 0;
GpuOffsetStats stats = {0}; GpuOffsetStats stats = {0};
int ret = gpu_offset_slice_mesh_and_offset( int ret;
{
py::gil_scoped_release release;
ret = gpu_offset_slice_mesh_and_offset(
tris_ptr, num_tris, tris_ptr, num_tris,
min_x, min_y, min_z, max_x, max_y, max_z, min_x, min_y, min_z, max_x, max_y, max_z,
layer_height, first_layer_height, &config, layer_height, first_layer_height, &config,
&results, &out_num_layers, &stats &results, &out_num_layers, &stats
); );
}
if (ret == GPU_OFFSET_ERR_NO_CUDA) { if (ret == GPU_OFFSET_ERR_NO_CUDA) {
throw std::runtime_error("CUDA backend was requested, but no functional CUDA-capable device was found."); throw std::runtime_error("CUDA backend was requested, but no functional CUDA-capable device was found.");

View file

@ -6,7 +6,7 @@
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <thread> #include <omp.h>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Fast Separable 2D Exact Euclidean Distance Transform on Binary Grid // Fast Separable 2D Exact Euclidean Distance Transform on Binary Grid
@ -21,8 +21,8 @@ static inline void edt_1d_binary_col(const uint8_t* col_mask, int height, int st
if (col_mask[(size_t)y * stride] == 0) { if (col_mask[(size_t)y * stride] == 0) {
last = y; last = y;
} }
float dy = (float)(y - last); int dy = y - last;
col_g[y] = (last >= 0) ? (dy * dy) : INF; col_g[y] = (last >= 0) ? (float)(dy * dy) : INF;
} }
// Backward pass // Backward pass
last = 1000000; last = 1000000;
@ -30,8 +30,34 @@ static inline void edt_1d_binary_col(const uint8_t* col_mask, int height, int st
if (col_mask[(size_t)y * stride] == 0) { if (col_mask[(size_t)y * stride] == 0) {
last = y; last = y;
} }
float dy = (float)(last - y); int dy = last - y;
float d2 = (last < height) ? (dy * dy) : INF; float d2 = (last < height) ? (float)(dy * dy) : INF;
if (d2 < col_g[y]) {
col_g[y] = d2;
}
}
}
// Pass 1 on inverted binary column (mask > 0 are seeds)
static inline void edt_1d_binary_col_inverted(const uint8_t* col_mask, int height, int stride, float* col_g) {
const float INF = 1e12f;
// Forward pass
int last = -1000000;
for (int y = 0; y < height; ++y) {
if (col_mask[(size_t)y * stride] != 0) {
last = y;
}
int dy = y - last;
col_g[y] = (last >= 0) ? (float)(dy * dy) : INF;
}
// Backward pass
last = 1000000;
for (int y = height - 1; y >= 0; --y) {
if (col_mask[(size_t)y * stride] != 0) {
last = y;
}
int dy = last - y;
float d2 = (last < height) ? (float)(dy * dy) : INF;
if (d2 < col_g[y]) { if (d2 < col_g[y]) {
col_g[y] = d2; col_g[y] = d2;
} }
@ -78,12 +104,19 @@ static inline void edt_2d_layer(
std::vector<float>& row_f, std::vector<float>& row_f,
std::vector<float>& row_d, std::vector<float>& row_d,
std::vector<int>& v_buf, std::vector<int>& v_buf,
std::vector<float>& z_buf std::vector<float>& z_buf,
bool invert = false
) { ) {
// Pass 1: Columns // Pass 1: Columns
if (!invert) {
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
edt_1d_binary_col(mask + x, height, width, g_col_buf.data() + (size_t)x * height); edt_1d_binary_col(mask + x, height, width, g_col_buf.data() + (size_t)x * height);
} }
} else {
for (int x = 0; x < width; ++x) {
edt_1d_binary_col_inverted(mask + x, height, width, g_col_buf.data() + (size_t)x * height);
}
}
// Pass 2: Rows // Pass 2: Rows
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
@ -98,13 +131,23 @@ static inline void edt_2d_layer(
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// CPU Multi-Threaded Polygon Rasterizer // Structures for Fast Exact Analytical Segment Distance on CPU
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
struct CpuEdge2D { struct CpuSegment2D {
float x1, y1; float x0, y0, x1, y1;
float x2, y2; float vx, vy;
float inv_len_sq;
float min_x, max_x, min_y, max_y;
}; };
struct TileSegNode {
int seg_idx;
int next;
};
// -----------------------------------------------------------------------------
// CPU Multi-Threaded Polygon Rasterizer (Scanline Parity)
// -----------------------------------------------------------------------------
void cpu_rasterize_polygon_layers_internal( void cpu_rasterize_polygon_layers_internal(
const GpuSliceLayer* in_layers, const GpuSliceLayer* in_layers,
size_t num_layers, size_t num_layers,
@ -112,60 +155,80 @@ void cpu_rasterize_polygon_layers_internal(
int width, int height, int width, int height,
float min_x, float min_y, float pitch_mm float min_x, float min_y, float pitch_mm
) { ) {
unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); int row_stride_words = (width + 31) / 32;
std::vector<std::thread> workers; float inv_pitch = 1.0f / pitch_mm;
auto worker_fn = [&](size_t start_layer, size_t end_layer) { #pragma omp parallel
for (size_t l = start_layer; l < end_layer; ++l) { {
uint8_t* mask = out_masks + l * ((size_t)width * height); std::vector<unsigned int> parity_words((size_t)height * row_stride_words);
#pragma omp for schedule(dynamic, 4)
for (int l = 0; l < (int)num_layers; ++l) {
uint8_t* mask = out_masks + (size_t)l * ((size_t)width * height);
const GpuSliceLayer& layer = in_layers[l]; const GpuSliceLayer& layer = in_layers[l];
std::fill(parity_words.begin(), parity_words.end(), 0u);
std::vector<CpuEdge2D> edges;
for (size_t c = 0; c < layer.num_contours; ++c) { for (size_t c = 0; c < layer.num_contours; ++c) {
const GpuContour& cnt = layer.contours[c]; const GpuContour& cnt = layer.contours[c];
if (cnt.num_points < 3) continue; if (cnt.num_points < 3) continue;
for (size_t p = 0; p < cnt.num_points; ++p) { for (size_t p = 0; p < cnt.num_points; ++p) {
size_t p_next = (p + 1) % cnt.num_points; size_t p_next = (p + 1 == cnt.num_points) ? 0 : p + 1;
edges.push_back({ cnt.points[p].x, cnt.points[p].y, float px0 = cnt.points[p].x;
cnt.points[p_next].x, cnt.points[p_next].y }); float py0 = cnt.points[p].y;
float px1 = cnt.points[p_next].x;
float py1 = cnt.points[p_next].y;
float gx0 = (px0 - min_x) * inv_pitch;
float gy0 = (py0 - min_y) * inv_pitch;
float gx1 = (px1 - min_x) * inv_pitch;
float gy1 = (py1 - min_y) * inv_pitch;
if (gy0 == gy1) continue;
if (gy0 > gy1) {
std::swap(gx0, gx1);
std::swap(gy0, gy1);
}
int y_start = (int)std::ceil(gy0);
int y_end = (int)std::ceil(gy1) - 1;
if (y_start < 0) y_start = 0;
if (y_end >= height) y_end = height - 1;
float dy = gy1 - gy0;
if (dy < 1e-7f) continue;
for (int y = y_start; y <= y_end; ++y) {
float t = ((float)y - gy0) / dy;
float gx = gx0 + t * (gx1 - gx0);
int x_int = (int)std::ceil(gx);
if (x_int < 0) x_int = 0;
if (x_int >= width) x_int = width - 1;
int w = x_int / 32;
int b = x_int % 32;
parity_words[(size_t)y * row_stride_words + w] ^= (1u << b);
}
} }
} }
// Scan parity into mask
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
float py = min_y + (y + 0.5f) * pitch_mm; const unsigned int* row_p = parity_words.data() + (size_t)y * row_stride_words;
std::vector<float> x_inters; uint8_t* row_m = mask + (size_t)y * width;
uint8_t inside = 0;
for (const auto& e : edges) { int x_out = 0;
if ((e.y1 <= py && e.y2 > py) || (e.y2 <= py && e.y1 > py)) { for (int w = 0; w < row_stride_words && x_out < width; ++w) {
float x_int = e.x1 + (py - e.y1) * (e.x2 - e.x1) / (e.y2 - e.y1); unsigned int word = row_p[w];
x_inters.push_back(x_int); for (int b = 0; b < 32 && x_out < width; ++b, ++x_out) {
} inside ^= (uint8_t)((word >> b) & 1u);
} row_m[x_out] = inside;
std::sort(x_inters.begin(), x_inters.end());
for (size_t i = 0; i + 1 < x_inters.size(); i += 2) {
int x_start = std::max(0, (int)std::ceil((x_inters[i] - min_x) / pitch_mm - 0.5f));
int x_end = std::min(width, (int)std::floor((x_inters[i + 1] - min_x) / pitch_mm - 0.5f) + 1);
for (int x = x_start; x < x_end; ++x) {
mask[(size_t)y * width + x] = 1;
} }
} }
} }
} }
};
size_t chunk = (num_layers + num_threads - 1) / num_threads;
for (unsigned int t = 0; t < num_threads; ++t) {
size_t s = t * chunk;
size_t e = std::min(s + chunk, num_layers);
if (s < e) {
workers.emplace_back(worker_fn, s, e);
} }
} }
for (auto& w : workers) w.join();
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// CPU Implementation: Core Mask Offsetting // CPU Implementation: Core Mask Offsetting
@ -189,43 +252,34 @@ int cpu_offset_compute_masks(
size_t layer_pixels = (size_t)width * height; size_t layer_pixels = (size_t)width * height;
size_t total_volume = (size_t)num_layers * layer_pixels; size_t total_volume = (size_t)num_layers * layer_pixels;
unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); #pragma omp parallel
std::vector<std::thread> workers; {
auto worker_fn = [&](size_t start_layer, size_t end_layer) {
std::vector<float> dist_sq(layer_pixels); std::vector<float> dist_sq(layer_pixels);
std::vector<float> g_col_buf(layer_pixels); std::vector<float> g_col_buf(layer_pixels);
std::vector<float> row_f(width), row_d(width); std::vector<float> row_f(width), row_d(width);
std::vector<int> v_buf(width + 1); std::vector<int> v_buf(width + 1);
std::vector<float> z_buf(width + 2); std::vector<float> z_buf(width + 2);
for (size_t l = start_layer; l < end_layer; ++l) { #pragma omp for schedule(dynamic, 4)
const uint8_t* mask_layer = in_masks + l * layer_pixels; for (int l = 0; l < num_layers; ++l) {
const uint8_t* mask_layer = in_masks + (size_t)l * layer_pixels;
// 1. Compute 2D EDT // 1. Compute 2D EDT
edt_2d_layer(mask_layer, width, height, dist_sq.data(), edt_2d_layer(mask_layer, width, height, dist_sq.data(),
g_col_buf, row_f, row_d, v_buf, z_buf); g_col_buf, row_f, row_d, v_buf, z_buf, false);
// 2. Thresholding for each offset distance // 2. Thresholding for each offset distance
for (int o = 0; o < num_offsets; ++o) { for (int o = 0; o < num_offsets; ++o) {
float thresh = offset_distances[o]; float thresh = offset_distances[o];
float thresh_sq_px = (thresh / pitch_mm) * (thresh / pitch_mm); float thresh_sq_px = (thresh / pitch_mm) * (thresh / pitch_mm);
uint8_t* out_layer = out_masks + (size_t)o * total_volume + l * layer_pixels; uint8_t* out_layer = out_masks + (size_t)o * total_volume + (size_t)l * layer_pixels;
for (size_t i = 0; i < layer_pixels; ++i) { for (size_t i = 0; i < layer_pixels; ++i) {
out_layer[i] = (mask_layer[i] > 0 && dist_sq[i] >= thresh_sq_px) ? 1 : 0; out_layer[i] = (mask_layer[i] > 0 && dist_sq[i] >= thresh_sq_px) ? 1 : 0;
} }
} }
} }
};
size_t chunk = (num_layers + num_threads - 1) / num_threads;
for (unsigned int t = 0; t < num_threads; ++t) {
size_t s = t * chunk;
size_t e = std::min(s + chunk, (size_t)num_layers);
if (s < e) workers.emplace_back(worker_fn, s, e);
} }
for (auto& w : workers) w.join();
auto t_end = std::chrono::high_resolution_clock::now(); auto t_end = std::chrono::high_resolution_clock::now();
double total_ms = std::chrono::duration<double, std::milli>(t_end - t_start).count(); double total_ms = std::chrono::duration<double, std::milli>(t_end - t_start).count();
@ -294,12 +348,11 @@ int cpu_offset_polygon_layers(
GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult)); GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult));
memset(res_array, 0, num_layers * sizeof(GpuLayerResult)); memset(res_array, 0, num_layers * sizeof(GpuLayerResult));
unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency());
std::vector<std::thread> workers;
int row_stride_words = (cols + 31) / 32; int row_stride_words = (cols + 31) / 32;
bool subpixel_lerp = config->subpixel_lerp != 0;
auto worker_fn = [&](size_t start_layer, size_t end_layer) { #pragma omp parallel
{
int stride_w = cols + 1; int stride_w = cols + 1;
size_t max_edges = 2 * (size_t)(rows + 1) * stride_w; size_t max_edges = 2 * (size_t)(rows + 1) * stride_w;
std::vector<int> next_edge_buf(max_edges, -1); std::vector<int> next_edge_buf(max_edges, -1);
@ -307,7 +360,14 @@ int cpu_offset_polygon_layers(
active_edges_buf.reserve(cols * rows / 16); active_edges_buf.reserve(cols * rows / 16);
std::vector<uint8_t> visited_buf(max_edges, 0); std::vector<uint8_t> visited_buf(max_edges, 0);
std::vector<float> dist_sq(layer_pixels); std::vector<float> dist_sq_ext(layer_pixels);
std::vector<float> dist_sq_int;
std::vector<float> layer_sdf;
if (subpixel_lerp) {
dist_sq_int.resize(layer_pixels);
layer_sdf.resize(layer_pixels);
}
std::vector<float> g_col_buf(layer_pixels); std::vector<float> g_col_buf(layer_pixels);
std::vector<float> row_f(cols), row_d(cols); std::vector<float> row_f(cols), row_d(cols);
std::vector<int> v_buf(cols + 1); std::vector<int> v_buf(cols + 1);
@ -315,12 +375,26 @@ int cpu_offset_polygon_layers(
std::vector<unsigned int> bit_words((size_t)rows * row_stride_words); std::vector<unsigned int> bit_words((size_t)rows * row_stride_words);
for (size_t l = start_layer; l < end_layer; ++l) { #pragma omp for schedule(dynamic, 2)
const uint8_t* mask_ptr = h_masks.data() + l * layer_pixels; for (int l = 0; l < (int)num_layers; ++l) {
const uint8_t* mask_ptr = h_masks.data() + (size_t)l * layer_pixels;
// Compute exact 2D EDT // Compute exact 2D EDT (exterior)
edt_2d_layer(mask_ptr, cols, rows, dist_sq.data(), edt_2d_layer(mask_ptr, cols, rows, dist_sq_ext.data(),
g_col_buf, row_f, row_d, v_buf, z_buf); g_col_buf, row_f, row_d, v_buf, z_buf, false);
if (subpixel_lerp) {
edt_2d_layer(mask_ptr, cols, rows, dist_sq_int.data(),
g_col_buf, row_f, row_d, v_buf, z_buf, true);
for (size_t i = 0; i < layer_pixels; ++i) {
if (mask_ptr[i] > 0) {
layer_sdf[i] = sqrtf(dist_sq_ext[i]) * pitch;
} else {
layer_sdf[i] = -sqrtf(dist_sq_int[i]) * pitch;
}
}
}
res_array[l].z_height = in_layers[l].z_height; res_array[l].z_height = in_layers[l].z_height;
res_array[l].num_perimeters = P; res_array[l].num_perimeters = P;
@ -328,13 +402,20 @@ int cpu_offset_polygon_layers(
// Perimeter passes // Perimeter passes
for (int o = 0; o < P; ++o) { for (int o = 0; o < P; ++o) {
std::vector<GpuContour> contours;
if (subpixel_lerp) {
float iso_thresh = resolved_offsets[o];
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
iso_thresh, config->simplify_tolerance_mm, 1,
next_edge_buf, active_edges_buf, visited_buf, contours);
} else {
float thresh = resolved_offsets[o]; float thresh = resolved_offsets[o];
float thresh_sq_px = (thresh / pitch) * (thresh / pitch); float thresh_sq_px = (thresh / pitch) * (thresh / pitch);
// Bit-pack thresholded mask
for (int y = 0; y < rows; ++y) { for (int y = 0; y < rows; ++y) {
const uint8_t* row_m = mask_ptr + (size_t)y * cols; const uint8_t* row_m = mask_ptr + (size_t)y * cols;
const float* row_d2 = dist_sq.data() + (size_t)y * cols; const float* row_d2 = dist_sq_ext.data() + (size_t)y * cols;
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words; unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
for (int w = 0; w < row_stride_words; ++w) { for (int w = 0; w < row_stride_words; ++w) {
@ -352,11 +433,10 @@ int cpu_offset_polygon_layers(
} }
} }
// Marching squares vectorization
std::vector<GpuContour> contours;
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words, vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
bounds_min_x, bounds_min_y, pitch, bounds_min_x, bounds_min_y, pitch,
next_edge_buf, active_edges_buf, visited_buf, contours); next_edge_buf, active_edges_buf, visited_buf, contours);
}
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o]; res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
res_array[l].perimeters[o].num_contours = contours.size(); res_array[l].perimeters[o].num_contours = contours.size();
@ -369,12 +449,18 @@ int cpu_offset_polygon_layers(
} }
// Infill pass (index P) // Infill pass (index P)
std::vector<GpuContour> infill_contours;
float infill_thresh = resolved_offsets[P]; float infill_thresh = resolved_offsets[P];
float infill_thresh_sq_px = (infill_thresh / pitch) * (infill_thresh / pitch);
if (subpixel_lerp) {
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
infill_thresh, config->simplify_tolerance_mm, 1,
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
} else {
float infill_thresh_sq_px = (infill_thresh / pitch) * (infill_thresh / pitch);
for (int y = 0; y < rows; ++y) { for (int y = 0; y < rows; ++y) {
const uint8_t* row_m = mask_ptr + (size_t)y * cols; const uint8_t* row_m = mask_ptr + (size_t)y * cols;
const float* row_d2 = dist_sq.data() + (size_t)y * cols; const float* row_d2 = dist_sq_ext.data() + (size_t)y * cols;
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words; unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
for (int w = 0; w < row_stride_words; ++w) { for (int w = 0; w < row_stride_words; ++w) {
@ -392,12 +478,12 @@ int cpu_offset_polygon_layers(
} }
} }
std::vector<GpuContour> infill_contours;
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words, vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
bounds_min_x, bounds_min_y, pitch, bounds_min_x, bounds_min_y, pitch,
next_edge_buf, active_edges_buf, visited_buf, infill_contours); next_edge_buf, active_edges_buf, visited_buf, infill_contours);
}
res_array[l].infill.offset_distance_mm = resolved_offsets[P]; res_array[l].infill.offset_distance_mm = infill_thresh;
res_array[l].infill.width = cols; res_array[l].infill.width = cols;
res_array[l].infill.height = rows; res_array[l].infill.height = rows;
res_array[l].infill.num_contours = infill_contours.size(); res_array[l].infill.num_contours = infill_contours.size();
@ -409,16 +495,15 @@ int cpu_offset_polygon_layers(
} }
if (config->export_infill_mask) { if (config->export_infill_mask) {
uint8_t* infill_mask = (uint8_t*)malloc((size_t)cols * rows); uint8_t* infill_mask = (uint8_t*)malloc(layer_pixels);
for (int y = 0; y < rows; ++y) { if (subpixel_lerp) {
const unsigned int* row_words = bit_words.data() + (size_t)y * row_stride_words; for (size_t i = 0; i < layer_pixels; ++i) {
uint8_t* row_out = infill_mask + (size_t)y * cols; infill_mask[i] = (layer_sdf[i] >= infill_thresh) ? 1 : 0;
int x_out = 0;
for (int w = 0; w < row_stride_words && x_out < cols; ++w) {
unsigned int word = row_words[w];
for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) {
row_out[x_out] = (uint8_t)((word >> b) & 1u);
} }
} else {
float infill_thresh_sq_px = (infill_thresh / pitch) * (infill_thresh / pitch);
for (size_t i = 0; i < layer_pixels; ++i) {
infill_mask[i] = (mask_ptr[i] > 0 && dist_sq_ext[i] >= infill_thresh_sq_px) ? 1 : 0;
} }
} }
res_array[l].infill.mask = infill_mask; res_array[l].infill.mask = infill_mask;
@ -426,15 +511,7 @@ int cpu_offset_polygon_layers(
res_array[l].infill.mask = nullptr; res_array[l].infill.mask = nullptr;
} }
} }
};
size_t chunk = (num_layers + num_threads - 1) / num_threads;
for (unsigned int t = 0; t < num_threads; ++t) {
size_t s = t * chunk;
size_t e = std::min(s + chunk, num_layers);
if (s < e) workers.emplace_back(worker_fn, s, e);
} }
for (auto& w : workers) w.join();
auto t_total_end = std::chrono::high_resolution_clock::now(); auto t_total_end = std::chrono::high_resolution_clock::now();
double t_total_ms = std::chrono::duration<double, std::milli>(t_total_end - t_total_start).count(); double t_total_ms = std::chrono::duration<double, std::milli>(t_total_end - t_total_start).count();
@ -442,7 +519,7 @@ int cpu_offset_polygon_layers(
if (stats) { if (stats) {
memset(stats, 0, sizeof(GpuOffsetStats)); memset(stats, 0, sizeof(GpuOffsetStats));
stats->t_raster_ms = t_raster_ms; stats->t_raster_ms = t_raster_ms;
stats->t_gpu_jfa_ms = t_total_ms - t_raster_ms; // EDT + Vectorize stats->t_gpu_jfa_ms = t_total_ms - t_raster_ms;
stats->t_total_pipeline_ms = t_total_ms; stats->t_total_pipeline_ms = t_total_ms;
} }
@ -510,6 +587,8 @@ int cpu_offset_slice_mesh_and_offset(
int row_stride_words = (cols + 31) / 32; int row_stride_words = (cols + 31) / 32;
size_t layer_pixels = (size_t)cols * rows; size_t layer_pixels = (size_t)cols * rows;
float inv_pitch = 1.0f / pitch;
bool subpixel_lerp = config->subpixel_lerp != 0;
// 1. Spatial Triangle Indexing into layers // 1. Spatial Triangle Indexing into layers
auto t_idx_0 = std::chrono::high_resolution_clock::now(); auto t_idx_0 = std::chrono::high_resolution_clock::now();
@ -532,14 +611,19 @@ int cpu_offset_slice_mesh_and_offset(
auto t_idx_1 = std::chrono::high_resolution_clock::now(); auto t_idx_1 = std::chrono::high_resolution_clock::now();
double t_idx_ms = std::chrono::duration<double, std::milli>(t_idx_1 - t_idx_0).count(); double t_idx_ms = std::chrono::duration<double, std::milli>(t_idx_1 - t_idx_0).count();
// 2. Parallel Layer Slicing, Parity Scan, Exact Segment SDF, Thresholding, Vectorization // 2. Parallel Layer Slicing, Parity Scan, Exact SDF / EDT, Thresholding, Vectorization
GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult)); GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult));
memset(res_array, 0, num_layers * sizeof(GpuLayerResult)); memset(res_array, 0, num_layers * sizeof(GpuLayerResult));
unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); const int TILE_SZ = 16;
std::vector<std::thread> workers; int num_tiles_x = (cols + TILE_SZ - 1) / TILE_SZ;
int num_tiles_y = (rows + TILE_SZ - 1) / TILE_SZ;
int total_tiles = num_tiles_x * num_tiles_y;
float max_offset = resolved_offsets.empty() ? 2.0f : resolved_offsets.back();
float SEARCH_R = std::max(1.5f, max_offset + 2.0f * pitch);
auto worker_fn = [&](size_t start_layer, size_t end_layer) { #pragma omp parallel
{
int stride_w = cols + 1; int stride_w = cols + 1;
size_t max_edges = 2 * (size_t)(rows + 1) * stride_w; size_t max_edges = 2 * (size_t)(rows + 1) * stride_w;
std::vector<int> next_edge_buf(max_edges, -1); std::vector<int> next_edge_buf(max_edges, -1);
@ -548,19 +632,40 @@ int cpu_offset_slice_mesh_and_offset(
std::vector<uint8_t> visited_buf(max_edges, 0); std::vector<uint8_t> visited_buf(max_edges, 0);
std::vector<unsigned int> parity_words((size_t)rows * row_stride_words, 0); std::vector<unsigned int> parity_words((size_t)rows * row_stride_words, 0);
std::vector<unsigned int> bit_words((size_t)rows * row_stride_words, 0);
std::vector<uint8_t> layer_mask(layer_pixels, 0); std::vector<uint8_t> layer_mask(layer_pixels, 0);
std::vector<float> layer_sdf(layer_pixels);
struct CpuSegment2D { std::vector<float> layer_sdf;
float x0, y0, x1, y1;
};
std::vector<CpuSegment2D> layer_segs; std::vector<CpuSegment2D> layer_segs;
layer_segs.reserve(256); std::vector<int> tile_head;
std::vector<TileSegNode> seg_nodes;
int candidate_buf[1024];
for (size_t l = start_layer; l < end_layer; ++l) { std::vector<float> dist_sq_ext;
std::vector<float> g_col_buf;
std::vector<float> row_f, row_d;
std::vector<int> v_buf;
std::vector<float> z_buf;
if (subpixel_lerp) {
layer_sdf.resize(layer_pixels);
layer_segs.reserve(2048);
tile_head.assign(total_tiles, -1);
seg_nodes.reserve(16384);
} else {
dist_sq_ext.resize(layer_pixels);
g_col_buf.resize(layer_pixels);
row_f.resize(cols);
row_d.resize(cols);
v_buf.resize(cols + 1);
z_buf.resize(cols + 2);
}
#pragma omp for schedule(dynamic, 2)
for (int l = 0; l < num_layers; ++l) {
float z0 = z_first + l * layer_height; float z0 = z_first + l * layer_height;
std::fill(parity_words.begin(), parity_words.end(), 0u); std::fill(parity_words.begin(), parity_words.end(), 0u);
layer_segs.clear(); if (subpixel_lerp) layer_segs.clear();
// Slicing & Parity Rasterization for Layer l // Slicing & Parity Rasterization for Layer l
const auto& tri_indices = layer_tri_indices[l]; const auto& tri_indices = layer_tri_indices[l];
@ -590,12 +695,25 @@ int cpu_offset_slice_mesh_and_offset(
if (count != 2) continue; if (count != 2) continue;
layer_segs.push_back({ pts_x[0], pts_y[0], pts_x[1], pts_y[1] }); float x0 = pts_x[0], y0 = pts_y[0], x1 = pts_x[1], y1 = pts_y[1];
if (subpixel_lerp) {
float vx = x1 - x0;
float vy = y1 - y0;
float len_sq = vx * vx + vy * vy;
float inv_len_sq = (len_sq > 1e-10f) ? (1.0f / len_sq) : 0.0f;
layer_segs.push_back({
x0, y0, x1, y1,
vx, vy,
inv_len_sq,
std::min(x0, x1), std::max(x0, x1),
std::min(y0, y1), std::max(y0, y1)
});
}
float gx0 = (pts_x[0] - bounds_min_x) / pitch; float gx0 = (x0 - bounds_min_x) * inv_pitch;
float gy0 = (pts_y[0] - bounds_min_y) / pitch; float gy0 = (y0 - bounds_min_y) * inv_pitch;
float gx1 = (pts_x[1] - bounds_min_x) / pitch; float gx1 = (x1 - bounds_min_x) * inv_pitch;
float gy1 = (pts_y[1] - bounds_min_y) / pitch; float gy1 = (y1 - bounds_min_y) * inv_pitch;
if (gy0 == gy1) continue; if (gy0 == gy1) continue;
@ -627,6 +745,7 @@ int cpu_offset_slice_mesh_and_offset(
} }
// Scan Parity Words into Binary Mask // Scan Parity Words into Binary Mask
bool has_inside = false;
for (int y = 0; y < rows; ++y) { for (int y = 0; y < rows; ++y) {
const unsigned int* row_p = parity_words.data() + (size_t)y * row_stride_words; const unsigned int* row_p = parity_words.data() + (size_t)y * row_stride_words;
uint8_t* row_m = layer_mask.data() + (size_t)y * cols; uint8_t* row_m = layer_mask.data() + (size_t)y * cols;
@ -637,39 +756,50 @@ int cpu_offset_slice_mesh_and_offset(
for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) { for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) {
inside ^= (uint8_t)((word >> b) & 1u); inside ^= (uint8_t)((word >> b) & 1u);
row_m[x_out] = inside; row_m[x_out] = inside;
if (inside) has_inside = true;
} }
} }
} }
// Fast 2D Tile Spatial Binning for analytical segment distance on CPU res_array[l].z_height = z0;
const int TILE_SZ = 16; res_array[l].num_perimeters = P;
int num_tiles_x = (cols + TILE_SZ - 1) / TILE_SZ; res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
int num_tiles_y = (rows + TILE_SZ - 1) / TILE_SZ;
std::vector<std::vector<int>> tile_segs(num_tiles_x * num_tiles_y); if (!has_inside || (subpixel_lerp && layer_segs.empty())) {
for (int o = 0; o < P; ++o) {
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
res_array[l].perimeters[o].num_contours = 0;
res_array[l].perimeters[o].contours = nullptr;
}
res_array[l].infill.offset_distance_mm = resolved_offsets[P];
res_array[l].infill.width = cols;
res_array[l].infill.height = rows;
res_array[l].infill.num_contours = 0;
res_array[l].infill.contours = nullptr;
res_array[l].infill.mask = nullptr;
continue;
}
if (subpixel_lerp) {
// Exact analytical segment distance field with flat linked-list tile binning
std::fill(tile_head.begin(), tile_head.end(), -1);
seg_nodes.clear();
const float SEARCH_R = 4.0f;
float inv_pitch = 1.0f / pitch;
int num_segs = (int)layer_segs.size(); int num_segs = (int)layer_segs.size();
for (int s = 0; s < num_segs; ++s) { for (int s = 0; s < num_segs; ++s) {
float sx0 = layer_segs[s].x0; const auto& seg = layer_segs[s];
float sy0 = layer_segs[s].y0; int tx0 = std::max(0, (int)((seg.min_x - SEARCH_R - bounds_min_x) * inv_pitch) / TILE_SZ);
float sx1 = layer_segs[s].x1; int tx1 = std::min(num_tiles_x - 1, (int)((seg.max_x + SEARCH_R - bounds_min_x) * inv_pitch) / TILE_SZ);
float sy1 = layer_segs[s].y1; int ty0 = std::max(0, (int)((seg.min_y - SEARCH_R - bounds_min_y) * inv_pitch) / TILE_SZ);
int ty1 = std::min(num_tiles_y - 1, (int)((seg.max_y + SEARCH_R - bounds_min_y) * inv_pitch) / TILE_SZ);
float min_x = std::min(sx0, sx1) - SEARCH_R;
float max_x = std::max(sx0, sx1) + SEARCH_R;
float min_y = std::min(sy0, sy1) - SEARCH_R;
float max_y = std::max(sy0, sy1) + SEARCH_R;
int tx0 = std::max(0, (int)((min_x - bounds_min_x) * inv_pitch) / TILE_SZ);
int tx1 = std::min(num_tiles_x - 1, (int)((max_x - bounds_min_x) * inv_pitch) / TILE_SZ);
int ty0 = std::max(0, (int)((min_y - bounds_min_y) * inv_pitch) / TILE_SZ);
int ty1 = std::min(num_tiles_y - 1, (int)((max_y - bounds_min_y) * inv_pitch) / TILE_SZ);
for (int ty = ty0; ty <= ty1; ++ty) { for (int ty = ty0; ty <= ty1; ++ty) {
int row_tile_idx = ty * num_tiles_x;
for (int tx = tx0; tx <= tx1; ++tx) { for (int tx = tx0; tx <= tx1; ++tx) {
tile_segs[ty * num_tiles_x + tx].push_back(s); int t_idx = row_tile_idx + tx;
int node = (int)seg_nodes.size();
seg_nodes.push_back({ s, tile_head[t_idx] });
tile_head[t_idx] = node;
} }
} }
} }
@ -677,12 +807,15 @@ int cpu_offset_slice_mesh_and_offset(
for (int ty = 0; ty < num_tiles_y; ++ty) { for (int ty = 0; ty < num_tiles_y; ++ty) {
int y_start = ty * TILE_SZ; int y_start = ty * TILE_SZ;
int y_end = std::min(rows, y_start + TILE_SZ); int y_end = std::min(rows, y_start + TILE_SZ);
int row_tile_idx = ty * num_tiles_x;
for (int tx = 0; tx < num_tiles_x; ++tx) { for (int tx = 0; tx < num_tiles_x; ++tx) {
int t_idx = row_tile_idx + tx;
int x_start = tx * TILE_SZ; int x_start = tx * TILE_SZ;
int x_end = std::min(cols, x_start + TILE_SZ); int x_end = std::min(cols, x_start + TILE_SZ);
const auto& candidates = tile_segs[ty * num_tiles_x + tx];
if (candidates.empty()) { int head = tile_head[t_idx];
if (head == -1) {
for (int y = y_start; y < y_end; ++y) { for (int y = y_start; y < y_end; ++y) {
for (int x = x_start; x < x_end; ++x) { for (int x = x_start; x < x_end; ++x) {
uint8_t m = layer_mask[(size_t)y * cols + x]; uint8_t m = layer_mask[(size_t)y * cols + x];
@ -692,27 +825,35 @@ int cpu_offset_slice_mesh_and_offset(
continue; continue;
} }
int num_cand = 0;
int cur = head;
while (cur != -1 && num_cand < 1024) {
candidate_buf[num_cand++] = seg_nodes[cur].seg_idx;
cur = seg_nodes[cur].next;
}
for (int y = y_start; y < y_end; ++y) { for (int y = y_start; y < y_end; ++y) {
float py = bounds_min_y + (float)y * pitch; float py = bounds_min_y + (float)y * pitch;
for (int x = x_start; x < x_end; ++x) { for (int x = x_start; x < x_end; ++x) {
float px = bounds_min_x + (float)x * pitch; float px = bounds_min_x + (float)x * pitch;
float min_d2 = 1e20f; float min_d2 = 1e10f;
for (int s_idx : candidates) {
float sx0 = layer_segs[s_idx].x0; for (int k = 0; k < num_cand; ++k) {
float sy0 = layer_segs[s_idx].y0; int s_idx = candidate_buf[k];
float sx1 = layer_segs[s_idx].x1; const auto& seg = layer_segs[s_idx];
float sy1 = layer_segs[s_idx].y1; float wx = px - seg.x0;
float dx = sx1 - sx0; float wy = py - seg.y0;
float dy = sy1 - sy0; float t = (wx * seg.vx + wy * seg.vy) * seg.inv_len_sq;
float l2 = dx * dx + dy * dy; t = (t < 0.0f) ? 0.0f : ((t > 1.0f) ? 1.0f : t);
float t = (l2 > 1e-12f) ? ((px - sx0) * dx + (py - sy0) * dy) / l2 : 0.0f; float qx = seg.x0 + t * seg.vx;
t = std::max(0.0f, std::min(1.0f, t)); float qy = seg.y0 + t * seg.vy;
float qx = sx0 + t * dx; float dx = px - qx;
float qy = sy0 + t * dy; float dy = py - qy;
float d2 = (px - qx) * (px - qx) + (py - qy) * (py - qy); float d2 = dx * dx + dy * dy;
if (d2 < min_d2) min_d2 = d2; if (d2 < min_d2) min_d2 = d2;
} }
float d = (min_d2 < 1e19f) ? sqrtf(min_d2) : SEARCH_R;
float d = (min_d2 < 1e9f) ? std::sqrt(min_d2) : SEARCH_R;
uint8_t m = layer_mask[(size_t)y * cols + x]; uint8_t m = layer_mask[(size_t)y * cols + x];
layer_sdf[(size_t)y * cols + x] = (m > 0) ? d : -d; layer_sdf[(size_t)y * cols + x] = (m > 0) ? d : -d;
} }
@ -720,17 +861,12 @@ int cpu_offset_slice_mesh_and_offset(
} }
} }
res_array[l].z_height = z0; // Perimeters with continuous SDF
res_array[l].num_perimeters = P;
res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
// Perimeters with continuous SDF and sub-pixel LERP
for (int o = 0; o < P; ++o) { for (int o = 0; o < P; ++o) {
float iso_thresh = resolved_offsets[o]; float iso_thresh = resolved_offsets[o];
std::vector<GpuContour> contours; std::vector<GpuContour> contours;
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch, vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
iso_thresh, config->simplify_tolerance_mm, config->subpixel_lerp, iso_thresh, config->simplify_tolerance_mm, 1,
next_edge_buf, active_edges_buf, visited_buf, contours); next_edge_buf, active_edges_buf, visited_buf, contours);
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o]; res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
@ -744,13 +880,13 @@ int cpu_offset_slice_mesh_and_offset(
} }
// Infill pass // Infill pass
float infill_iso_thresh = resolved_offsets[P]; float infill_thresh = resolved_offsets[P];
std::vector<GpuContour> infill_contours; std::vector<GpuContour> infill_contours;
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch, vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
infill_iso_thresh, config->simplify_tolerance_mm, config->subpixel_lerp, infill_thresh, config->simplify_tolerance_mm, 1,
next_edge_buf, active_edges_buf, visited_buf, infill_contours); next_edge_buf, active_edges_buf, visited_buf, infill_contours);
res_array[l].infill.offset_distance_mm = infill_iso_thresh; res_array[l].infill.offset_distance_mm = infill_thresh;
res_array[l].infill.width = cols; res_array[l].infill.width = cols;
res_array[l].infill.height = rows; res_array[l].infill.height = rows;
res_array[l].infill.num_contours = infill_contours.size(); res_array[l].infill.num_contours = infill_contours.size();
@ -764,22 +900,109 @@ int cpu_offset_slice_mesh_and_offset(
if (config->export_infill_mask) { if (config->export_infill_mask) {
uint8_t* infill_mask = (uint8_t*)malloc(layer_pixels); uint8_t* infill_mask = (uint8_t*)malloc(layer_pixels);
for (size_t i = 0; i < layer_pixels; ++i) { for (size_t i = 0; i < layer_pixels; ++i) {
infill_mask[i] = (layer_sdf[i] >= infill_iso_thresh) ? 1 : 0; infill_mask[i] = (layer_sdf[i] >= infill_thresh) ? 1 : 0;
}
res_array[l].infill.mask = infill_mask;
} else {
res_array[l].infill.mask = nullptr;
}
} else {
// Discrete Marching Squares (No LERP) via Exact 2D EDT & Bitpacking
edt_2d_layer(layer_mask.data(), cols, rows, dist_sq_ext.data(),
g_col_buf, row_f, row_d, v_buf, z_buf, false);
for (int o = 0; o < P; ++o) {
float thresh = resolved_offsets[o];
float thresh_sq_px = (thresh / pitch) * (thresh / pitch);
for (int y = 0; y < rows; ++y) {
const uint8_t* row_m = layer_mask.data() + (size_t)y * cols;
const float* row_d2 = dist_sq_ext.data() + (size_t)y * cols;
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
for (int w = 0; w < row_stride_words; ++w) {
unsigned int packed = 0;
int x_base = w * 32;
for (int b = 0; b < 32; ++b) {
int x = x_base + b;
if (x < cols) {
if (row_m[x] > 0 && row_d2[x] >= thresh_sq_px) {
packed |= (1u << b);
}
}
}
row_w[w] = packed;
}
}
std::vector<GpuContour> contours;
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
bounds_min_x, bounds_min_y, pitch,
next_edge_buf, active_edges_buf, visited_buf, contours);
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
res_array[l].perimeters[o].num_contours = contours.size();
if (!contours.empty()) {
res_array[l].perimeters[o].contours = (GpuContour*)malloc(contours.size() * sizeof(GpuContour));
memcpy(res_array[l].perimeters[o].contours, contours.data(), contours.size() * sizeof(GpuContour));
} else {
res_array[l].perimeters[o].contours = nullptr;
}
}
// Infill pass (index P)
float infill_thresh = resolved_offsets[P];
float infill_thresh_sq_px = (infill_thresh / pitch) * (infill_thresh / pitch);
for (int y = 0; y < rows; ++y) {
const uint8_t* row_m = layer_mask.data() + (size_t)y * cols;
const float* row_d2 = dist_sq_ext.data() + (size_t)y * cols;
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
for (int w = 0; w < row_stride_words; ++w) {
unsigned int packed = 0;
int x_base = w * 32;
for (int b = 0; b < 32; ++b) {
int x = x_base + b;
if (x < cols) {
if (row_m[x] > 0 && row_d2[x] >= infill_thresh_sq_px) {
packed |= (1u << b);
}
}
}
row_w[w] = packed;
}
}
std::vector<GpuContour> infill_contours;
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
bounds_min_x, bounds_min_y, pitch,
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
res_array[l].infill.offset_distance_mm = infill_thresh;
res_array[l].infill.width = cols;
res_array[l].infill.height = rows;
res_array[l].infill.num_contours = infill_contours.size();
if (!infill_contours.empty()) {
res_array[l].infill.contours = (GpuContour*)malloc(infill_contours.size() * sizeof(GpuContour));
memcpy(res_array[l].infill.contours, infill_contours.data(), infill_contours.size() * sizeof(GpuContour));
} else {
res_array[l].infill.contours = nullptr;
}
if (config->export_infill_mask) {
uint8_t* infill_mask = (uint8_t*)malloc(layer_pixels);
for (size_t i = 0; i < layer_pixels; ++i) {
infill_mask[i] = (layer_mask[i] > 0 && dist_sq_ext[i] >= infill_thresh_sq_px) ? 1 : 0;
} }
res_array[l].infill.mask = infill_mask; res_array[l].infill.mask = infill_mask;
} else { } else {
res_array[l].infill.mask = nullptr; res_array[l].infill.mask = nullptr;
} }
} }
};
size_t chunk = (num_layers + num_threads - 1) / num_threads;
for (unsigned int t = 0; t < num_threads; ++t) {
size_t s = t * chunk;
size_t e = std::min(s + chunk, (size_t)num_layers);
if (s < e) workers.emplace_back(worker_fn, s, e);
} }
for (auto& w : workers) w.join(); }
auto t_total_end = std::chrono::high_resolution_clock::now(); auto t_total_end = std::chrono::high_resolution_clock::now();
double t_total_ms = std::chrono::duration<double, std::milli>(t_total_end - t_total_start).count(); double t_total_ms = std::chrono::duration<double, std::milli>(t_total_end - t_total_start).count();

View file

@ -7,6 +7,7 @@
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <cstdint> #include <cstdint>
#include <algorithm>
inline int get_h_edge_id(int x, int y, int stride_w) { inline int get_h_edge_id(int x, int y, int stride_w) {
return 2 * (y * stride_w + x) + 0; return 2 * (y * stride_w + x) + 0;
@ -82,13 +83,16 @@ inline GpuPoint2D get_sdf_edge_point(
static inline void rdp_simplify_recursive( static inline void rdp_simplify_recursive(
const std::vector<GpuPoint2D>& pts, const std::vector<GpuPoint2D>& pts,
size_t i, size_t j, size_t i, size_t j,
size_t n,
float eps_sq, float eps_sq,
std::vector<bool>& keep std::vector<uint8_t>& keep
) { ) {
if (j <= i + 1) return; if (j <= i + 1) return;
float p1x = pts[i].x, p1y = pts[i].y; const GpuPoint2D& p1 = pts[i % n];
float p2x = pts[j].x, p2y = pts[j].y; const GpuPoint2D& p2 = pts[j % n];
float p1x = p1.x, p1y = p1.y;
float p2x = p2.x, p2y = p2.y;
float dx = p2x - p1x; float dx = p2x - p1x;
float dy = p2y - p1y; float dy = p2y - p1y;
float len_sq = dx * dx + dy * dy; float len_sq = dx * dx + dy * dy;
@ -97,7 +101,8 @@ static inline void rdp_simplify_recursive(
size_t max_idx = i; size_t max_idx = i;
for (size_t k = i + 1; k < j; ++k) { for (size_t k = i + 1; k < j; ++k) {
float px = pts[k].x, py = pts[k].y; const GpuPoint2D& pk = pts[k % n];
float px = pk.x, py = pk.y;
float dist_sq = 0.0f; float dist_sq = 0.0f;
if (len_sq < 1e-10f) { if (len_sq < 1e-10f) {
@ -116,9 +121,9 @@ static inline void rdp_simplify_recursive(
} }
if (max_dist_sq > eps_sq) { if (max_dist_sq > eps_sq) {
keep[max_idx] = true; keep[max_idx] = 1;
rdp_simplify_recursive(pts, i, max_idx, eps_sq, keep); rdp_simplify_recursive(pts, i, max_idx, n, eps_sq, keep);
rdp_simplify_recursive(pts, max_idx, j, eps_sq, keep); rdp_simplify_recursive(pts, max_idx, j, n, eps_sq, keep);
} }
} }
@ -144,16 +149,13 @@ static inline std::vector<GpuPoint2D> simplify_contour_rdp(
} }
} }
std::vector<GpuPoint2D> closed_pts = loop_pts; std::vector<uint8_t> keep(n + 1, 0);
closed_pts.push_back(loop_pts[0]); keep[0] = 1;
keep[split_idx] = 1;
keep[n] = 1;
std::vector<bool> keep(n + 1, false); rdp_simplify_recursive(loop_pts, 0, split_idx, n, eps_sq, keep);
keep[0] = true; rdp_simplify_recursive(loop_pts, split_idx, n, n, eps_sq, keep);
keep[split_idx] = true;
keep[n] = true;
rdp_simplify_recursive(closed_pts, 0, split_idx, eps_sq, keep);
rdp_simplify_recursive(closed_pts, split_idx, n, eps_sq, keep);
std::vector<GpuPoint2D> simplified; std::vector<GpuPoint2D> simplified;
simplified.reserve(n); simplified.reserve(n);
@ -164,9 +166,9 @@ static inline std::vector<GpuPoint2D> simplify_contour_rdp(
} }
// C++ Seam Alignment: positions starting vertex at sharpest concave corner (or sharpest angle) // C++ Seam Alignment: positions starting vertex at sharpest concave corner (or sharpest angle)
static inline std::vector<GpuPoint2D> align_contour_seam_sharpest_cpp(const std::vector<GpuPoint2D>& loop) { static inline void align_contour_seam_sharpest_cpp_inplace(std::vector<GpuPoint2D>& loop) {
size_t M = loop.size(); size_t M = loop.size();
if (M < 3) return loop; if (M < 3) return;
// 1. Calculate signed area // 1. Calculate signed area
double signed_area = 0.0; double signed_area = 0.0;
@ -175,7 +177,7 @@ static inline std::vector<GpuPoint2D> align_contour_seam_sharpest_cpp(const std:
signed_area += (double)loop[i].x * (double)loop[next_i].y - (double)loop[next_i].x * (double)loop[i].y; signed_area += (double)loop[i].x * (double)loop[next_i].y - (double)loop[next_i].x * (double)loop[i].y;
} }
signed_area *= 0.5; signed_area *= 0.5;
if (std::abs(signed_area) < 1e-9) return loop; if (std::abs(signed_area) < 1e-9) return;
// 2. Find sharpest concave angle or fallback to sharpest angle // 2. Find sharpest concave angle or fallback to sharpest angle
size_t best_concave_idx = 0; size_t best_concave_idx = 0;
@ -218,12 +220,14 @@ static inline std::vector<GpuPoint2D> align_contour_seam_sharpest_cpp(const std:
} }
size_t best_idx = found_concave ? best_concave_idx : best_any_idx; size_t best_idx = found_concave ? best_concave_idx : best_any_idx;
if (best_idx == 0) return loop; if (best_idx != 0) {
std::rotate(loop.begin(), loop.begin() + best_idx, loop.end());
}
}
std::vector<GpuPoint2D> aligned; static inline std::vector<GpuPoint2D> align_contour_seam_sharpest_cpp(const std::vector<GpuPoint2D>& loop) {
aligned.reserve(M); std::vector<GpuPoint2D> aligned = loop;
for (size_t i = best_idx; i < M; ++i) aligned.push_back(loop[i]); align_contour_seam_sharpest_cpp_inplace(aligned);
for (size_t i = 0; i < best_idx; ++i) aligned.push_back(loop[i]);
return aligned; return aligned;
} }