star-2d

Contour structuring for efficient 2D geometric queries
git clone git://git.meso-star.fr/star-2d.git
Log | Files | Refs | README | LICENSE

commit 95987dd2f27fb613bd9ca9cb40f473e7179705e0
parent 9d8d2ff7ee24f94b92e507cacdaa719d19ea5888
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 27 Jun 2016 15:40:59 +0200

Implement and test the s2d_scene_sample function

Implement the s2d_primitive API

Diffstat:
Mcmake/CMakeLists.txt | 2++
Msrc/s2d_line_segments.c | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/s2d_line_segments.h | 6++++++
Asrc/s2d_primitive.c | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/s2d_scene.c | 135++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/s2d_scene_c.h | 7+++++++
Asrc/test_s2d_sample.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 503 insertions(+), 12 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -59,6 +59,7 @@ set(S2D_FILES_SRC s2d_device.c s2d_geometry.c s2d_line_segments.c + s2d_primitive.c s2d_scene.c s2d_shape.c) set(S2D_FILES_INC_API s2d.h) @@ -126,6 +127,7 @@ if(NOT NO_TEST) endfunction() new_test(test_s2d_device) + new_test(test_s2d_sample) new_test(test_s2d_scene) new_test(test_s2d_shape) new_test(test_s2d_trace_ray) diff --git a/src/s2d_line_segments.c b/src/s2d_line_segments.c @@ -35,6 +35,26 @@ /******************************************************************************* * Helper functions ******************************************************************************/ +static FINLINE float +line_compute_segment_length + (struct line_segments* lines, + const size_t isegment) +{ + const uint32_t* ids; + const float* pos; + const float* v0; + const float* v1; + const size_t id = isegment*2/*#ids per segment*/; + float tmp[2]; + ASSERT(lines && isegment < line_segments_get_nsegments(lines)); + + ids = line_segments_get_ids(lines); + pos = line_segments_get_pos(lines); + v0 = pos + ids[id+0]*2/*#coords*/; + v1 = pos + ids[id+1]*2/*#coords*/; + return f2_len(f2_sub(tmp, v1, v0)); +} + static void line_setup_indices (struct line_segments* lines, @@ -209,6 +229,7 @@ line_release(ref_T* ref) lines = CONTAINER_OF(ref, struct line_segments, ref); line_segments_clear(lines); dev = lines->dev; + darray_float_release(&lines->cdf); MEM_RM(dev->allocator, lines); S2D(device_ref_put(dev)); } @@ -232,6 +253,7 @@ line_segments_create(struct s2d_device* dev, struct line_segments** out_lines) ref_init(&lines->ref); S2D(device_ref_get(dev)); lines->dev = dev; + darray_float_init(dev->allocator, &lines->cdf); exit: *out_lines = lines; @@ -275,6 +297,7 @@ line_segments_clear(struct line_segments* lines) } lines->resize_mask = 0; lines->update_mask = 0; + darray_float_clear(&lines->cdf); } size_t @@ -326,28 +349,47 @@ line_segments_get_attr return darray_float_data_get(&lines->attribs[usage]->data); } +res_T +line_segments_compute_cdf(struct line_segments* lines) +{ + size_t iseg, nsegs; + float length = 0.f; + res_T res = RES_OK; + ASSERT(lines); + + darray_float_clear(&lines->cdf); + + nsegs = line_segments_get_nsegments(lines); + if(!nsegs) goto exit; + + res = darray_float_resize(&lines->cdf, nsegs); + if(res != RES_OK) goto error; + + FOR_EACH(iseg, 0, nsegs) { + length += line_compute_segment_length(lines, iseg); + darray_float_data_get(&lines->cdf)[iseg] = length; + } + +exit: + return res; +error: + darray_float_clear(&lines->cdf); + goto exit; +} + float line_segments_compute_length(struct line_segments* lines) { - const uint32_t* ids; - const float* pos; size_t iseg, nsegs; - float tmp[2]; float length = 0.f; ASSERT(lines); nsegs = line_segments_get_nsegments(lines); if(!nsegs) return 0.f; - ids = line_segments_get_ids(lines); - pos = line_segments_get_pos(lines); + FOR_EACH(iseg, 0, nsegs) + length += line_compute_segment_length(lines, iseg); - FOR_EACH(iseg, 0, nsegs) { - const size_t id = iseg * 2/*#ids per segment*/; - const float* v0 = pos + ids[id+0]*2/*#coords*/; - const float* v1 = pos + ids[id+1]*2/*#coords*/; - length += f2_len(f2_sub(tmp, v0, v1)); - } return length; } diff --git a/src/s2d_line_segments.h b/src/s2d_line_segments.h @@ -62,6 +62,7 @@ struct line_segments { /* Segmented contour */ struct index_buffer* indices; struct vertex_buffer* attribs[S2D_ATTRIBS_COUNT__]; enum s2d_type attribs_type[S2D_ATTRIBS_COUNT__]; + struct darray_float cdf; struct hit_filter filter; int resize_mask; /* Combination of buffer_type */ @@ -108,6 +109,11 @@ line_segments_get_attr (struct line_segments* lines, const enum s2d_attrib_usage usage); +/* Compute line segments Cumulative Distribution Function */ +extern LOCAL_SYM res_T +line_segments_compute_cdf + (struct line_segments* line); + extern LOCAL_SYM float line_segments_compute_length (struct line_segments* lines); diff --git a/src/s2d_primitive.c b/src/s2d_primitive.c @@ -0,0 +1,162 @@ +/* Copyright (C) |Meso|Star> 2016 (contact@meso-star.com) + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s2d.h" +#include "s2d_geometry.h" +#include "s2d_line_segments.h" + +#include <rsys/float2.h> + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +s2d_primitive_get_attrib + (const struct s2d_primitive* prim, + const enum s2d_attrib_usage usage, + const float s, + struct s2d_attrib* attrib) +{ + const uint32_t* ids; + struct geometry* geom = NULL; + res_T res = RES_OK; + + if(!prim + || !attrib + || (usage != S2D_GEOMETRY_NORMAL && (unsigned)usage >= S2D_ATTRIBS_COUNT__)) { + res = RES_BAD_ARG; + goto error; + } + + if(prim->geom_id == S2D_INVALID_ID + || prim->prim_id == S2D_INVALID_ID + || prim->mesh__ == NULL) { + res = RES_BAD_ARG; + goto error; + } + + /* Unnormalized barycentric coordinates */ + if(s < 0.f || s >= 1.f) { + res = RES_BAD_ARG; + goto error; + } + + geom = (struct geometry*)prim->mesh__; + + /* The line segments haven't tge required attrib */ + if(usage != S2D_GEOMETRY_NORMAL && !geom->lines->attribs[usage]) { + res = RES_BAD_ARG; + goto error; + } + + /* Out of bound primitive index */ + if(prim->prim_id >= line_segments_get_nsegments(geom->lines)) { + res = RES_BAD_ARG; + goto error; + } + + ids = line_segments_get_ids(geom->lines) + prim->prim_id * 2/*#segment ids*/; + attrib->usage = usage; + + if(usage == S2D_POSITION || usage == S2D_GEOMETRY_NORMAL) { + const float* v0; + const float* v1; + const float* pos; + + attrib->type = S2D_FLOAT2; + pos = line_segments_get_pos(geom->lines); + v0 = pos + ids[0] * 2; + v1 = pos + ids[1] * 2; + + if(usage == S2D_POSITION) { + float tmp[2]; + f2_mulf(attrib->value, v1, s); + f2_add(attrib->value, attrib->value, f2_mulf(tmp, v0, 1.f-s)); + } else { ASSERT(usage == S2D_GEOMETRY_NORMAL); + const float dx = v1[0] - v0[0]; + const float dy = v1[1] - v0[1]; + /* Build the segment normal with respect to edge orientation. + * Default is clock wise */ + if(geom->flip_contour) { + attrib->value[0] = -dy; + attrib->value[1] = dx; + } else { + attrib->value[0] = dy; + attrib->value[1] = -dx; + } + } + } else { + FATAL("Unimplemented attribute getter.\n"); + } + +exit: + return res; +error: + goto exit; +} + +res_T +s2d_primitive_sample + (const struct s2d_primitive* prim, + const float u, + float* s) +{ + if(!prim || S2D_PRIMITIVE_EQ(prim, &S2D_PRIMITIVE_NULL) || !s) + return RES_BAD_ARG; + + if(u < 0.f || u >= 1.f) + return RES_BAD_ARG; + + /* Only line segments primitives are currently supported. So the "u" + * canonical variable is directly the parametric coordinate of the sample + * onto the segment */ + *s = u; + return RES_OK; +} + +res_T +s2d_primitive_compute_length(const s2d_primitive* prim, float* length) +{ + const uint32_t* ids; + const float* pos; + const float* v0, *v1; + float tmp[2]; + struct geometry* geom; + + if(!prim || !length || S2D_PRIMITIVE_EQ(prim, &S2D_PRIMITIVE_NULL)) + return RES_BAD_ARG; + + geom = (struct geometry*)prim->mesh__; + pos = line_segments_get_pos(geom->lines); + ids = line_segments_get_ids(geom->lines) + prim->prim_id * 2/*#segment ids*/; + v0 = pos + ids[0] * 2/*#coords*/; + v1 = pos + ids[1] * 2/*#coords*/; + *length = f2_len(f2_sub(tmp, v1, v0)); + return RES_OK; +} + diff --git a/src/s2d_scene.c b/src/s2d_scene.c @@ -38,6 +38,8 @@ #include <rsys/float3.h> #include <rsys/mem_allocator.h> +#include <algorithm> + struct ray_extended : public RTCRay { struct s2d_scene* scene; void* data; /* User defined data */ @@ -369,6 +371,60 @@ scene_detach_shape return RES_OK; } +static res_T +scene_compute_cdf(struct s2d_scene* scn) +{ + struct list_node* node; + struct s2d_shape* shape; + struct geometry** pgeom; + struct geometry* geom; + size_t len; + float length = 0.f; + res_T res = RES_OK; + ASSERT(scn); + + darray_fltui_clear(&scn->cdf); + + LIST_FOR_EACH(node, &scn->shapes) { + + shape = CONTAINER_OF(node, struct s2d_shape, scene_attachment); + pgeom = htable_geom_find(&scn->cached_geoms, &shape); + ASSERT(pgeom != NULL); + geom = *pgeom; + + if(!geom->is_enabled) continue; + + res = line_segments_compute_cdf(geom->lines); + if(res != RES_OK) goto error; + len = darray_float_size_get(&geom->lines->cdf); + + if(len) { /* The geometry has valid segments */ + struct fltui fltui; + length += darray_float_cdata_get(&geom->lines->cdf)[len - 1]; + fltui.ui = geom->irtc; + fltui.flt = length; + res = darray_fltui_push_back(&scn->cdf, &fltui); + if(res != RES_OK) goto error; + } + } + +exit: + return res; +error: + darray_fltui_clear(&scn->cdf); + goto exit; +} + +static FINLINE bool +operator < (const struct fltui& it, const float val) +{ + /* This operator is used by the std::lower_bound algorithm that returns an + * iterator to the first element that is not less than val while one expect + * an iterator on the first element that is not less *or equal* than val. + * That's why we use <= rather than < */ + return it.flt <= val; +} + static void scene_compute_aabb(struct s2d_scene* scn) { @@ -419,7 +475,8 @@ scene_sync } if((session_mask & S2D_SAMPLE) != 0) { - FATAL("The S2D_SAMPLE session is not implemented yet!\n"); + res = scene_compute_cdf(scn); + if(res != RES_OK) goto error; } if((session_mask & S2D_GET_PRIMITIVE) != 0) { FATAL("The S2D_GET_PRIMITIVE session is not implemented yet!\n"); @@ -450,6 +507,7 @@ scene_release(ref_T* ref) if(scn->rtc_scn) rtcDeleteScene(scn->rtc_scn); htable_geom_release(&scn->cached_geoms); darray_geom_release(&scn->embree2geoms); + darray_fltui_release(&scn->cdf); MEM_RM(dev->allocator, scn); S2D(device_ref_put(dev)); } @@ -484,6 +542,7 @@ s2d_scene_create(struct s2d_device* dev, struct s2d_scene** out_scn) list_init(&scn->shapes); htable_geom_init(dev->allocator, &scn->cached_geoms); darray_geom_init(dev->allocator, &scn->embree2geoms); + darray_fltui_init(dev->allocator, &scn->cdf); ref_init(&scn->ref); S2D(device_ref_get(dev)); scn->dev = dev; @@ -678,3 +737,77 @@ s2d_scene_trace_ray return RES_OK; } +res_T +s2d_scene_sample + (struct s2d_scene* scn, + const float u, + const float v, + struct s2d_primitive* primitive, + float* s) +{ + struct geometry* geom; + const struct fltui* fltui_begin, *fltui_end, *fltui_found; + const float* flt_begin, *flt_end, *flt_found; + size_t igeom; + float f; + res_T res = RES_OK; + + if(!scn || !primitive || !s) { + res = RES_BAD_ARG; + goto error; + } + + if(u < 0.f || u >= 1.f || v < 0.f || v >= 1.f) { + log_error(scn->dev, + "%s: The submitted numbers are not canonical, i.e. they ar not in [0, 1[.\n", + __FUNCTION__); + res = RES_BAD_ARG; + goto error; + } + if((scn->session_mask & S2D_SAMPLE) == 0) { + log_error(scn->dev, + "%s: A S2D_SAMPLE session should be active onto the submitted scene.\n", + __FUNCTION__); + res = RES_BAD_OP; + goto error; + } + + /* Find the sampled geometry */ + if(darray_fltui_size_get(&scn->cdf) == 0) { /* No geometry to sample */ + *primitive = S2D_PRIMITIVE_NULL; + goto exit; + } else if(darray_fltui_size_get(&scn->cdf) == 1) { + igeom = darray_fltui_cdata_get(&scn->cdf)[0].ui; + f = u * darray_fltui_cdata_get(&scn->cdf)[0].flt;/* Map u to the CDF bounds */ + } else { + fltui_begin = darray_fltui_cdata_get(&scn->cdf); + fltui_end = fltui_begin + darray_fltui_size_get(&scn->cdf); + f = u * fltui_end[-1].flt; /* Map u to the CDF bounds */ + fltui_found = std::lower_bound(fltui_begin, fltui_end, f); + ASSERT(fltui_found != fltui_end); + igeom = fltui_found->ui; + + if(fltui_found != fltui_begin) /* Transform u to the geometry CDF bounds */ + f -= fltui_found[-1].flt; + } + geom = darray_geom_data_get(&scn->embree2geoms)[igeom]; + ASSERT(geom); + + /* Find the sampled segment */ + flt_begin = darray_float_cdata_get(&geom->lines->cdf); + flt_end = flt_begin + darray_float_size_get(&geom->lines->cdf); + flt_found = std::lower_bound(flt_begin, flt_end, f); + ASSERT(flt_found != flt_end); + + primitive->mesh__ = geom; + primitive->geom_id = geom->name; + primitive->prim_id = (unsigned)(flt_found - flt_begin); + primitive->scene_prim_id = 0/*TODO*/; + S2D(primitive_sample(primitive, v, s)); + +exit: + return res; +error: + goto exit; +} + diff --git a/src/s2d_scene_c.h b/src/s2d_scene_c.h @@ -59,10 +59,17 @@ geom_ptr_init__(struct mem_allocator* alloc, struct geometry** geom) #define HTABLE_KEY struct s2d_shape* #include <rsys/hash_table.h> +/* Generate the darray_fltui dynamic array */ +struct fltui { float flt; unsigned ui; }; +#define DARRAY_NAME fltui +#define DARRAY_DATA struct fltui +#include <rsys/dynamic_array.h> + struct s2d_scene { struct list_node shapes; /* List of attached shapes */ struct htable_geom cached_geoms; /* Cached shape geometries */ struct darray_geom embree2geoms; /* Shape geometries indexed by embree id */ + struct darray_fltui cdf; /* Unormalized CDF */ float lower[2], upper[2]; /* AABB of the scene */ diff --git a/src/test_s2d_sample.c b/src/test_s2d_sample.c @@ -0,0 +1,139 @@ +/* Copyright (C) |Meso|Star> 2016 (contact@meso-star.com) + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s2d.h" +#include "test_s2d_utils.h" + +#define NSAMPS 4096 + +static float +rand_canonic(void) +{ + int r; + while((r = rand()) == RAND_MAX); + return (float)r / (float)RAND_MAX; +} + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct s2d_vertex_data vdata; + struct s2d_device* dev; + struct s2d_scene* scn; + struct s2d_shape* shape; + struct s2d_primitive prim; + float s; + unsigned box_id; + int i; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + CHECK(s2d_device_create(NULL, &allocator, 1, &dev), RES_OK); + CHECK(s2d_scene_create(dev, &scn), RES_OK); + CHECK(s2d_shape_create_line_segments(dev, &shape), RES_OK); + + CHECK(s2d_shape_get_id(shape, &box_id), RES_OK); + CHECK(s2d_scene_attach_shape(scn, shape), RES_OK); + CHECK(s2d_scene_begin_session(scn, S2D_SAMPLE), RES_OK); + + CHECK(s2d_scene_sample(NULL, 1, 1, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 1, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 1, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 1, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 0, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 0, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 0, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 0, NULL, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 1, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 1, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 1, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 1, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 0, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 0, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 0, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 0, &prim, NULL), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 1, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 1, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 1, NULL,&s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 1, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 0, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 0, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 0, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 0, NULL, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 1, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 1, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 1, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 1, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 1, 0, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 1, 0, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(NULL, 0, 0, &prim, &s), RES_BAD_ARG); + CHECK(s2d_scene_sample(scn, 0, 0, &prim, &s), RES_OK); + CHECK(S2D_PRIMITIVE_EQ(&prim, &S2D_PRIMITIVE_NULL), 1); + + vdata.usage = S2D_POSITION; + vdata.type = S2D_FLOAT2; + vdata.get = box_get_position; + CHECK(s2d_line_segments_setup_indexed_vertices + (shape, box_nsegs, box_get_ids, box_nverts, &vdata, 1, (void*)&box_desc), + RES_OK); + + CHECK(s2d_scene_sample(scn, 0, 0, &prim, &s), RES_OK); + CHECK(S2D_PRIMITIVE_EQ(&prim, &S2D_PRIMITIVE_NULL), 1); + CHECK(s2d_scene_end_session(scn), RES_OK); + + CHECK(s2d_scene_begin_session(scn, S2D_SAMPLE), RES_OK); + CHECK(s2d_scene_sample(scn, 0, 0, &prim, &s), RES_OK); + CHECK(S2D_PRIMITIVE_EQ(&prim, &S2D_PRIMITIVE_NULL), 0); + + CHECK(prim.prim_id < 4, 1); + CHECK(prim.geom_id, box_id); + CHECK(s, 0); + + FOR_EACH(i, 0, NSAMPS) { + const float u = rand_canonic(); + const float v = rand_canonic(); + CHECK(s2d_scene_sample(scn, u, v, &prim, &s), RES_OK); + CHECK(S2D_PRIMITIVE_EQ(&prim, &S2D_PRIMITIVE_NULL), 0); + CHECK(prim.prim_id < 4, 1); + CHECK(prim.geom_id, box_id); + } + + CHECK(s2d_scene_end_session(scn), RES_OK); + + CHECK(s2d_device_ref_put(dev), RES_OK); + CHECK(s2d_scene_ref_put(scn), RES_OK); + CHECK(s2d_shape_ref_put(shape), RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHECK(mem_allocated_size(), 0); + return 0; +} +