test_svx_octree.c (16145B)
1 /* Copyright (C) 2018, 2020-2025 |Méso|Star> (contact@meso-star.com) 2 * Copyright (C) 2018 Université Paul Sabatier 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17 #include "svx.h" 18 #include "test_svx_utils.h" 19 20 #include <rsys/double3.h> 21 #include <rsys/morton.h> 22 23 #include <string.h> 24 25 struct leaves_context { 26 double* lower; 27 double* upper; 28 size_t* nvoxels; 29 size_t depth; 30 }; 31 32 struct at_context { 33 double position[3]; 34 size_t depth; 35 }; 36 37 struct aabb { 38 double lower[3]; 39 double upper[3]; 40 size_t depth; 41 }; 42 43 struct build_context { 44 double voxsz[3]; 45 double lower[3]; 46 double upper[3]; 47 size_t max_depth; 48 }; 49 50 static int 51 no_merge(const struct svx_voxel voxels[], const size_t nvoxels, void* ctx) 52 { 53 CHK(voxels != NULL); 54 CHK(nvoxels != 0); 55 CHK((intptr_t)ctx == 0xDECAFBAD); 56 return 0; /* Merge nothing */ 57 } 58 59 static int 60 merge_level0(const struct svx_voxel voxels[], const size_t nvoxels, void* ctx) 61 { 62 double min_val = DBL_MAX; 63 double max_val =-DBL_MAX; 64 size_t i; 65 CHK(voxels != NULL); 66 CHK(nvoxels != 0); 67 CHK((intptr_t)ctx == 0xDECAFBAD); 68 69 FOR_EACH(i, 0, nvoxels) { 70 const double* val = voxels[i].data; 71 min_val = MMIN(min_val, *val); 72 max_val = MMAX(max_val, *val); 73 } 74 75 FOR_EACH(i, 0, nvoxels) { 76 if(max_val - min_val < 8) { 77 CHK(voxels[i].depth == 5); 78 } 79 } 80 81 return (max_val - min_val) < 8; 82 } 83 84 static void 85 keep_max(void* dst, const void* voxels[], const size_t nvoxels, void* ctx) 86 { 87 double* vox_dst = dst; 88 double max_val = -DBL_MAX; 89 size_t i; 90 91 CHK(dst != NULL); 92 CHK(voxels != NULL); 93 CHK(nvoxels != 0); 94 CHK(ctx != NULL); 95 96 FOR_EACH(i, 0, nvoxels) { 97 const double* val = voxels[i]; 98 max_val = MMAX(max_val, *val); 99 } 100 *vox_dst = max_val; 101 } 102 103 static void 104 get(const size_t xyz[3], const uint64_t mcode, void* dst, void* ctx) 105 { 106 uint32_t ui3[3]; 107 double* val = dst; 108 CHK(xyz != NULL); 109 CHK(val != NULL); 110 CHK((intptr_t)ctx == 0xDECAFBAD); 111 112 ui3[0] = (uint32_t)xyz[0]; 113 ui3[1] = (uint32_t)xyz[1]; 114 ui3[2] = (uint32_t)xyz[2]; 115 116 CHK(mcode == morton_xyz_encode_u21(ui3)); 117 CHK(mcode == 118 ( morton3D_encode_u21(ui3[0]) << 2 119 | morton3D_encode_u21(ui3[1]) << 1 120 | morton3D_encode_u21(ui3[2]) << 0)); 121 *val = (double)mcode; 122 } 123 124 static void 125 check_leaves 126 (const struct svx_voxel* leaf, 127 const size_t ileaf, 128 void* context) 129 { 130 const double* dbl = NULL; 131 struct leaves_context* ctx = context; 132 uint64_t mcode; 133 uint32_t xyz[3]; 134 double lower[3]; 135 double delta[3]; 136 137 CHK(leaf != NULL); 138 CHK(leaf->data != NULL); 139 CHK(ctx != NULL); 140 CHK(leaf->lower[0] < leaf->upper[0]); 141 CHK(leaf->lower[1] < leaf->upper[1]); 142 CHK(leaf->lower[2] < leaf->upper[2]); 143 CHK(ileaf < ctx->nvoxels[0]*ctx->nvoxels[1]*ctx->nvoxels[2]); 144 145 dbl = leaf->data; 146 CHK(*dbl >= 0); 147 148 mcode = (uint64_t)(*dbl); 149 CHK(*dbl == (double)mcode); 150 151 delta[0] = (ctx->upper[0] - ctx->lower[0]) / (double)ctx->nvoxels[0]; 152 delta[1] = (ctx->upper[1] - ctx->lower[1]) / (double)ctx->nvoxels[1]; 153 delta[2] = (ctx->upper[2] - ctx->lower[2]) / (double)ctx->nvoxels[2]; 154 155 morton_xyz_decode_u21(mcode, xyz); 156 lower[0] = xyz[0] * delta[0]; 157 lower[1] = xyz[1] * delta[1]; 158 lower[2] = xyz[2] * delta[2]; 159 160 CHK(eq_eps(lower[0], leaf->lower[0], 1.e-6)); 161 CHK(eq_eps(lower[1], leaf->lower[1], 1.e-6)); 162 CHK(eq_eps(lower[2], leaf->lower[2], 1.e-6)); 163 CHK(eq_eps(lower[0] + delta[0], leaf->upper[0], 1.e-6)); 164 CHK(eq_eps(lower[1] + delta[1], leaf->upper[1], 1.e-6)); 165 CHK(eq_eps(lower[2] + delta[2], leaf->upper[2], 1.e-6)); 166 167 CHK(leaf->depth == ctx->depth - 1); 168 } 169 170 static void 171 write_scalars 172 (const struct svx_voxel* leaf, 173 const size_t ileaf, 174 void* context) 175 { 176 FILE* stream = context; 177 (void)ileaf; 178 CHK(stream != NULL); 179 CHK(leaf != NULL); 180 fprintf(stream, "%g\n", *(double*)leaf->data); 181 } 182 183 static int 184 max_lod 185 (const struct svx_voxel* vox, 186 const double pos[3], 187 void* context) 188 { 189 const struct at_context* ctx = context; 190 CHK(vox != NULL); 191 CHK(pos != NULL); 192 CHK(ctx != NULL); 193 CHK(vox->depth <= ctx->depth); 194 CHK(d3_eq(pos, ctx->position)); 195 return vox->depth < ctx->depth; 196 } 197 198 static void 199 get_aabb(const size_t xyz[3], const uint64_t mcode, void* dst, void* ctx) 200 { 201 const struct build_context* build_ctx = ctx; 202 struct aabb* aabb = dst; 203 uint32_t ui3[3]; 204 205 aabb->lower[0] = (double)xyz[0] * build_ctx->voxsz[0] + build_ctx->lower[0]; 206 aabb->lower[1] = (double)xyz[1] * build_ctx->voxsz[1] + build_ctx->lower[1]; 207 aabb->lower[2] = (double)xyz[2] * build_ctx->voxsz[2] + build_ctx->lower[2]; 208 209 ui3[0] = (uint32_t)xyz[0]; 210 ui3[1] = (uint32_t)xyz[1]; 211 ui3[2] = (uint32_t)xyz[2]; 212 CHK(mcode == morton_xyz_encode_u21(ui3)); 213 d3_add(aabb->upper, aabb->lower, build_ctx->voxsz); 214 aabb->depth = build_ctx->max_depth; 215 } 216 217 static void 218 merge_aabb(void* dst, const void* voxels[], const size_t nvoxels, void* ctx) 219 { 220 const struct build_context* build_ctx = ctx; 221 double upper[3]; 222 double voxsz[3]; 223 struct aabb* aabb = dst; 224 size_t depth = SIZE_MAX; 225 size_t i; 226 CHK(dst && voxels && nvoxels && ctx); 227 228 d3_splat(aabb->lower, DBL_MAX); 229 d3_splat(aabb->upper,-DBL_MAX); 230 aabb->depth = 0; 231 232 FOR_EACH(i, 0, nvoxels) { 233 const struct aabb* vox_aabb = voxels[i]; 234 if(depth == SIZE_MAX) { 235 depth = vox_aabb->depth; 236 } else { 237 CHK(depth == vox_aabb->depth); 238 } 239 aabb->lower[0] = MMIN(vox_aabb->lower[0], aabb->lower[0]); 240 aabb->lower[1] = MMIN(vox_aabb->lower[1], aabb->lower[1]); 241 aabb->lower[2] = MMIN(vox_aabb->lower[2], aabb->lower[2]); 242 aabb->upper[0] = MMAX(vox_aabb->upper[0], aabb->upper[0]); 243 aabb->upper[1] = MMAX(vox_aabb->upper[1], aabb->upper[1]); 244 aabb->upper[2] = MMAX(vox_aabb->upper[2], aabb->upper[2]); 245 } 246 247 CHK(build_ctx->max_depth >= depth); 248 CHK(depth > 0); 249 aabb->depth = depth - 1; 250 251 i = build_ctx->max_depth - aabb->depth; 252 voxsz[0] = build_ctx->voxsz[0] * (double)(1<<i); 253 voxsz[1] = build_ctx->voxsz[1] * (double)(1<<i); 254 voxsz[2] = build_ctx->voxsz[2] * (double)(1<<i); 255 256 /* Clamp voxel to grid size */ 257 upper[0] = MMIN(aabb->lower[0] + voxsz[0], build_ctx->upper[0]); 258 upper[1] = MMIN(aabb->lower[1] + voxsz[1], build_ctx->upper[1]); 259 upper[2] = MMIN(aabb->lower[2] + voxsz[2], build_ctx->upper[2]); 260 261 /* Adjust the voxel size from the clampd voxel */ 262 voxsz[0] = upper[0] - aabb->lower[0]; 263 voxsz[1] = upper[1] - aabb->lower[1]; 264 voxsz[2] = upper[2] - aabb->lower[2]; 265 266 CHK(eq_eps(voxsz[0], aabb->upper[0] - aabb->lower[0], 1.e-6)); 267 CHK(eq_eps(voxsz[1], aabb->upper[1] - aabb->lower[1], 1.e-6)); 268 CHK(eq_eps(voxsz[2], aabb->upper[2] - aabb->lower[2], 1.e-6)); 269 } 270 271 static int 272 challenge_aabb(const struct svx_voxel voxels[], const size_t nvoxels, void* ctx) 273 { 274 const struct build_context* build_ctx = ctx; 275 size_t depth = SIZE_MAX; 276 size_t i; 277 CHK(voxels && nvoxels && ctx); 278 279 FOR_EACH(i, 0, nvoxels) { 280 double voxsz[3]; 281 const struct aabb* aabb = voxels[i].data; 282 const size_t n = build_ctx->max_depth - aabb->depth; 283 CHK(build_ctx->max_depth >= aabb->depth); 284 285 if(depth == SIZE_MAX) { 286 depth = aabb->depth; 287 } else { 288 CHK(depth == aabb->depth); 289 } 290 CHK(depth == voxels[i].depth); 291 CHK(d3_eq_eps(aabb->lower, voxels[i].lower, 1.e-6)); 292 CHK(d3_eq_eps(aabb->upper, voxels[i].upper, 1.e-6)); 293 voxsz[0] = build_ctx->voxsz[0] * (double)(1<<n); 294 voxsz[1] = build_ctx->voxsz[1] * (double)(1<<n); 295 voxsz[2] = build_ctx->voxsz[2] * (double)(1<<n); 296 CHK(eq_eps(voxsz[0], aabb->upper[0] - aabb->lower[0], 1.e-6)); 297 CHK(eq_eps(voxsz[1], aabb->upper[1] - aabb->lower[1], 1.e-6)); 298 CHK(eq_eps(voxsz[2], aabb->upper[2] - aabb->lower[2], 1.e-6)); 299 } 300 301 return 1; 302 } 303 304 static void 305 test_at_accessor(struct svx_tree* oct, const size_t nvoxels[3]) 306 { 307 struct svx_tree_desc tree_desc; 308 struct at_context ctx; 309 size_t nvxls; 310 double delta[3]; 311 double ocsz[3]; 312 size_t x, y, z; 313 314 CHK(nvoxels != NULL); 315 CHK(svx_tree_get_desc(oct, &tree_desc) == RES_OK); 316 CHK(tree_desc.type == SVX_OCTREE); 317 CHK(tree_desc.frame[0] == SVX_AXIS_X); 318 CHK(tree_desc.frame[1] == SVX_AXIS_Y); 319 CHK(tree_desc.frame[2] == SVX_AXIS_Z); 320 321 ocsz[0] = tree_desc.upper[0] - tree_desc.lower[0]; 322 ocsz[1] = tree_desc.upper[1] - tree_desc.lower[1]; 323 ocsz[2] = tree_desc.upper[2] - tree_desc.lower[2]; 324 delta[0] = ocsz[0]/(double)nvoxels[0]; 325 delta[1] = ocsz[1]/(double)nvoxels[1]; 326 delta[2] = ocsz[2]/(double)nvoxels[2]; 327 328 nvxls = nvoxels[0]; 329 nvxls = MMAX(nvxls, nvoxels[1]); 330 nvxls = MMAX(nvxls, nvoxels[2]); 331 332 ctx.depth = tree_desc.depth; 333 CHK(ctx.depth > 0); 334 335 while(ctx.depth-- != 0) { 336 const size_t iter = tree_desc.depth - ctx.depth - 1; 337 double pos[3]; 338 double low[3]; 339 double upp[3]; 340 341 FOR_EACH(x, 0, nvxls) { 342 pos[0] = tree_desc.lower[0] + ((double)x+1.0/(1u<<(2+iter)))*delta[0]; 343 low[0] = tree_desc.lower[0] + (double)x * delta[0]; 344 upp[0] = low[0] + delta[0]; 345 if(x*(size_t)(1u<<iter) >= nvoxels[0]) break; 346 347 FOR_EACH(y, 0, nvxls) { 348 pos[1] = tree_desc.lower[1] + ((double)y+1.0/(1u<<(2+iter)))*delta[1]; 349 low[1] = tree_desc.lower[1] + (double)y * delta[1]; 350 upp[1] = low[1] + delta[1]; 351 352 if(y*(size_t)(1u<<iter) >= nvoxels[1]) break; 353 354 FOR_EACH(z, 0, nvxls) { 355 struct svx_voxel vox; 356 uint32_t ui3[3]; 357 uint64_t mcode; 358 359 pos[2] = tree_desc.lower[2] + ((double)z+1.0/(1u<<(2+iter)))*delta[2]; 360 low[2] = tree_desc.lower[2] + (double)z * delta[2]; 361 upp[2] = low[2] + delta[2]; 362 363 if(z*(size_t)(1u<<iter) >= nvoxels[2]) break; 364 365 d3_set(ctx.position, pos); 366 CHK(svx_tree_at(oct, pos, max_lod, &ctx, &vox) == RES_OK); 367 CHK(!SVX_VOXEL_NONE(&vox)); 368 369 ui3[0] = (uint32_t)x * (1u << iter) + ((1u << iter) - 1); 370 ui3[1] = (uint32_t)y * (1u << iter) + ((1u << iter) - 1); 371 ui3[2] = (uint32_t)z * (1u << iter) + ((1u << iter) - 1); 372 ui3[0] = MMIN(ui3[0], (uint32_t)(nvoxels[0]-1)); 373 ui3[1] = MMIN(ui3[1], (uint32_t)(nvoxels[1]-1)); 374 ui3[2] = MMIN(ui3[2], (uint32_t)(nvoxels[2]-1)); 375 376 mcode = morton_xyz_encode_u21(ui3); 377 CHK(*((double*)vox.data) == mcode); 378 CHK(vox.is_leaf == (tree_desc.depth-1==ctx.depth)); 379 CHK(vox.depth == ctx.depth); 380 CHK(d3_eq_eps(vox.lower, low, 1.e-6)); 381 CHK(d3_eq_eps(vox.upper, upp, 1.e-6)); 382 } 383 } 384 } 385 386 nvxls = nvxls == 1 ? 0 : (nvxls + 1/*ceil*/) / 2; 387 delta[0] = MMIN(delta[0] * 2, ocsz[0]); 388 delta[1] = MMIN(delta[1] * 2, ocsz[1]); 389 delta[2] = MMIN(delta[2] * 2, ocsz[2]); 390 } 391 CHK(nvxls == 0); 392 } 393 394 int 395 main(int argc, char** argv) 396 { 397 struct svx_device* dev = NULL; 398 struct svx_tree* oct = NULL; 399 struct mem_allocator allocator; 400 struct svx_voxel_desc voxdesc = SVX_VOXEL_DESC_NULL; 401 struct svx_tree_desc tree_desc = SVX_TREE_DESC_NULL; 402 struct build_context build_ctx; 403 double low[3]; 404 double upp[3]; 405 size_t nvxls[3]; 406 struct leaves_context ctx; 407 void* ptr = (void*)(intptr_t)0xDECAFBAD; 408 FILE* stream = NULL; 409 (void)argc, (void)argv; 410 411 CHK(mem_init_proxy_allocator(&allocator, &mem_default_allocator) == RES_OK); 412 413 CHK(svx_device_create(NULL, &allocator, 1, &dev) == RES_OK); 414 415 d3_splat(low, 0.0); 416 d3_splat(upp, 1.0); 417 nvxls[0] = nvxls[1] = nvxls[2] = 5; 418 419 ctx.lower = low; 420 ctx.upper = upp; 421 ctx.nvoxels = nvxls; 422 ctx.depth = 4; 423 424 #define NEW_SCN svx_octree_create 425 426 voxdesc.get = get; 427 voxdesc.merge = keep_max; 428 voxdesc.challenge_merge = no_merge; 429 voxdesc.context = ptr; 430 voxdesc.size = sizeof(double); 431 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK); 432 433 CHK(svx_tree_ref_get(NULL) == RES_BAD_ARG); 434 CHK(svx_tree_ref_get(oct) == RES_OK); 435 CHK(svx_tree_ref_put(NULL) == RES_BAD_ARG); 436 CHK(svx_tree_ref_put(oct) == RES_OK); 437 CHK(svx_tree_ref_put(oct) == RES_OK); 438 439 upp[0] = low[0]; 440 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 441 upp[0] = 1.0; 442 443 nvxls[2] = 0; 444 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 445 nvxls[2] = nvxls[0]; 446 447 CHK(NEW_SCN(NULL, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 448 CHK(NEW_SCN(dev, NULL, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 449 CHK(NEW_SCN(dev, low, NULL, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 450 CHK(NEW_SCN(dev, low, upp, NULL, &voxdesc, &oct) == RES_BAD_ARG); 451 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, NULL) == RES_BAD_ARG); 452 453 voxdesc.get = NULL; 454 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 455 voxdesc.get = get; 456 457 voxdesc.merge = NULL; 458 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 459 voxdesc.merge = keep_max; 460 461 voxdesc.challenge_merge = NULL; 462 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 463 voxdesc.challenge_merge = no_merge; 464 465 voxdesc.size = 0; 466 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 467 voxdesc.size = sizeof(double); 468 469 voxdesc.size = SVX_MAX_SIZEOF_VOXEL + 1; 470 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG); 471 voxdesc.size = sizeof(double); 472 473 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK); 474 475 CHK(svx_tree_for_each_leaf(oct, check_leaves, &ctx) == RES_OK); 476 477 CHK(svx_tree_get_desc(NULL, &tree_desc) == RES_BAD_ARG); 478 CHK(svx_tree_get_desc(oct, NULL) == RES_BAD_ARG); 479 CHK(svx_tree_get_desc(oct, &tree_desc) == RES_OK); 480 CHK(tree_desc.nleaves == 5*5*5); 481 CHK(tree_desc.nvoxels == tree_desc.nleaves + 36/*#parents*/); 482 CHK(tree_desc.depth == 4); 483 CHK(tree_desc.type == SVX_OCTREE); 484 485 d3_splat(low, 0); 486 d3_splat(upp, 1); 487 CHK(d3_eq(tree_desc.lower, low)); 488 CHK(d3_eq(tree_desc.upper, upp)); 489 490 test_at_accessor(oct, nvxls); 491 492 CHK(stream = tmpfile()); 493 CHK(svx_tree_write(NULL, stream) == RES_BAD_ARG); 494 CHK(svx_tree_write(oct, NULL) == RES_BAD_ARG); 495 CHK(svx_tree_write(oct, stream) == RES_OK); 496 497 CHK(svx_tree_ref_put(oct) == RES_OK); 498 499 rewind(stream); 500 CHK(svx_tree_create_from_stream(NULL, stream, &oct) == RES_BAD_ARG); 501 CHK(svx_tree_create_from_stream(dev, NULL, &oct) == RES_BAD_ARG); 502 CHK(svx_tree_create_from_stream(dev, stream, NULL) == RES_BAD_ARG); 503 CHK(svx_tree_create_from_stream(dev, stream, &oct) == RES_OK); 504 fclose(stream); 505 506 test_at_accessor(oct, nvxls); 507 CHK(svx_tree_ref_put(oct) == RES_OK); 508 509 nvxls[0] = nvxls[1] = nvxls[2] = 32; 510 voxdesc.challenge_merge = merge_level0; 511 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK); 512 CHK(svx_tree_get_desc(oct, &tree_desc) == RES_OK); 513 CHK(tree_desc.nleaves == nvxls[0]*nvxls[1]*nvxls[2] / 8); 514 CHK(tree_desc.nvoxels == (tree_desc.nleaves*8 - 1) / 7); 515 CHK(tree_desc.depth == (size_t)log2i((int)(nvxls[0]/2))+1); 516 CHK(tree_desc.type == SVX_OCTREE); 517 518 dump_data(stdout, oct, TYPE_FLOAT, 1, write_scalars); 519 520 CHK(svx_tree_ref_put(oct) == RES_OK); 521 522 nvxls[0] = 32; 523 nvxls[1] = 16; 524 nvxls[2] = 33; 525 526 build_ctx.max_depth = (size_t)log2i 527 ((int)round_up_pow2(MMAX(MMAX(nvxls[0], nvxls[1]), nvxls[2]))); 528 529 d3_set(build_ctx.lower, low); 530 d3_set(build_ctx.upper, upp); 531 build_ctx.voxsz[0] = (upp[0]-low[0])/(double)nvxls[0]; 532 build_ctx.voxsz[1] = (upp[1]-low[1])/(double)nvxls[1]; 533 build_ctx.voxsz[2] = (upp[2]-low[2])/(double)nvxls[2]; 534 535 voxdesc.get = get_aabb; 536 voxdesc.merge = merge_aabb; 537 voxdesc.challenge_merge = challenge_aabb; 538 voxdesc.context = &build_ctx; 539 voxdesc.size = sizeof(struct aabb); 540 541 CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK); 542 543 #undef NEW_SCN 544 545 CHK(svx_device_ref_put(dev) == RES_OK); 546 CHK(svx_tree_ref_put(oct) == RES_OK); 547 548 check_memory_allocator(&allocator); 549 mem_shutdown_proxy_allocator(&allocator); 550 CHK(mem_allocated_size() == 0); 551 return 0; 552 } 553