1184 lines
42 KiB
Text
1184 lines
42 KiB
Text
#include "gpu_sdf.h"
|
|
#include "gpu_offset.h"
|
|
#include "cpu_offset.h"
|
|
#include "vectorizer.h"
|
|
#include <cuda_runtime.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <thread>
|
|
#include <map>
|
|
#include <set>
|
|
|
|
#define CUDA_CHECK(call) do { \
|
|
cudaError_t err = call; \
|
|
if (err != cudaSuccess) { \
|
|
std::cerr << "[CUDA Error] " << cudaGetErrorString(err) \
|
|
<< " at " << __FILE__ << ":" << __LINE__ << std::endl; \
|
|
return nullptr; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CUDA_CHECK_INT(call) do { \
|
|
cudaError_t err = call; \
|
|
if (err != cudaSuccess) { \
|
|
std::cerr << "[CUDA Error] " << cudaGetErrorString(err) \
|
|
<< " at " << __FILE__ << ":" << __LINE__ << std::endl; \
|
|
return GPU_OFFSET_ERR_GENERIC; \
|
|
} \
|
|
} while (0)
|
|
|
|
// Seed packing helpers
|
|
__device__ __inline__ int pack_seed_sdf(short x, short y) {
|
|
return ((int)(unsigned short)y << 16) | (int)(unsigned short)x;
|
|
}
|
|
__device__ __inline__ short get_seed_x_sdf(int packed) {
|
|
return (short)(packed & 0xFFFF);
|
|
}
|
|
__device__ __inline__ short get_seed_y_sdf(int packed) {
|
|
return (short)((packed >> 16) & 0xFFFF);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Internal Implementation of GpuSdfStack
|
|
// -----------------------------------------------------------------------------
|
|
struct GpuSdfStack_T {
|
|
GpuSdfMetadata meta;
|
|
int32_t* d_z_um; // GPU pointer to array of layer heights [num_layers] in micrometers
|
|
float* d_sdf; // GPU pointer to 3D continuous distance field [num_layers * height * width] in mm
|
|
cudaStream_t stream;
|
|
bool owns_stream;
|
|
};
|
|
|
|
struct LayerSamplingInfo {
|
|
int l0_a;
|
|
int l1_a;
|
|
float t_a; // < 0 if not present in A
|
|
int l0_b;
|
|
int l1_b;
|
|
float t_b; // < 0 if not present in B
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// CUDA Kernels for JFA Full Signed Distance Generation
|
|
// -----------------------------------------------------------------------------
|
|
__global__ void jfa_init_interior_exterior_kernel(
|
|
const uint8_t* __restrict__ masks,
|
|
int* __restrict__ seeds_ext,
|
|
int* __restrict__ seeds_int,
|
|
int width, int height, int layers
|
|
) {
|
|
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
|
int z = blockIdx.z;
|
|
if (x >= width || y >= height || z >= layers) return;
|
|
|
|
size_t idx = ((size_t)z * height + y) * width + x;
|
|
uint8_t m = masks[idx];
|
|
|
|
// seeds_ext: background pixels (0) are seeds to propagate into foreground (1)
|
|
if (m == 0) {
|
|
seeds_ext[idx] = pack_seed_sdf((short)x, (short)y);
|
|
} else {
|
|
seeds_ext[idx] = -1;
|
|
}
|
|
|
|
// seeds_int: foreground pixels (1) are seeds to propagate into background (0)
|
|
if (m > 0) {
|
|
seeds_int[idx] = pack_seed_sdf((short)x, (short)y);
|
|
} else {
|
|
seeds_int[idx] = -1;
|
|
}
|
|
}
|
|
|
|
__global__ void jfa_step_dual_kernel(
|
|
const int* __restrict__ in_seeds_ext,
|
|
int* __restrict__ out_seeds_ext,
|
|
const int* __restrict__ in_seeds_int,
|
|
int* __restrict__ out_seeds_int,
|
|
int width, int height, int layers,
|
|
int step
|
|
) {
|
|
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
|
int z = blockIdx.z;
|
|
if (x >= width || y >= height || z >= layers) return;
|
|
|
|
size_t layer_offset = (size_t)z * height * width;
|
|
size_t idx = layer_offset + y * width + x;
|
|
|
|
// 1. Exterior seeds
|
|
int best_seed_ext = in_seeds_ext[idx];
|
|
int best_dist2_ext = 0x7FFFFFFF;
|
|
if (best_seed_ext != -1) {
|
|
int dx = (int)get_seed_x_sdf(best_seed_ext) - x;
|
|
int dy = (int)get_seed_y_sdf(best_seed_ext) - y;
|
|
best_dist2_ext = dx * dx + dy * dy;
|
|
}
|
|
|
|
// 2. Interior seeds
|
|
int best_seed_int = in_seeds_int[idx];
|
|
int best_dist2_int = 0x7FFFFFFF;
|
|
if (best_seed_int != -1) {
|
|
int dx = (int)get_seed_x_sdf(best_seed_int) - x;
|
|
int dy = (int)get_seed_y_sdf(best_seed_int) - y;
|
|
best_dist2_int = dx * dx + dy * dy;
|
|
}
|
|
|
|
#pragma unroll
|
|
for (int dy = -1; dy <= 1; ++dy) {
|
|
#pragma unroll
|
|
for (int dx = -1; dx <= 1; ++dx) {
|
|
if (dx == 0 && dy == 0) continue;
|
|
int nx = x + dx * step;
|
|
int ny = y + dy * step;
|
|
if ((unsigned int)nx < (unsigned int)width && (unsigned int)ny < (unsigned int)height) {
|
|
size_t n_idx = layer_offset + ny * width + nx;
|
|
|
|
int s_ext = in_seeds_ext[n_idx];
|
|
if (s_ext != -1) {
|
|
int dist_x = (int)get_seed_x_sdf(s_ext) - x;
|
|
int dist_y = (int)get_seed_y_sdf(s_ext) - y;
|
|
int dist2 = dist_x * dist_x + dist_y * dist_y;
|
|
if (dist2 < best_dist2_ext) {
|
|
best_dist2_ext = dist2;
|
|
best_seed_ext = s_ext;
|
|
}
|
|
}
|
|
|
|
int s_int = in_seeds_int[n_idx];
|
|
if (s_int != -1) {
|
|
int dist_x = (int)get_seed_x_sdf(s_int) - x;
|
|
int dist_y = (int)get_seed_y_sdf(s_int) - y;
|
|
int dist2 = dist_x * dist_x + dist_y * dist_y;
|
|
if (dist2 < best_dist2_int) {
|
|
best_dist2_int = dist2;
|
|
best_seed_int = s_int;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out_seeds_ext[idx] = best_seed_ext;
|
|
out_seeds_int[idx] = best_seed_int;
|
|
}
|
|
|
|
__global__ void fuse_signed_sdf_kernel(
|
|
const uint8_t* __restrict__ masks,
|
|
const int* __restrict__ seeds_ext,
|
|
const int* __restrict__ seeds_int,
|
|
float* __restrict__ out_sdf,
|
|
int width, int height, int layers,
|
|
float pitch_mm
|
|
) {
|
|
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
|
int z = blockIdx.z;
|
|
if (x >= width || y >= height || z >= layers) return;
|
|
|
|
size_t idx = ((size_t)z * height + y) * width + x;
|
|
uint8_t m = masks[idx];
|
|
|
|
if (m > 0) {
|
|
int s = seeds_ext[idx];
|
|
if (s != -1) {
|
|
float dx = (float)(get_seed_x_sdf(s) - x);
|
|
float dy = (float)(get_seed_y_sdf(s) - y);
|
|
out_sdf[idx] = sqrtf(dx * dx + dy * dy) * pitch_mm;
|
|
} else {
|
|
out_sdf[idx] = 1e5f;
|
|
}
|
|
} else {
|
|
int s = seeds_int[idx];
|
|
if (s != -1) {
|
|
float dx = (float)(get_seed_x_sdf(s) - x);
|
|
float dy = (float)(get_seed_y_sdf(s) - y);
|
|
out_sdf[idx] = -sqrtf(dx * dx + dy * dy) * pitch_mm;
|
|
} else {
|
|
out_sdf[idx] = -1e5f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// CSG Mathematical Device Functions
|
|
// -----------------------------------------------------------------------------
|
|
__device__ __inline__ float csg_eval_op(float a, float b, int op, float blend_k) {
|
|
switch (op) {
|
|
case GPU_SDF_OP_UNION:
|
|
return fmaxf(a, b);
|
|
case GPU_SDF_OP_INTERSECTION:
|
|
return fminf(a, b);
|
|
case GPU_SDF_OP_DIFFERENCE:
|
|
return fminf(a, -b);
|
|
case GPU_SDF_OP_SMOOTH_UNION: {
|
|
if (blend_k <= 1e-6f) return fmaxf(a, b);
|
|
float h = fmaxf(blend_k - fabsf(a - b), 0.0f) / blend_k;
|
|
return fmaxf(a, b) + h * h * blend_k * 0.25f;
|
|
}
|
|
case GPU_SDF_OP_SMOOTH_INTERSECT: {
|
|
if (blend_k <= 1e-6f) return fminf(a, b);
|
|
float h = fmaxf(blend_k - fabsf(a - b), 0.0f) / blend_k;
|
|
return fminf(a, b) - h * h * blend_k * 0.25f;
|
|
}
|
|
case GPU_SDF_OP_SMOOTH_DIFF: {
|
|
float neg_b = -b;
|
|
if (blend_k <= 1e-6f) return fminf(a, neg_b);
|
|
float h = fmaxf(blend_k - fabsf(a - neg_b), 0.0f) / blend_k;
|
|
return fminf(a, neg_b) - h * h * blend_k * 0.25f;
|
|
}
|
|
default:
|
|
return a;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Identical Grid Fast CSG Kernel
|
|
// -----------------------------------------------------------------------------
|
|
__global__ void gpu_sdf_csg_identical_kernel(
|
|
const float* __restrict__ sdf_a,
|
|
const float* __restrict__ sdf_b,
|
|
float* __restrict__ sdf_out,
|
|
size_t total_elements,
|
|
int op,
|
|
float blend_k
|
|
) {
|
|
size_t idx = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx >= total_elements) return;
|
|
|
|
float a = sdf_a[idx];
|
|
float b = sdf_b[idx];
|
|
sdf_out[idx] = csg_eval_op(a, b, op, blend_k);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Harmonized Resampling + CSG Kernel
|
|
// -----------------------------------------------------------------------------
|
|
__device__ __inline__ float sample_stack_sdf(
|
|
const float* __restrict__ sdf,
|
|
int w, int h,
|
|
int x, int y,
|
|
int l0, int l1, float t
|
|
) {
|
|
if (t < -0.5f) return -1e5f; // Not present in stack (outside)
|
|
if (x < 0 || x >= w || y < 0 || y >= h) return -1e5f;
|
|
|
|
size_t layer_stride = (size_t)w * h;
|
|
if (l0 == l1 || t <= 1e-6f) {
|
|
return sdf[(size_t)l0 * layer_stride + y * w + x];
|
|
} else {
|
|
float v0 = sdf[(size_t)l0 * layer_stride + y * w + x];
|
|
float v1 = sdf[(size_t)l1 * layer_stride + y * w + x];
|
|
return (1.0f - t) * v0 + t * v1;
|
|
}
|
|
}
|
|
|
|
__global__ void gpu_sdf_csg_resample_kernel(
|
|
const float* __restrict__ sdf_a,
|
|
GpuSdfMetadata meta_a,
|
|
const float* __restrict__ sdf_b,
|
|
GpuSdfMetadata meta_b,
|
|
float* __restrict__ sdf_out,
|
|
GpuSdfMetadata meta_out,
|
|
const LayerSamplingInfo* __restrict__ layer_map,
|
|
int op,
|
|
float blend_k
|
|
) {
|
|
int x_out = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y_out = blockIdx.y * blockDim.y + threadIdx.y;
|
|
int l_out = blockIdx.z;
|
|
|
|
if (x_out >= meta_out.width || y_out >= meta_out.height || l_out >= meta_out.num_layers) return;
|
|
|
|
LayerSamplingInfo info = layer_map[l_out];
|
|
|
|
// Compute world X, Y in micrometers
|
|
int32_t world_x_um = meta_out.min_x_um + x_out * meta_out.pitch_um;
|
|
int32_t world_y_um = meta_out.min_y_um + y_out * meta_out.pitch_um;
|
|
|
|
// Sample from Stack A
|
|
int x_a = (world_x_um - meta_a.min_x_um) / meta_a.pitch_um;
|
|
int y_a = (world_y_um - meta_a.min_y_um) / meta_a.pitch_um;
|
|
float val_a = sample_stack_sdf(sdf_a, meta_a.width, meta_a.height, x_a, y_a, info.l0_a, info.l1_a, info.t_a);
|
|
|
|
// Sample from Stack B
|
|
int x_b = (world_x_um - meta_b.min_x_um) / meta_b.pitch_um;
|
|
int y_b = (world_y_um - meta_b.min_y_um) / meta_b.pitch_um;
|
|
float val_b = sample_stack_sdf(sdf_b, meta_b.width, meta_b.height, x_b, y_b, info.l0_b, info.l1_b, info.t_b);
|
|
|
|
float res = csg_eval_op(val_a, val_b, op, blend_k);
|
|
|
|
size_t out_idx = ((size_t)l_out * meta_out.height + y_out) * meta_out.width + x_out;
|
|
sdf_out[out_idx] = res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Unary GPU Operations (Offset, Shell, Infill)
|
|
// -----------------------------------------------------------------------------
|
|
__global__ void gpu_sdf_offset_kernel(
|
|
const float* __restrict__ in_sdf,
|
|
float* __restrict__ out_sdf,
|
|
size_t total_elements,
|
|
float delta_mm
|
|
) {
|
|
size_t idx = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx >= total_elements) return;
|
|
out_sdf[idx] = in_sdf[idx] + delta_mm;
|
|
}
|
|
|
|
__global__ void gpu_sdf_shell_kernel(
|
|
const float* __restrict__ in_sdf,
|
|
float* __restrict__ out_sdf,
|
|
size_t total_elements,
|
|
float thickness_mm
|
|
) {
|
|
size_t idx = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx >= total_elements) return;
|
|
|
|
float d = in_sdf[idx];
|
|
float half_t = thickness_mm * 0.5f;
|
|
// Positive inside shell, negative outside / hollow center
|
|
out_sdf[idx] = half_t - fabsf(d - half_t);
|
|
}
|
|
|
|
__global__ void gpu_sdf_infill_kernel(
|
|
const float* __restrict__ in_sdf,
|
|
float* __restrict__ out_sdf,
|
|
int width, int height, int num_layers,
|
|
int32_t min_x_um, int32_t min_y_um, int32_t pitch_um,
|
|
const int32_t* __restrict__ z_heights_um,
|
|
int pattern,
|
|
float cell_size_mm,
|
|
float density_ratio,
|
|
float wall_offset_mm
|
|
) {
|
|
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
|
int l = blockIdx.z;
|
|
|
|
if (x >= width || y >= height || l >= num_layers) return;
|
|
|
|
size_t idx = ((size_t)l * height + y) * width + x;
|
|
float dist = in_sdf[idx];
|
|
|
|
// Outside boundary or within shell wall offset -> retain wall distance
|
|
if (dist <= wall_offset_mm) {
|
|
out_sdf[idx] = dist;
|
|
return;
|
|
}
|
|
|
|
float world_x = (float)(min_x_um + x * pitch_um) * 0.001f;
|
|
float world_y = (float)(min_y_um + y * pitch_um) * 0.001f;
|
|
float world_z = (float)(z_heights_um[l]) * 0.001f;
|
|
|
|
float cell_sz = (cell_size_mm > 0.1f) ? cell_size_mm : 5.0f;
|
|
float freq = 6.28318530718f / cell_sz;
|
|
|
|
float px = world_x * freq;
|
|
float py = world_y * freq;
|
|
float pz = world_z * freq;
|
|
|
|
float field = 0.0f;
|
|
if (pattern == GPU_SDF_INFILL_GYROID) {
|
|
field = sinf(px) * cosf(py) + sinf(py) * cosf(pz) + sinf(pz) * cosf(px);
|
|
} else if (pattern == GPU_SDF_INFILL_SCHWARZ_P) {
|
|
field = cosf(px) + cosf(py) + cosf(pz);
|
|
} else if (pattern == GPU_SDF_INFILL_SCHWARZ_D) {
|
|
field = cosf(px)*cosf(py)*cosf(pz) - sinf(px)*sinf(py)*sinf(pz);
|
|
} else if (pattern == GPU_SDF_INFILL_GRID) {
|
|
float fx = fabsf(fmodf(world_x, cell_sz) - cell_sz * 0.5f);
|
|
float fy = fabsf(fmodf(world_y, cell_sz) - cell_sz * 0.5f);
|
|
field = fminf(fx, fy) / (cell_sz * 0.5f) - 0.5f;
|
|
}
|
|
|
|
float threshold = (density_ratio - 0.5f) * 1.5f;
|
|
// Combine lattice with the infill bounding region
|
|
float lattice_sdf = (field >= threshold) ? 1.0f : -1.0f;
|
|
out_sdf[idx] = fminf(dist - wall_offset_mm, lattice_sdf);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Direct Bitpack Vectorizer Kernel from SDF
|
|
// -----------------------------------------------------------------------------
|
|
__global__ void gpu_sdf_threshold_bitpack_kernel(
|
|
const float* __restrict__ sdf,
|
|
unsigned int* __restrict__ out_bit_words,
|
|
int width, int height, int layers,
|
|
const float* __restrict__ thresholds_mm,
|
|
int num_offsets,
|
|
int row_stride_words
|
|
) {
|
|
int x_word = blockIdx.x * blockDim.x + threadIdx.x;
|
|
int y = blockIdx.y;
|
|
int z = blockIdx.z;
|
|
|
|
if (x_word >= row_stride_words || y >= height || z >= layers) return;
|
|
|
|
size_t in_layer_offset = ((size_t)z * height + y) * width;
|
|
size_t total_word_layer = (size_t)layers * height * row_stride_words;
|
|
size_t out_layer_offset = (size_t)z * height * row_stride_words + y * row_stride_words;
|
|
|
|
int x_base = x_word * 32;
|
|
|
|
for (int o = 0; o < num_offsets; ++o) {
|
|
float thresh = thresholds_mm[o];
|
|
unsigned int packed = 0;
|
|
|
|
#pragma unroll
|
|
for (int b = 0; b < 32; ++b) {
|
|
int x = x_base + b;
|
|
if (x < width) {
|
|
float d = sdf[in_layer_offset + x];
|
|
if (d >= thresh) {
|
|
packed |= (1u << b);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t out_idx = (size_t)o * total_word_layer + out_layer_offset + x_word;
|
|
out_bit_words[out_idx] = packed;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Public C API Implementation
|
|
// -----------------------------------------------------------------------------
|
|
extern "C" {
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_create(
|
|
const GpuSdfMetadata* meta,
|
|
const int32_t* layer_z_heights_um
|
|
) {
|
|
if (!meta || meta->width <= 0 || meta->height <= 0 || meta->num_layers <= 0) return nullptr;
|
|
|
|
GpuSdfStackHandle stack = new GpuSdfStack_T();
|
|
stack->meta = *meta;
|
|
stack->owns_stream = true;
|
|
if (cudaStreamCreate(&stack->stream) != cudaSuccess) {
|
|
delete stack;
|
|
return nullptr;
|
|
}
|
|
|
|
size_t total_voxels = (size_t)meta->num_layers * meta->height * meta->width;
|
|
size_t sdf_bytes = total_voxels * sizeof(float);
|
|
size_t z_bytes = meta->num_layers * sizeof(int32_t);
|
|
|
|
if (cudaMalloc(&stack->d_sdf, sdf_bytes) != cudaSuccess ||
|
|
cudaMalloc(&stack->d_z_um, z_bytes) != cudaSuccess) {
|
|
if (stack->d_sdf) cudaFree(stack->d_sdf);
|
|
if (stack->d_z_um) cudaFree(stack->d_z_um);
|
|
cudaStreamDestroy(stack->stream);
|
|
delete stack;
|
|
return nullptr;
|
|
}
|
|
|
|
if (layer_z_heights_um) {
|
|
cudaMemcpyAsync(stack->d_z_um, layer_z_heights_um, z_bytes, cudaMemcpyHostToDevice, stack->stream);
|
|
} else {
|
|
// Default zero heights
|
|
cudaMemsetAsync(stack->d_z_um, 0, z_bytes, stack->stream);
|
|
}
|
|
cudaMemsetAsync(stack->d_sdf, 0, sdf_bytes, stack->stream);
|
|
|
|
return stack;
|
|
}
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_masks(
|
|
const uint8_t* h_masks,
|
|
int width,
|
|
int height,
|
|
size_t num_layers,
|
|
const int32_t* layer_z_heights_um,
|
|
int32_t min_x_um,
|
|
int32_t min_y_um,
|
|
int32_t pitch_um
|
|
) {
|
|
if (!h_masks || width <= 0 || height <= 0 || num_layers == 0) return nullptr;
|
|
|
|
GpuSdfMetadata meta;
|
|
meta.width = width;
|
|
meta.height = height;
|
|
meta.num_layers = num_layers;
|
|
meta.min_x_um = min_x_um;
|
|
meta.min_y_um = min_y_um;
|
|
meta.pitch_um = pitch_um > 0 ? pitch_um : 100;
|
|
|
|
GpuSdfStackHandle stack = gpu_sdf_create(&meta, layer_z_heights_um);
|
|
if (!stack) return nullptr;
|
|
|
|
size_t total_voxels = (size_t)num_layers * height * width;
|
|
size_t mask_bytes = total_voxels * sizeof(uint8_t);
|
|
size_t seed_bytes = total_voxels * sizeof(int);
|
|
|
|
uint8_t* d_masks = nullptr;
|
|
int* d_seeds_ext_a = nullptr;
|
|
int* d_seeds_ext_b = nullptr;
|
|
int* d_seeds_int_a = nullptr;
|
|
int* d_seeds_int_b = nullptr;
|
|
|
|
if (cudaMalloc(&d_masks, mask_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_ext_a, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_ext_b, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_int_a, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_int_b, seed_bytes) != cudaSuccess) {
|
|
if (d_masks) cudaFree(d_masks);
|
|
if (d_seeds_ext_a) cudaFree(d_seeds_ext_a);
|
|
if (d_seeds_ext_b) cudaFree(d_seeds_ext_b);
|
|
if (d_seeds_int_a) cudaFree(d_seeds_int_a);
|
|
if (d_seeds_int_b) cudaFree(d_seeds_int_b);
|
|
gpu_sdf_destroy(stack);
|
|
return nullptr;
|
|
}
|
|
|
|
cudaMemcpyAsync(d_masks, h_masks, mask_bytes, cudaMemcpyHostToDevice, stack->stream);
|
|
|
|
dim3 block(16, 16, 1);
|
|
dim3 grid((width + 15) / 16, (height + 15) / 16, (unsigned int)num_layers);
|
|
|
|
jfa_init_interior_exterior_kernel<<<grid, block, 0, stack->stream>>>(
|
|
d_masks, d_seeds_ext_a, d_seeds_int_a, width, height, (int)num_layers
|
|
);
|
|
|
|
int max_dim = std::max(width, height);
|
|
int step = 1;
|
|
while (step * 2 < max_dim) step *= 2;
|
|
|
|
int* in_seeds_ext = d_seeds_ext_a;
|
|
int* out_seeds_ext = d_seeds_ext_b;
|
|
int* in_seeds_int = d_seeds_int_a;
|
|
int* out_seeds_int = d_seeds_int_b;
|
|
|
|
while (step >= 1) {
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
width, height, (int)num_layers, step
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
step /= 2;
|
|
}
|
|
|
|
// JFA+2 and JFA+1 corrections
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
width, height, (int)num_layers, 2
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
width, height, (int)num_layers, 1
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
|
|
float pitch_mm = (float)meta.pitch_um * 0.001f;
|
|
fuse_signed_sdf_kernel<<<grid, block, 0, stack->stream>>>(
|
|
d_masks, in_seeds_ext, in_seeds_int, stack->d_sdf,
|
|
width, height, (int)num_layers, pitch_mm
|
|
);
|
|
|
|
cudaStreamSynchronize(stack->stream);
|
|
|
|
cudaFree(d_masks);
|
|
cudaFree(d_seeds_ext_a);
|
|
cudaFree(d_seeds_ext_b);
|
|
cudaFree(d_seeds_int_a);
|
|
cudaFree(d_seeds_int_b);
|
|
|
|
return stack;
|
|
}
|
|
|
|
// Forward declare GPU mesh rasterizer helper from gpu_offset.cu
|
|
extern int gpu_slice_mesh_to_device_mask(
|
|
const GpuTriangle3D* triangles,
|
|
size_t num_triangles,
|
|
float min_z, float h_first, float layer_height, int num_layers,
|
|
float bounds_min_x, float bounds_min_y, float pitch,
|
|
int cols, int rows,
|
|
uint8_t* d_masks,
|
|
cudaStream_t stream
|
|
);
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_mesh(
|
|
const GpuTriangle3D* triangles,
|
|
size_t num_triangles,
|
|
float min_x, float min_y, float min_z,
|
|
float max_x, float max_y, float max_z,
|
|
float layer_height_mm,
|
|
float first_layer_height_mm,
|
|
float pitch_mm,
|
|
float pad_mm
|
|
) {
|
|
if (!triangles || num_triangles == 0) return nullptr;
|
|
|
|
float pad = (pad_mm > 0.0f) ? pad_mm : 1.5f;
|
|
float pitch = (pitch_mm > 0.0f) ? pitch_mm : 0.10f;
|
|
float bounds_min_x = min_x - pad;
|
|
float bounds_min_y = min_y - pad;
|
|
float bounds_max_x = max_x + pad;
|
|
float bounds_max_y = max_y + pad;
|
|
|
|
int cols = (int)std::ceil((bounds_max_x - bounds_min_x) / pitch);
|
|
int rows = (int)std::ceil((bounds_max_y - bounds_min_y) / pitch);
|
|
|
|
float h_first = (first_layer_height_mm > 0.0f) ? first_layer_height_mm : layer_height_mm;
|
|
float z_first = min_z + h_first;
|
|
|
|
int num_layers = 0;
|
|
if (max_z >= z_first) {
|
|
num_layers = (int)std::floor((max_z - z_first) / layer_height_mm) + 1;
|
|
}
|
|
if (num_layers <= 0) return nullptr;
|
|
|
|
std::vector<int32_t> z_heights_um(num_layers);
|
|
for (int l = 0; l < num_layers; ++l) {
|
|
float z_val = z_first + l * layer_height_mm;
|
|
z_heights_um[l] = (int32_t)std::round(z_val * 1000.0f);
|
|
}
|
|
|
|
GpuSdfMetadata meta;
|
|
meta.width = cols;
|
|
meta.height = rows;
|
|
meta.num_layers = num_layers;
|
|
meta.min_x_um = (int32_t)std::round(bounds_min_x * 1000.0f);
|
|
meta.min_y_um = (int32_t)std::round(bounds_min_y * 1000.0f);
|
|
meta.pitch_um = (int32_t)std::round(pitch * 1000.0f);
|
|
|
|
GpuSdfStackHandle stack = gpu_sdf_create(&meta, z_heights_um.data());
|
|
if (!stack) return nullptr;
|
|
|
|
size_t total_volume = (size_t)num_layers * rows * cols;
|
|
size_t tri_bytes = num_triangles * sizeof(GpuTriangle3D);
|
|
size_t seed_bytes = total_volume * sizeof(int);
|
|
|
|
GpuTriangle3D* d_triangles = nullptr;
|
|
uint8_t* d_masks = nullptr;
|
|
int* d_seeds_ext_a = nullptr;
|
|
int* d_seeds_ext_b = nullptr;
|
|
int* d_seeds_int_a = nullptr;
|
|
int* d_seeds_int_b = nullptr;
|
|
|
|
if (cudaMalloc(&d_triangles, tri_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_masks, total_volume * sizeof(uint8_t)) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_ext_a, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_ext_b, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_int_a, seed_bytes) != cudaSuccess ||
|
|
cudaMalloc(&d_seeds_int_b, seed_bytes) != cudaSuccess) {
|
|
if (d_triangles) cudaFree(d_triangles);
|
|
if (d_masks) cudaFree(d_masks);
|
|
if (d_seeds_ext_a) cudaFree(d_seeds_ext_a);
|
|
if (d_seeds_ext_b) cudaFree(d_seeds_ext_b);
|
|
if (d_seeds_int_a) cudaFree(d_seeds_int_a);
|
|
if (d_seeds_int_b) cudaFree(d_seeds_int_b);
|
|
gpu_sdf_destroy(stack);
|
|
return nullptr;
|
|
}
|
|
|
|
cudaMemcpyAsync(d_triangles, triangles, tri_bytes, cudaMemcpyHostToDevice, stack->stream);
|
|
|
|
int slice_ret = gpu_slice_mesh_to_device_mask(
|
|
d_triangles, num_triangles, min_z, h_first, layer_height_mm, num_layers,
|
|
bounds_min_x, bounds_min_y, pitch, cols, rows, d_masks, stack->stream
|
|
);
|
|
if (slice_ret != GPU_OFFSET_SUCCESS) {
|
|
cudaFree(d_triangles);
|
|
cudaFree(d_masks);
|
|
cudaFree(d_seeds_ext_a);
|
|
cudaFree(d_seeds_ext_b);
|
|
cudaFree(d_seeds_int_a);
|
|
cudaFree(d_seeds_int_b);
|
|
gpu_sdf_destroy(stack);
|
|
return nullptr;
|
|
}
|
|
|
|
// Run Batched 3D JFA Dual Direction Signed Distance
|
|
dim3 block(16, 16, 1);
|
|
dim3 grid((cols + 15) / 16, (rows + 15) / 16, num_layers);
|
|
|
|
jfa_init_interior_exterior_kernel<<<grid, block, 0, stack->stream>>>(
|
|
d_masks, d_seeds_ext_a, d_seeds_int_a, cols, rows, num_layers
|
|
);
|
|
|
|
int max_dim = std::max(cols, rows);
|
|
int step = 1;
|
|
while (step * 2 < max_dim) step *= 2;
|
|
|
|
int* in_seeds_ext = d_seeds_ext_a;
|
|
int* out_seeds_ext = d_seeds_ext_b;
|
|
int* in_seeds_int = d_seeds_int_a;
|
|
int* out_seeds_int = d_seeds_int_b;
|
|
|
|
while (step >= 1) {
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
cols, rows, num_layers, step
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
step /= 2;
|
|
}
|
|
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
cols, rows, num_layers, 2
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
|
|
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
in_seeds_ext, out_seeds_ext,
|
|
in_seeds_int, out_seeds_int,
|
|
cols, rows, num_layers, 1
|
|
);
|
|
std::swap(in_seeds_ext, out_seeds_ext);
|
|
std::swap(in_seeds_int, out_seeds_int);
|
|
|
|
fuse_signed_sdf_kernel<<<grid, block, 0, stack->stream>>>(
|
|
d_masks, in_seeds_ext, in_seeds_int, stack->d_sdf,
|
|
cols, rows, num_layers, pitch
|
|
);
|
|
|
|
cudaStreamSynchronize(stack->stream);
|
|
|
|
cudaFree(d_triangles);
|
|
cudaFree(d_masks);
|
|
cudaFree(d_seeds_ext_a);
|
|
cudaFree(d_seeds_ext_b);
|
|
cudaFree(d_seeds_int_a);
|
|
cudaFree(d_seeds_int_b);
|
|
|
|
return stack;
|
|
}
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_clone(GpuSdfStackHandle stack) {
|
|
if (!stack) return nullptr;
|
|
|
|
std::vector<int32_t> z_um(stack->meta.num_layers);
|
|
gpu_sdf_get_layer_z_um(stack, z_um.data());
|
|
|
|
GpuSdfStackHandle clone = gpu_sdf_create(&stack->meta, z_um.data());
|
|
if (!clone) return nullptr;
|
|
|
|
size_t total_voxels = (size_t)stack->meta.num_layers * stack->meta.height * stack->meta.width;
|
|
cudaMemcpyAsync(clone->d_sdf, stack->d_sdf, total_voxels * sizeof(float),
|
|
cudaMemcpyDeviceToDevice, clone->stream);
|
|
cudaStreamSynchronize(clone->stream);
|
|
|
|
return clone;
|
|
}
|
|
|
|
GPU_SDF_API void gpu_sdf_destroy(GpuSdfStackHandle stack) {
|
|
if (!stack) return;
|
|
if (stack->d_sdf) {
|
|
cudaFree(stack->d_sdf);
|
|
stack->d_sdf = nullptr;
|
|
}
|
|
if (stack->d_z_um) {
|
|
cudaFree(stack->d_z_um);
|
|
stack->d_z_um = nullptr;
|
|
}
|
|
if (stack->owns_stream && stack->stream) {
|
|
cudaStreamDestroy(stack->stream);
|
|
stack->stream = nullptr;
|
|
}
|
|
delete stack;
|
|
}
|
|
|
|
GPU_SDF_API int gpu_sdf_get_metadata(GpuSdfStackHandle stack, GpuSdfMetadata* out_meta) {
|
|
if (!stack || !out_meta) return GPU_OFFSET_ERR_INVALID_ARG;
|
|
*out_meta = stack->meta;
|
|
return GPU_OFFSET_SUCCESS;
|
|
}
|
|
|
|
GPU_SDF_API int gpu_sdf_get_layer_z_um(GpuSdfStackHandle stack, int32_t* out_z_um) {
|
|
if (!stack || !out_z_um) return GPU_OFFSET_ERR_INVALID_ARG;
|
|
CUDA_CHECK_INT(cudaMemcpy(out_z_um, stack->d_z_um, stack->meta.num_layers * sizeof(int32_t), cudaMemcpyDeviceToHost));
|
|
return GPU_OFFSET_SUCCESS;
|
|
}
|
|
|
|
GPU_SDF_API int gpu_sdf_to_host(GpuSdfStackHandle stack, float* h_out_data) {
|
|
if (!stack || !h_out_data) return GPU_OFFSET_ERR_INVALID_ARG;
|
|
size_t total_voxels = (size_t)stack->meta.num_layers * stack->meta.height * stack->meta.width;
|
|
CUDA_CHECK_INT(cudaMemcpy(h_out_data, stack->d_sdf, total_voxels * sizeof(float), cudaMemcpyDeviceToHost));
|
|
return GPU_OFFSET_SUCCESS;
|
|
}
|
|
|
|
GPU_SDF_API float* gpu_sdf_get_device_ptr(GpuSdfStackHandle stack) {
|
|
return stack ? stack->d_sdf : nullptr;
|
|
}
|
|
|
|
GPU_SDF_API int32_t* gpu_sdf_get_device_z_ptr(GpuSdfStackHandle stack) {
|
|
return stack ? stack->d_z_um : nullptr;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// CSG Execution Engine
|
|
// -----------------------------------------------------------------------------
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_csg_op(
|
|
GpuSdfStackHandle a,
|
|
GpuSdfStackHandle b,
|
|
GpuSdfOp op,
|
|
GpuSdfLayerMatchMode match_mode,
|
|
float blend_radius_mm
|
|
) {
|
|
if (!a || !b) return nullptr;
|
|
|
|
// Check if grids and layers are 100% identical
|
|
std::vector<int32_t> z_a(a->meta.num_layers);
|
|
std::vector<int32_t> z_b(b->meta.num_layers);
|
|
gpu_sdf_get_layer_z_um(a, z_a.data());
|
|
gpu_sdf_get_layer_z_um(b, z_b.data());
|
|
|
|
bool identical_grid = (a->meta.min_x_um == b->meta.min_x_um) &&
|
|
(a->meta.min_y_um == b->meta.min_y_um) &&
|
|
(a->meta.pitch_um == b->meta.pitch_um) &&
|
|
(a->meta.width == b->meta.width) &&
|
|
(a->meta.height == b->meta.height) &&
|
|
(a->meta.num_layers == b->meta.num_layers) &&
|
|
(z_a == z_b);
|
|
|
|
if (identical_grid) {
|
|
GpuSdfStackHandle out_stack = gpu_sdf_create(&a->meta, z_a.data());
|
|
if (!out_stack) return nullptr;
|
|
|
|
size_t total_voxels = (size_t)a->meta.num_layers * a->meta.height * a->meta.width;
|
|
int block_sz = 256;
|
|
int grid_sz = (total_voxels + block_sz - 1) / block_sz;
|
|
|
|
gpu_sdf_csg_identical_kernel<<<grid_sz, block_sz, 0, out_stack->stream>>>(
|
|
a->d_sdf, b->d_sdf, out_stack->d_sdf, total_voxels, (int)op, blend_radius_mm
|
|
);
|
|
cudaStreamSynchronize(out_stack->stream);
|
|
return out_stack;
|
|
}
|
|
|
|
// Harmonize Spatial Grid (XY)
|
|
int32_t pitch_um = a->meta.pitch_um;
|
|
int32_t min_x_um = std::min(a->meta.min_x_um, b->meta.min_x_um);
|
|
int32_t min_y_um = std::min(a->meta.min_y_um, b->meta.min_y_um);
|
|
int32_t max_x_um = std::max(a->meta.min_x_um + a->meta.width * a->meta.pitch_um,
|
|
b->meta.min_x_um + b->meta.width * b->meta.pitch_um);
|
|
int32_t max_y_um = std::max(a->meta.min_y_um + a->meta.height * a->meta.pitch_um,
|
|
b->meta.min_y_um + b->meta.height * b->meta.pitch_um);
|
|
|
|
int out_w = (int)std::ceil((float)(max_x_um - min_x_um) / pitch_um);
|
|
int out_h = (int)std::ceil((float)(max_y_um - min_y_um) / pitch_um);
|
|
|
|
// Harmonize Layers (Z)
|
|
std::vector<int32_t> out_z_um;
|
|
if (match_mode == GPU_SDF_LAYER_MATCH_A_DOMINANT) {
|
|
out_z_um = z_a;
|
|
} else if (match_mode == GPU_SDF_LAYER_MATCH_UNION) {
|
|
std::set<int32_t> z_set(z_a.begin(), z_a.end());
|
|
z_set.insert(z_b.begin(), z_b.end());
|
|
out_z_um.assign(z_set.begin(), z_set.end());
|
|
} else { // INTERSECTION
|
|
std::set<int32_t> set_b(z_b.begin(), z_b.end());
|
|
for (int32_t z : z_a) {
|
|
if (set_b.count(z)) out_z_um.push_back(z);
|
|
}
|
|
}
|
|
|
|
if (out_z_um.empty()) return nullptr;
|
|
|
|
GpuSdfMetadata meta_out;
|
|
meta_out.width = out_w;
|
|
meta_out.height = out_h;
|
|
meta_out.num_layers = out_z_um.size();
|
|
meta_out.min_x_um = min_x_um;
|
|
meta_out.min_y_um = min_y_um;
|
|
meta_out.pitch_um = pitch_um;
|
|
|
|
GpuSdfStackHandle out_stack = gpu_sdf_create(&meta_out, out_z_um.data());
|
|
if (!out_stack) return nullptr;
|
|
|
|
// Build Layer Sampling Info table on Host & upload to Device
|
|
std::vector<LayerSamplingInfo> layer_infos(meta_out.num_layers);
|
|
|
|
auto find_layer_sampling = [](const std::vector<int32_t>& z_list, int32_t target_z, int& l0, int& l1, float& t) {
|
|
if (z_list.empty()) {
|
|
l0 = l1 = -1; t = -1.0f; return;
|
|
}
|
|
if (target_z < z_list.front() || target_z > z_list.back()) {
|
|
l0 = l1 = -1; t = -1.0f; return;
|
|
}
|
|
auto it = std::lower_bound(z_list.begin(), z_list.end(), target_z);
|
|
if (it != z_list.end() && *it == target_z) {
|
|
l0 = l1 = (int)(it - z_list.begin());
|
|
t = 0.0f;
|
|
return;
|
|
}
|
|
// Interpolation between adjacent layers
|
|
size_t idx1 = it - z_list.begin();
|
|
size_t idx0 = idx1 - 1;
|
|
l0 = (int)idx0;
|
|
l1 = (int)idx1;
|
|
t = (float)(target_z - z_list[idx0]) / (float)(z_list[idx1] - z_list[idx0]);
|
|
};
|
|
|
|
for (size_t l = 0; l < meta_out.num_layers; ++l) {
|
|
int32_t tz = out_z_um[l];
|
|
find_layer_sampling(z_a, tz, layer_infos[l].l0_a, layer_infos[l].l1_a, layer_infos[l].t_a);
|
|
find_layer_sampling(z_b, tz, layer_infos[l].l0_b, layer_infos[l].l1_b, layer_infos[l].t_b);
|
|
}
|
|
|
|
LayerSamplingInfo* d_layer_map = nullptr;
|
|
cudaMalloc(&d_layer_map, meta_out.num_layers * sizeof(LayerSamplingInfo));
|
|
cudaMemcpyAsync(d_layer_map, layer_infos.data(), meta_out.num_layers * sizeof(LayerSamplingInfo),
|
|
cudaMemcpyHostToDevice, out_stack->stream);
|
|
|
|
dim3 block(16, 16, 1);
|
|
dim3 grid((out_w + 15) / 16, (out_h + 15) / 16, (unsigned int)meta_out.num_layers);
|
|
|
|
gpu_sdf_csg_resample_kernel<<<grid, block, 0, out_stack->stream>>>(
|
|
a->d_sdf, a->meta,
|
|
b->d_sdf, b->meta,
|
|
out_stack->d_sdf, meta_out,
|
|
d_layer_map, (int)op, blend_radius_mm
|
|
);
|
|
|
|
cudaStreamSynchronize(out_stack->stream);
|
|
cudaFree(d_layer_map);
|
|
|
|
return out_stack;
|
|
}
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_offset(
|
|
GpuSdfStackHandle stack,
|
|
float delta_mm
|
|
) {
|
|
if (!stack) return nullptr;
|
|
|
|
std::vector<int32_t> z_um(stack->meta.num_layers);
|
|
gpu_sdf_get_layer_z_um(stack, z_um.data());
|
|
|
|
GpuSdfStackHandle out_stack = gpu_sdf_create(&stack->meta, z_um.data());
|
|
if (!out_stack) return nullptr;
|
|
|
|
size_t total_voxels = (size_t)stack->meta.num_layers * stack->meta.height * stack->meta.width;
|
|
int block_sz = 256;
|
|
int grid_sz = (total_voxels + block_sz - 1) / block_sz;
|
|
|
|
gpu_sdf_offset_kernel<<<grid_sz, block_sz, 0, out_stack->stream>>>(
|
|
stack->d_sdf, out_stack->d_sdf, total_voxels, delta_mm
|
|
);
|
|
cudaStreamSynchronize(out_stack->stream);
|
|
|
|
return out_stack;
|
|
}
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_shell(
|
|
GpuSdfStackHandle stack,
|
|
float thickness_mm
|
|
) {
|
|
if (!stack || thickness_mm <= 0.0f) return nullptr;
|
|
|
|
std::vector<int32_t> z_um(stack->meta.num_layers);
|
|
gpu_sdf_get_layer_z_um(stack, z_um.data());
|
|
|
|
GpuSdfStackHandle out_stack = gpu_sdf_create(&stack->meta, z_um.data());
|
|
if (!out_stack) return nullptr;
|
|
|
|
size_t total_voxels = (size_t)stack->meta.num_layers * stack->meta.height * stack->meta.width;
|
|
int block_sz = 256;
|
|
int grid_sz = (total_voxels + block_sz - 1) / block_sz;
|
|
|
|
gpu_sdf_shell_kernel<<<grid_sz, block_sz, 0, out_stack->stream>>>(
|
|
stack->d_sdf, out_stack->d_sdf, total_voxels, thickness_mm
|
|
);
|
|
cudaStreamSynchronize(out_stack->stream);
|
|
|
|
return out_stack;
|
|
}
|
|
|
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_generate_infill(
|
|
GpuSdfStackHandle stack,
|
|
GpuSdfInfillPattern pattern,
|
|
float cell_size_mm,
|
|
float density_ratio,
|
|
float wall_offset_mm
|
|
) {
|
|
if (!stack) return nullptr;
|
|
|
|
std::vector<int32_t> z_um(stack->meta.num_layers);
|
|
gpu_sdf_get_layer_z_um(stack, z_um.data());
|
|
|
|
GpuSdfStackHandle out_stack = gpu_sdf_create(&stack->meta, z_um.data());
|
|
if (!out_stack) return nullptr;
|
|
|
|
dim3 block(16, 16, 1);
|
|
dim3 grid((stack->meta.width + 15) / 16, (stack->meta.height + 15) / 16, (unsigned int)stack->meta.num_layers);
|
|
|
|
gpu_sdf_infill_kernel<<<grid, block, 0, out_stack->stream>>>(
|
|
stack->d_sdf, out_stack->d_sdf,
|
|
stack->meta.width, stack->meta.height, (int)stack->meta.num_layers,
|
|
stack->meta.min_x_um, stack->meta.min_y_um, stack->meta.pitch_um,
|
|
stack->d_z_um, (int)pattern, cell_size_mm, density_ratio, wall_offset_mm
|
|
);
|
|
cudaStreamSynchronize(out_stack->stream);
|
|
|
|
return out_stack;
|
|
}
|
|
|
|
GPU_SDF_API int gpu_sdf_vectorize(
|
|
GpuSdfStackHandle stack,
|
|
const GpuOffsetConfig* config,
|
|
GpuLayerResult** out_results,
|
|
size_t* out_num_layers,
|
|
GpuOffsetStats* stats
|
|
) {
|
|
if (!stack || !config || !out_results) return GPU_OFFSET_ERR_INVALID_ARG;
|
|
|
|
auto t_total_start = std::chrono::high_resolution_clock::now();
|
|
|
|
int cols = stack->meta.width;
|
|
int rows = stack->meta.height;
|
|
size_t num_layers = stack->meta.num_layers;
|
|
if (out_num_layers) *out_num_layers = num_layers;
|
|
|
|
float bounds_min_x = (float)stack->meta.min_x_um * 0.001f;
|
|
float bounds_min_y = (float)stack->meta.min_y_um * 0.001f;
|
|
float pitch_mm = (float)stack->meta.pitch_um * 0.001f;
|
|
|
|
int P = (config->perimeters_count >= 0) ? config->perimeters_count : 2;
|
|
float W = (config->layer_width > 0.0f) ? config->layer_width : 0.45f;
|
|
std::vector<float> resolved_offsets;
|
|
if (config->offset_distances_mm && config->num_offsets > 0) {
|
|
resolved_offsets.assign(config->offset_distances_mm, config->offset_distances_mm + config->num_offsets);
|
|
P = (config->perimeters_count > 0) ? config->perimeters_count : (int)resolved_offsets.size() - 1;
|
|
if (P < 1) P = (int)resolved_offsets.size();
|
|
} else {
|
|
resolved_offsets.resize(P + 1);
|
|
for (int i = 0; i < P; ++i) {
|
|
resolved_offsets[i] = (i + 0.5f) * W;
|
|
}
|
|
resolved_offsets[P] = (float)P * W;
|
|
}
|
|
size_t num_offsets = resolved_offsets.size();
|
|
|
|
float simplify_tol = config->simplify_tolerance_mm;
|
|
if (simplify_tol <= 0.0f && config->subpixel_lerp != 0) {
|
|
simplify_tol = 0.02f; // default 0.02 mm (20 µm) tolerance
|
|
}
|
|
|
|
size_t total_voxels = (size_t)num_layers * rows * cols;
|
|
size_t sdf_bytes = total_voxels * sizeof(float);
|
|
|
|
float* h_pinned_sdf = nullptr;
|
|
CUDA_CHECK_INT(cudaMallocHost(&h_pinned_sdf, sdf_bytes));
|
|
CUDA_CHECK_INT(cudaMemcpyAsync(h_pinned_sdf, stack->d_sdf, sdf_bytes,
|
|
cudaMemcpyDeviceToHost, stack->stream));
|
|
CUDA_CHECK_INT(cudaStreamSynchronize(stack->stream));
|
|
|
|
std::vector<int32_t> z_heights_um(num_layers);
|
|
gpu_sdf_get_layer_z_um(stack, z_heights_um.data());
|
|
|
|
// Multi-threaded C++ Sub-Pixel LERP Vectorization
|
|
auto t_vec_0 = std::chrono::high_resolution_clock::now();
|
|
|
|
GpuLayerResult* res_array = (GpuLayerResult*)malloc(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;
|
|
|
|
auto vec_worker = [&](size_t start_layer, size_t end_layer) {
|
|
int stride_w = cols + 1;
|
|
size_t max_edges = 2 * (size_t)(rows + 1) * stride_w;
|
|
std::vector<int> next_edge_buf(max_edges, -1);
|
|
std::vector<int> active_edges_buf;
|
|
active_edges_buf.reserve(cols * rows / 16);
|
|
std::vector<uint8_t> visited_buf(max_edges, 0);
|
|
|
|
for (size_t l = start_layer; l < end_layer; ++l) {
|
|
res_array[l].z_height = (float)z_heights_um[l] * 0.001f;
|
|
res_array[l].num_perimeters = P;
|
|
res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
|
|
|
|
const float* layer_sdf_ptr = h_pinned_sdf + l * ((size_t)rows * cols);
|
|
|
|
for (int o = 0; o < P; ++o) {
|
|
float iso_thresh = resolved_offsets[o];
|
|
|
|
std::vector<GpuContour> contours;
|
|
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
|
iso_thresh, simplify_tol,
|
|
next_edge_buf, active_edges_buf, visited_buf, contours);
|
|
|
|
res_array[l].perimeters[o].offset_distance_mm = iso_thresh;
|
|
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
|
|
float infill_iso_thresh = resolved_offsets[P];
|
|
std::vector<GpuContour> infill_contours;
|
|
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
|
infill_iso_thresh, simplify_tol,
|
|
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
|
|
|
res_array[l].infill.offset_distance_mm = infill_iso_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((size_t)cols * rows);
|
|
for (int i = 0; i < cols * rows; ++i) {
|
|
infill_mask[i] = (layer_sdf_ptr[i] >= infill_iso_thresh) ? 1 : 0;
|
|
}
|
|
res_array[l].infill.mask = infill_mask;
|
|
} else {
|
|
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(vec_worker, s, e);
|
|
}
|
|
for (auto& w : workers) w.join();
|
|
|
|
auto t_vec_1 = std::chrono::high_resolution_clock::now();
|
|
double t_vec_ms = std::chrono::duration<double, std::milli>(t_vec_1 - t_vec_0).count();
|
|
|
|
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();
|
|
|
|
if (stats) {
|
|
stats->t_vectorize_ms = t_vec_ms;
|
|
stats->t_total_pipeline_ms = t_total_ms;
|
|
}
|
|
|
|
cudaFreeHost(h_pinned_sdf);
|
|
|
|
*out_results = res_array;
|
|
return GPU_OFFSET_SUCCESS;
|
|
}
|
|
|
|
} // extern "C"
|