star-vx

Structuring voxels for ray-tracing
git clone git://git.meso-star.fr/star-vx.git
Log | Files | Refs | README | LICENSE

commit da20b8fccea4de3285fade7986b79cb119c76ced
parent 3467e2d3dda84ea28cd0d10ac16c1759d7c09fb3
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu,  3 May 2018 15:24:52 +0200

Make the build process generic to the tree dimension

Diffstat:
Mcmake/CMakeLists.txt | 6+++---
Msrc/svx.h | 21+++++++++++++++++++++
Asrc/svx_buffer.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/svx_buffer.h | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/svx_c.h | 12++++++++++++
Msrc/svx_octree.c | 464++++++-------------------------------------------------------------------------
Msrc/svx_octree.h | 6+++---
Dsrc/svx_octree_buffer.c | 198-------------------------------------------------------------------------------
Dsrc/svx_octree_buffer.h | 248-------------------------------------------------------------------------------
Msrc/svx_octree_trace_ray.c | 16++++++++--------
Asrc/svx_tree_builder.h | 456+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 979 insertions(+), 894 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -41,14 +41,14 @@ set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(SVX_FILES_SRC + svx_buffer.c svx_device.c svx_octree.c - svx_octree_buffer.c svx_octree_trace_ray.c) set(SSOL_FILES_INC + svx_buffer.h svx_device.h - svx_octree.h - svx_octree_buffer.h) + svx_octree.h) set(SSOL_FILES_INC_API svx.h) diff --git a/src/svx.h b/src/svx.h @@ -165,6 +165,7 @@ struct mem_allocator; /* Forward declaration of opaque data types */ struct svx_device; struct svx_octree; +struct svx_bintree; BEGIN_DECLS @@ -236,5 +237,25 @@ svx_octree_at void* context, /* Client data sent as the last argument of the filter func */ struct svx_voxel* voxel); +/******************************************************************************* + * Binary tree + ******************************************************************************/ +SVX_API res_T +svx_bintree_create + (struct svx_device* dev, + const double lower, /* Lower bound of the bintree */ + const double upper, /* Upper bound of the bintree */ + const size_t nvoxels, /* # voxels along the range */ + const struct svx_voxel_desc* desc, /* Descriptor of a voxel */ + struct svx_bintree** bintree); + +SVX_API res_T +svx_bintree_ref_get + (struct svx_bintree* bintree); + +SVX_API res_T +svx_bintree_ref_put + (struct svx_bintree* bintree); + #endif /* SVX_H */ diff --git a/src/svx_buffer.c b/src/svx_buffer.c @@ -0,0 +1,198 @@ +/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "svx_buffer.h" + +#include <rsys/mem_allocator.h> + +#include <unistd.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE res_T +ensure_allocated_nodes(struct buffer* buf, const size_t nnodes) +{ + char* node_page = NULL; + size_t nnode_pages = 0; + res_T res = RES_OK; + ASSERT(buf); + + if(buf->node_head.ipage != BUFFER_INDEX_NULL.ipage + && buf->node_head.inode + nnodes <= buf->pagesize/sizeof(struct buffer_xnode)) + goto exit; + + nnode_pages = darray_page_size_get(&buf->node_pages); + if(nnode_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; } + ASSERT(nnode_pages == buf->node_head.ipage + 1); + + /* Alloc and register a node page containing the node and the far indices */ + node_page = MEM_ALLOC(buf->allocator, buf->pagesize); + if(!node_page) { res = RES_MEM_ERR; goto error; } + res = darray_page_push_back(&buf->node_pages, &node_page); + if(res != RES_OK) goto error; + + buf->node_head.inode = 0; + buf->node_head.ipage = (uint32_t)nnode_pages; + +exit: + return res; +error: + if(node_page) MEM_RM(buf->allocator, node_page); + CHK(darray_page_resize(&buf->node_pages, nnode_pages) == RES_OK); + goto exit; +} + +static INLINE res_T +ensure_allocated_attrs(struct buffer* buf, const size_t nattrs) +{ + char* attr_page = NULL; + size_t nattr_pages = 0; + res_T res = RES_OK; + ASSERT(buf); + + if(buf->attr_head.ipage != BUFFER_INDEX_NULL.ipage + && buf->attr_head.inode + nattrs <= buf->pagesize/buf->voxsize) + goto exit; + + nattr_pages = darray_page_size_get(&buf->attr_pages); + if(nattr_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; } + ASSERT(nattr_pages == buf->attr_head.ipage + 1); + + /* Alloc and register a attr page */ + attr_page = MEM_ALLOC(buf->allocator, buf->pagesize); + if(!attr_page) { res = RES_MEM_ERR; goto error; } + res = darray_page_push_back(&buf->attr_pages, &attr_page); + if(res != RES_OK) goto error; + + buf->attr_head.inode = 0; + buf->attr_head.ipage = (uint32_t)nattr_pages; + +exit: + return res; +error: + if(attr_page) MEM_RM(buf->allocator, attr_page); + CHK(darray_page_resize(&buf->attr_pages, nattr_pages) == RES_OK); + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +buffer_init + (struct mem_allocator* allocator, + const size_t voxel_size, + struct buffer* buf) +{ + ASSERT(buf && allocator); + memset(buf, 0, sizeof(struct buffer)); + buf->pagesize = (size_t)sysconf(_SC_PAGESIZE); + buf->voxsize = voxel_size; + darray_page_init(allocator, &buf->node_pages); + darray_page_init(allocator, &buf->attr_pages); + buf->node_head = BUFFER_INDEX_NULL; + buf->attr_head = BUFFER_INDEX_NULL; + buf->allocator = allocator; + CHK(buf->voxsize <= buf->pagesize); +} + +void +buffer_release(struct buffer* buf) +{ + ASSERT(buf); + buffer_clear(buf); + darray_page_release(&buf->node_pages); + darray_page_release(&buf->attr_pages); +} + +res_T +buffer_alloc_nodes + (struct buffer* buf, + const size_t nnodes, + struct buffer_index* first_node) +{ + res_T res = RES_OK; + ASSERT(buf && first_node); + + if(nnodes > buf->pagesize / sizeof(struct buffer_xnode)) + return RES_MEM_ERR; + + res = ensure_allocated_nodes(buf, nnodes); + if(res != RES_OK) return res; + + *first_node = buf->node_head; + buf->node_head.inode = (uint16_t)(buf->node_head.inode + nnodes); + return RES_OK; + +} + +res_T +buffer_alloc_attrs + (struct buffer* buf, + const size_t nattrs, + struct buffer_index* first_attr) +{ + res_T res = RES_OK; + ASSERT(buf && first_attr); + + if(nattrs > buf->pagesize / buf->voxsize) return RES_MEM_ERR; + + res = ensure_allocated_attrs(buf, nattrs); + if(res != RES_OK) return res; + + *first_attr = buf->attr_head; + buf->attr_head.inode = (uint16_t)(buf->attr_head.inode + nattrs); + return RES_OK; +} + +res_T +buffer_alloc_far_index + (struct buffer* buf, + struct buffer_index* id) +{ + size_t remaining_size; + size_t skipped_nnodes; + STATIC_ASSERT(sizeof(struct buffer_index) >= sizeof(struct buffer_xnode), + Unexpected_type_size); + + remaining_size = buf->pagesize - buf->node_head.inode*sizeof(struct buffer_xnode); + + /* Not enough memory in the current page */ + if(sizeof(struct buffer_index) > remaining_size) return RES_MEM_ERR; + + *id = buf->node_head; + skipped_nnodes = sizeof(struct buffer_index) / sizeof(struct buffer_xnode); + buf->node_head.inode = (uint16_t)(buf->node_head.inode + skipped_nnodes); + return RES_OK; +} + +void +buffer_clear(struct buffer* buf) +{ + size_t i; + ASSERT(buf); + FOR_EACH(i, 0, darray_page_size_get(&buf->node_pages)) { + MEM_RM(buf->allocator, darray_page_data_get(&buf->node_pages)[i]); + } + FOR_EACH(i, 0, darray_page_size_get(&buf->attr_pages)) { + MEM_RM(buf->allocator, darray_page_data_get(&buf->attr_pages)[i]); + } + darray_page_purge(&buf->node_pages); + darray_page_purge(&buf->attr_pages); + buf->node_head = BUFFER_INDEX_NULL; + buf->attr_head = BUFFER_INDEX_NULL; +} + diff --git a/src/svx_buffer.h b/src/svx_buffer.h @@ -0,0 +1,248 @@ +/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef SVX_BUFFER_H +#define SVX_BUFFER_H + +#include "svx_c.h" +#include <rsys/dynamic_array.h> + +/* + * Buffer containing the data of a tree. These data are partitioned in fixed + * size memory pages whose capacity is defined on buffer initialisation with + * respect to the page size of the system. + * + * The children of a node are stored consecutively into a page. The parent node + * directly references its first valid children excepted if they lie in a + * different page. In this case, the node references a `struct buffer_index', + * stored into the same page, that defines the absolute position of its first + * valid child into the whole list of node pages + * + * The data of the nodes are stored in their own memory pages. The attribs of + * the children of a node are stored consecutively into a page. If the page + * identifier of the attribs is the same of the page into which their parent + * node lies, then the node saves the index toward the first valid attrib into + * the page of attribs. In the other case, the node references a `struct + * buffer_index', stored into the same page of the node, that defines the + * absolute position of its first valid attrib into the buffer of attribs. + */ + +#define BUFFER_XNODE_FLAG_FAR_INDEX (1u<<15) +#define BUFFER_XNODE_MAX_CHILDREN_OFFSET (BUFFER_XNODE_FLAG_FAR_INDEX-1) +#define BUFFER_XNODE_MASK BUFFER_XNODE_MAX_CHILDREN_OFFSET + +struct buffer_xnode { + /* Offset to retrieve the children. If the BUFFER_XNODE_FLAG_FAR_INDEX bit is + * not set, the children are stored in the same page at the position `offset + * & BUFFER_XNODE_MASK'. If BUFFER_XNODE_FLAG_FAR_INDEX is set, `offset & + * BUFFER_XNODE_MASK' reference an buffer_index toward the node children */ + uint16_t node_offset; + uint16_t attr_offset; + uint8_t is_valid; /* Mask defining if the children are valid */ + uint8_t is_leaf; /* Mask defining if the children are leaves */ + uint16_t dummy__; /* Ensure a size of 8 Bytes */ +}; +STATIC_ASSERT(sizeof(struct buffer_xnode) == 8, + Unexpected_sizeof_buffer_xnode); + +#define BUFFER_INDEX_IPAGE_MAX UINT32_MAX +#define BUFFER_INDEX_INODE_MAX UINT16_MAX + +struct buffer_index { + uint32_t ipage; /* Identifier of the page */ + uint16_t inode; /* Identifier of the node in the page */ + uint16_t dummy__; /* Padding to ensure the tree index is 8 bytes lenght */ +}; +STATIC_ASSERT(sizeof(struct buffer_index) == 8, + Unexpected_sizeof_buffer_index); +#define BUFFER_INDEX_NULL__ {UINT32_MAX, UINT16_MAX, UINT16_MAX} +static const struct buffer_index BUFFER_INDEX_NULL = BUFFER_INDEX_NULL__; +#define BUFFER_INDEX_EQ(A, B) ((A)->inode==(B)->inode && (A)->ipage==(B)->ipage) + +/* Define the dynamic array of pages */ +#define DARRAY_NAME page +#define DARRAY_DATA char* +#include <rsys/dynamic_array.h> + +struct buffer { + size_t pagesize; /* Memory page size in bytes */ + size_t voxsize; /* Memory size of a voxel in bytes */ + + struct darray_page node_pages; /* List of pages storing nodes */ + struct darray_page attr_pages; /* List of pages storing node attributes */ + struct buffer_index node_head; /* Index of the next valid node */ + struct buffer_index attr_head; /* Index of the next valid attr */ + + struct mem_allocator* allocator; +}; + +extern LOCAL_SYM void +buffer_init + (struct mem_allocator* allocator, + const size_t sizeof_voxel, /* Size in bytes of a voxel */ + struct buffer* buf); + +extern LOCAL_SYM void +buffer_release + (struct buffer* buf); + +extern LOCAL_SYM res_T +buffer_alloc_nodes + (struct buffer* buf, + const size_t nnodes, + struct buffer_index* first_node); /* Index toward the 1st allocated node */ + +extern LOCAL_SYM res_T +buffer_alloc_attrs + (struct buffer* buf, + const size_t nattrs, + struct buffer_index* first_attr); /* Index toward the 1st allocated attrib */ + +/* Allocate an buffer_index in the current buffer page. Return RES_MEM_ERR if + * the node index cannot be allocated in the current page. In this case one + * have to alloc new nodes */ +extern LOCAL_SYM res_T +buffer_alloc_far_index + (struct buffer* buf, + struct buffer_index* id); /* Index toward the allocated far index */ + +extern LOCAL_SYM void +buffer_clear + (struct buffer* buf); + +static FINLINE int +buffer_is_empty(const struct buffer* buf) +{ + ASSERT(buf); + return darray_page_size_get(&buf->node_pages) == 0; +} + +static FINLINE struct buffer_xnode* +buffer_get_node + (struct buffer* buf, + const struct buffer_index id) +{ + char* mem; + ASSERT(buf && id.inode < buf->pagesize/sizeof(struct buffer_xnode)); + ASSERT(id.ipage < darray_page_size_get(&buf->node_pages)); + mem = darray_page_data_get(&buf->node_pages)[id.ipage]; + mem += id.inode * sizeof(struct buffer_xnode); + return (struct buffer_xnode*)mem; +} + +static FINLINE void* +buffer_get_attr + (struct buffer* buf, + const struct buffer_index id) +{ + char* mem; + ASSERT(buf && id.inode < buf->pagesize/buf->voxsize); + ASSERT(id.ipage < darray_page_size_get(&buf->attr_pages)); + mem = darray_page_data_get(&buf->attr_pages)[id.ipage]; + mem += id.inode * buf->voxsize; + return mem; +} + +static FINLINE struct buffer_index* +buffer_get_far_index + (struct buffer* buf, + const struct buffer_index id) +{ + char* mem; + ASSERT(buf && id.inode < buf->pagesize/sizeof(struct buffer_xnode)); + ASSERT(id.ipage < darray_page_size_get(&buf->node_pages)); + mem = darray_page_data_get(&buf->node_pages)[id.ipage]; + mem += id.inode * sizeof(struct buffer_xnode); + return (struct buffer_index*)mem; +} + +static FINLINE struct buffer_index +buffer_get_child_node_index + (struct buffer* buf, + const struct buffer_index id, + const int ichild) /* in [0, 7] */ +{ + struct buffer_index child_id = BUFFER_INDEX_NULL; + struct buffer_xnode* node = NULL; + uint16_t offset; + const int ichild_flag = BIT(ichild); + int ichild_off; + uint8_t mask; + + ASSERT(ichild >= 0 && ichild < 8 && buf); + + node = buffer_get_node(buf, id); + mask = (uint8_t)(node->is_valid & ~node->is_leaf); + ASSERT(mask & ichild_flag); + + /* Compute the child offset from the first child node */ + ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask)); + + offset = node->node_offset & BUFFER_XNODE_MASK; + if(!(node->node_offset & BUFFER_XNODE_FLAG_FAR_INDEX)) { + child_id.ipage = id.ipage; + child_id.inode = (uint16_t)(offset + ichild_off); + } else { + char* mem = darray_page_data_get(&buf->node_pages)[id.ipage]; + child_id = *(struct buffer_index*)(mem+offset*(sizeof(struct buffer_xnode))); + child_id.inode = (uint16_t)(child_id.inode + ichild_off); + } + return child_id; +} + +static FINLINE struct buffer_index +buffer_get_child_attr_index + (struct buffer* buf, + const struct buffer_index id, + const int ichild) /* In [0, 7] */ +{ + struct buffer_index child_id = BUFFER_INDEX_NULL; + struct buffer_xnode* node = NULL; + uint16_t offset; + const int ichild_flag = BIT(ichild); + int ichild_off; + uint8_t mask; + + ASSERT(ichild >= 0 && ichild < 8 && buf); + + node = buffer_get_node(buf, id); + mask = node->is_valid; + ASSERT(mask & ichild_flag); + + /* Compute the attr offset from the first child node */ + ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask)); + + offset = node->attr_offset & BUFFER_XNODE_MASK; + if(!(node->attr_offset & BUFFER_XNODE_FLAG_FAR_INDEX)) { + child_id.ipage = id.ipage; + child_id.inode = (uint16_t)(offset + ichild_off); + } else { + char* mem = darray_page_data_get(&buf->node_pages)[id.ipage]; + child_id = *(struct buffer_index*)(mem+offset*(sizeof(struct buffer_xnode))); + child_id.inode = (uint16_t)(child_id.inode + ichild_off); + } + return child_id; +} + +static FINLINE size_t +buffer_absolute_attr_index + (const struct buffer* buf, + const struct buffer_index index) +{ + ASSERT(buf); + return index.ipage * buf->pagesize/buf->voxsize + index.inode; +} + +#endif /* SVX_BUFFER_H */ diff --git a/src/svx_c.h b/src/svx_c.h @@ -16,6 +16,7 @@ #ifndef SVX_C_H #define SVX_C_H +#include "svx.h" #include <rsys/rsys.h> static INLINE uint64_t @@ -71,5 +72,16 @@ morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3]) xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0); } +static INLINE int +check_svx_voxel_desc(const struct svx_voxel_desc* desc) +{ + return desc + && desc->get + && desc->merge + && desc->challenge_merge + && desc->size > 0 + && desc->size <= SVX_MAX_SIZEOF_VOXEL; +} + #endif /* SVX_C_H */ diff --git a/src/svx_octree.c b/src/svx_octree.c @@ -18,422 +18,18 @@ #include "svx_device.h" #include "svx_octree.h" +#include "svx_tree_builder.h" + #include <rsys/double3.h> #include <rsys/mem_allocator.h> -#define OCTREE_DEPTH_MAX 16 /* Maximum depth of an octree */ - -struct voxel { - uint64_t mcode; /* Morton code of the voxel */ - void* data; /* Data of the voxel */ -}; -static const struct voxel VOXEL_NULL = {0, NULL}; - -struct octree_node { - struct octree_index ichild_node; /* Index of the 1st child node */ - struct octree_index ichild_attr; /* Index of the 1st child attr */ - uint8_t is_valid; /* Mask defining whether the children are valid or not */ - uint8_t is_leaf; /* Mask defining whether the children are leaves or not */ - ALIGN(16) char data[8][SVX_MAX_SIZEOF_VOXEL]; /* Data of the leaves */ -}; - -/* Stacked children of an octree node */ -struct stack { - struct octree_node nodes[8]; /* List of registered children nodes */ - uint8_t mask; /* Mask of valid children nodes (0 = empty) */ -}; - -struct octree_builder { - struct stack stacks[OCTREE_DEPTH_MAX]; - struct octree_buffer* buffer; - - const struct svx_voxel_desc* desc; - size_t nleaves; /* Number of emitted leaves */ - - int octree_depth; /* Maximum octree depth */ - int non_empty_lvl; /* Index of the 1st non empty level */ - - uint64_t mcode; /* Morton code of the last registered voxels */ -}; +/* Generate the tree_builder_3d data structure */ +#define TREE_DIMENSION 3 +#include "svx_tree_builder.h" /******************************************************************************* * Helper function ******************************************************************************/ -#ifndef NDEBUG -static void -check_octree(struct octree_buffer* buf, const struct octree_index root) -{ - const struct octree_xnode* node; - int ichild; - ASSERT(buf); - - node = octree_buffer_get_node(buf, root); - FOR_EACH(ichild, 0, 8) { - const int ichild_flag = BIT(ichild); - if((node->is_valid & ichild_flag) == 0) continue; - - if(node->is_leaf & ichild_flag) { - struct octree_index iattr; - iattr = octree_buffer_get_child_attr_index(buf, root, ichild); - ASSERT(octree_buffer_get_attr(buf, iattr) != NULL); - } else { - struct octree_index child; - child = octree_buffer_get_child_node_index(buf, root, ichild); - check_octree(buf, child); - } - } -} -#endif - -static INLINE int -check_svx_voxel_desc(const struct svx_voxel_desc* desc) -{ - return desc - && desc->get - && desc->merge - && desc->challenge_merge - && desc->size > 0 - && desc->size <= SVX_MAX_SIZEOF_VOXEL; -} - -static INLINE void -stack_clear(struct stack* stack) -{ - int inode; - FOR_EACH(inode, 0, 8) { - int ichild; - stack->nodes[inode].is_leaf = 0; - stack->nodes[inode].is_valid = 0; - stack->nodes[inode].ichild_node = OCTREE_INDEX_NULL; - stack->nodes[inode].ichild_attr = OCTREE_INDEX_NULL; - FOR_EACH(ichild, 0, 8) { - memset(stack->nodes[inode].data[ichild], 0, SVX_MAX_SIZEOF_VOXEL); - } - } - stack->mask = 0; -} - -/* Build a parent octree node from the registered stack nodes */ -static INLINE void -stack_setup_node - (struct stack* stack, - struct octree_node* node, - const struct svx_voxel_desc* desc) -{ - int ichild; - ASSERT(stack && node && check_svx_voxel_desc(desc)); - - node->ichild_node = OCTREE_INDEX_NULL; - node->ichild_attr = OCTREE_INDEX_NULL; - node->is_valid = stack->mask; - node->is_leaf = 0; - - if(stack->mask == 0) return; /* Empty stack */ - - /* Try to merge the child's leaves */ - FOR_EACH(ichild, 0, 8) { - const void* data[8]; - const uint8_t ichild_flag = (uint8_t)BIT(ichild); - struct octree_node* child = stack->nodes + ichild; - int igrandchild; - - /* Fetch the grandchildren data */ - FOR_EACH(igrandchild, 0, 8) { - data[igrandchild] = child->data[igrandchild]; - } - - desc->merge(node->data[ichild], data, 8, desc->context); - - if(child->is_leaf==0xFF && desc->challenge_merge(data, 8, desc->context)) { - /* The node becomes a leaf : the children does not exist anymore */ - node->is_leaf |= ichild_flag; - stack->mask ^= ichild_flag; - } - } -} - -static res_T -stack_write - (struct stack* stack, /* Node to write */ - struct octree_buffer* buf, /* Buffer where nodes are written */ - struct octree_index* out_index, /* Index of the first written node */ - size_t* out_nleaves) /* #writen leaves */ -{ - struct octree_index nodes_id = OCTREE_INDEX_NULL; - struct octree_node* node = NULL; - size_t nleaves = 0; - int inode; - res_T res = RES_OK; - ASSERT(stack && buf && out_index && out_nleaves); - - /* No registered nodes, this means that the nodes were merged in an higher - * level */ - if(!stack->mask) goto exit; - - /* Write the attrib of the children */ - FOR_EACH(inode, 0, 8) { - char* data = NULL; - size_t nattrs = 0; - size_t nvoxs = 0; - int ichild = 0; - - if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */ - - node = stack->nodes + inode; - - nattrs = (size_t)popcount(node->is_valid); - ASSERT(nattrs > 0); - - res = octree_buffer_alloc_attrs(buf, nattrs, &node->ichild_attr); - if(res != RES_OK) goto error; - - data = octree_buffer_get_attr(buf, node->ichild_attr); - nvoxs = 0; - FOR_EACH(ichild, 0, 8) { - if(!(node->is_valid & BIT(ichild))) continue; - memcpy(data + nvoxs*buf->voxsize, node->data[ichild], buf->voxsize); - ++nvoxs; - } - ASSERT(nvoxs == nattrs); - nleaves += (size_t)popcount(node->is_leaf); - } - - do { - struct octree_index index = OCTREE_INDEX_NULL; - struct octree_xnode* xnodes = NULL; - const size_t nnodes = (size_t)popcount(stack->mask); - size_t ixnode = 0; - - /* Alloc the octree nodes */ - res = octree_buffer_alloc_nodes(buf, (size_t)nnodes, &nodes_id); - if(res != RES_OK) goto error; - xnodes = octree_buffer_get_node(buf, nodes_id); - - FOR_EACH(inode, 0, 8) { - uint16_t attr_offset = UINT16_MAX; - uint16_t node_offset = UINT16_MAX; - - if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */ - node = stack->nodes + inode; - - /* Setup the offset toward the children and children attribs */ - if(node->ichild_node.ipage == nodes_id.ipage) { - node_offset = node->ichild_node.inode; - } - /* The page id of the children is not the same as that of node */ - if(node_offset > OCTREE_XNODE_MAX_CHILDREN_OFFSET) { - res = octree_buffer_alloc_far_index(buf, &index); - if(res != RES_OK) break; - *octree_buffer_get_far_index(buf, index) = node->ichild_node; - node_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode; - } - - /* Setup the offset toward the children attribs */ - if(node->ichild_attr.ipage == nodes_id.ipage) { - attr_offset = node->ichild_attr.inode; - } - - /* The page id of the attribs is not tthe same as that of node */ - if(attr_offset > OCTREE_XNODE_FLAG_FAR_INDEX) { - res = octree_buffer_alloc_far_index(buf, &index); - if(res != RES_OK) break; - *octree_buffer_get_far_index(buf, index) = node->ichild_attr; - attr_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode; - } - - xnodes[ixnode].node_offset = node_offset; - xnodes[ixnode].attr_offset = attr_offset; - xnodes[ixnode].is_valid = node->is_valid; - xnodes[ixnode].is_leaf = node->is_leaf; - ++ixnode; - } - /* inode < 8 <=> not enough memory in the current page. A far index could not - * be stored in the same page of its associated node. The write process was - * stoped. Rewrite the whole stacked nodes in a new page. */ - } while(inode < 8); - - -exit: - /* Return the index toward the first writen nodes */ - *out_index = nodes_id; - *out_nleaves = nleaves; - return res; -error: - goto exit; -} - -static res_T -octree_builder_init - (struct octree_builder* bldr, - const size_t definition, - const struct svx_voxel_desc* desc, - struct octree_buffer* buffer) -{ - int ilvl; - res_T res = RES_OK; - ASSERT(bldr && IS_POW2(definition) && check_svx_voxel_desc(desc)); - memset(bldr, 0, sizeof(struct octree_builder)); - - /* Compute the maximum depth of the octree */ - bldr->octree_depth = log2i((int)definition); - if(bldr->octree_depth > OCTREE_DEPTH_MAX) { - res = RES_MEM_ERR; - goto error; - } - - /* Init the per octree level stack */ - FOR_EACH(ilvl, 0, bldr->octree_depth) { - stack_clear(&bldr->stacks[ilvl]); - } - - octree_buffer_clear(buffer); - bldr->nleaves = 0; - bldr->desc = desc; - bldr->buffer = buffer; - bldr->non_empty_lvl = bldr->octree_depth - 1; - -exit: - return res; -error: - goto exit; -} - -static res_T -octree_builder_add_voxel - (struct octree_builder* bldr, - const struct voxel* vox) -{ - uint64_t mcode_xor; - int inode; - int ichild; - uint8_t ichild_flag; - res_T res = RES_OK; - ASSERT(bldr && vox && vox->mcode >= bldr->mcode); - - /* Define if the bits in [4 .. 63] of the previous and the next Morton - * codes are the same */ - mcode_xor = bldr->mcode ^ vox->mcode; - - /* The next voxel is not in the current node */ - if(mcode_xor >= 8) { - size_t ilvl; - - inode = (bldr->mcode >> 3) & 7; - bldr->stacks[0].mask |= (uint8_t)BIT(inode); - - /* Flush the stack of the octree level that does not contain the next - * voxel. The last octree level is actually the level that contains the - * root node while the penultimate describes the root node itself. These 2 - * levels contain all the voxels and can be skipped */ - FOR_EACH(ilvl, 0, bldr->octree_depth-2/*The 2 last leaves contain all voxels*/) { - struct octree_node* stack_node; - uint64_t mcode_max_lvl; - size_t nleaves; - - /* Compute the maximum morton code value for the current octree level */ - mcode_max_lvl = 8lu/*#children*/ * (1lu<<(3*(ilvl+1)))/*#voxels per children*/; - - if(mcode_xor < mcode_max_lvl) break; - - /* Retrieve the node index of the next level */ - inode = (bldr->mcode >> (3*(ilvl+2))) & 7; - - /* The next voxel is not in the ilvl^th stack. Setup the parent node of the - * nodes registered into the stack */ - stack_node = &bldr->stacks[ilvl+1].nodes[inode]; - stack_setup_node(&bldr->stacks[ilvl], stack_node, bldr->desc); - bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode); - - /* Write the nodes of the stack of the current octree level into the buf */ - res = stack_write - (&bldr->stacks[ilvl], bldr->buffer, &stack_node->ichild_node, &nleaves); - if(res != RES_OK) goto error; - - bldr->nleaves += nleaves; - if(nleaves) bldr->non_empty_lvl = MMIN(bldr->non_empty_lvl, (int)ilvl); - - /* Reset the current stack */ - stack_clear(&bldr->stacks[ilvl]); - } - } - - /* Retrieve the index of the current voxel and of its parent */ - ichild = vox->mcode & 7; - inode = (vox->mcode >> 3) & 7; - ichild_flag = (uint8_t)BIT(ichild); - - /* Register the voxel */ - memcpy(bldr->stacks[0].nodes[inode].data[ichild], vox->data, bldr->desc->size); - bldr->stacks[0].nodes[inode].is_valid |= ichild_flag; - bldr->stacks[0].nodes[inode].is_leaf |= ichild_flag; - - /* Update morton code of the last registered voxel */ - bldr->mcode = vox->mcode; - -exit: - return res; -error: - goto exit; -} - -static INLINE res_T -octree_builder_finalize - (struct octree_builder* bldr, - struct octree_index* root_id, - void* root_data) -{ - const void* data[8]; - size_t inode; - size_t nleaves; - int ilvl; - int ichild; - res_T res = RES_OK; - ASSERT(bldr); - - inode = (bldr->mcode >> 3) & 7; - bldr->stacks[0].mask |= (uint8_t)BIT(inode); - - /* Flush the stacked nodes */ - FOR_EACH(ilvl, 0, bldr->octree_depth-1) { - struct octree_node* parent_node; - - if(bldr->stacks[ilvl].mask == 0) continue; - - /* Retrieve the node index of the next level */ - inode = (bldr->mcode >> (3*(ilvl+2))) & 7; - - /* Setup the parent node of the nodes registered into the current stack */ - parent_node = &bldr->stacks[ilvl+1].nodes[inode]; /* Fetch the parent node */ - stack_setup_node(&bldr->stacks[ilvl], parent_node, bldr->desc); - bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode); - - /* Write the stacked nodes of the current level */ - res = stack_write - (&bldr->stacks[ilvl], bldr->buffer, &parent_node->ichild_node, &nleaves); - if(res != RES_OK) goto error; - bldr->nleaves += nleaves; - } - - ilvl = bldr->octree_depth-1; /* Root level */ - - /* Write the root node */ - res = stack_write(&bldr->stacks[ilvl], bldr->buffer, root_id, &nleaves); - if(res != RES_OK) goto error; - bldr->nleaves += nleaves; - - /* Setup the root attribs */ - ASSERT(bldr->stacks[ilvl].mask == 1); /* Only the root node is active */ - FOR_EACH(ichild, 0, 8) { - data[ichild] = bldr->stacks[ilvl].nodes[0].data[ichild]; - } - bldr->desc->merge(root_data, data, 8, bldr->desc->context); - -exit: - return res; -error: - goto exit; -} - static void octree_release(ref_T* ref) { @@ -441,7 +37,7 @@ octree_release(ref_T* ref) struct svx_device* dev; ASSERT(ref); oct = CONTAINER_OF(ref, struct svx_octree, ref); - octree_buffer_release(&oct->buffer); + buffer_release(&oct->buffer); dev = oct->dev; MEM_RM(dev->allocator, oct); SVX(device_ref_put(dev)); @@ -467,8 +63,8 @@ svx_octree_create uint64_t mcode; res_T res = RES_OK; - if(!dev || !lower || !upper || !nvoxels - || !check_svx_voxel_desc(desc) || !out_oct) { + if(!dev || !lower || !upper || !nvoxels || !check_svx_voxel_desc(desc) + || !out_oct) { res = RES_BAD_ARG; goto error; } @@ -500,7 +96,7 @@ svx_octree_create } ref_init(&oct->ref); SVX(device_ref_get(dev)); - octree_buffer_init(dev->allocator, desc->size, &oct->buffer); + buffer_init(dev->allocator, desc->size, &oct->buffer); oct->dev = dev; /* Compute the octree definition */ @@ -548,12 +144,12 @@ svx_octree_create if(res != RES_OK) goto error; oct->nleaves = bldr.nleaves; - oct->depth = (size_t)(bldr.octree_depth - bldr.non_empty_lvl) + oct->depth = (size_t)(bldr.tree_depth - bldr.non_empty_lvl) + 1 /* leaf level */; - ASSERT(bldr.octree_depth > bldr.non_empty_lvl); + ASSERT(bldr.tree_depth > bldr.non_empty_lvl); #ifndef NDEBUG - check_octree(&oct->buffer, oct->root); + octree_check(&oct->buffer, oct->root); #endif /* Setup the octree AABB in world space */ d3_set(oct->lower, lower); @@ -615,7 +211,7 @@ svx_octree_get_desc d3_set(desc->lower, oct->lower); d3_set(desc->upper, oct->upper); desc->nleaves = oct->nleaves; - desc->nvoxels = octree_buffer_absolute_attr_index + desc->nvoxels = buffer_absolute_attr_index (&oct->buffer, oct->buffer.attr_head) + 1; /* Root node */ desc->depth = oct->depth; return RES_OK; @@ -631,11 +227,11 @@ svx_octree_for_each_leaf void* ctx) { struct stack_entry { - struct octree_index inode; + struct buffer_index inode; size_t depth; double low[3]; double hsz[3]; /* Half size */ - } stack[OCTREE_DEPTH_MAX*8]; + } stack[TREE_DEPTH_MAX*8]; int istack; struct svx_voxel leaf = SVX_VOXEL_NULL; size_t ileaf = 0; @@ -657,10 +253,10 @@ svx_octree_for_each_leaf const size_t child_depth = entry.depth + 1; double child_hsz[3]; double mid[3]; /* Middle point of the current node */ - struct octree_xnode* node; + struct buffer_xnode* node; int ichild; - node = octree_buffer_get_node(&oct->buffer, entry.inode); + node = buffer_get_node(&oct->buffer, entry.inode); mid[0] = entry.low[0] + entry.hsz[0]; mid[1] = entry.low[1] + entry.hsz[1]; @@ -680,15 +276,15 @@ svx_octree_for_each_leaf low[2] = ichild&1 ? mid[2] : entry.low[2]; if(node->is_leaf & ichild_flag) { - struct octree_index iattr; + struct buffer_index iattr; - iattr = octree_buffer_get_child_attr_index + iattr = buffer_get_child_attr_index (&oct->buffer, entry.inode, ichild); d3_set(leaf.lower, low); d3_add(leaf.upper, low, entry.hsz); - leaf.data = octree_buffer_get_attr(&oct->buffer, iattr); - leaf.id = octree_buffer_absolute_attr_index(&oct->buffer, iattr); + leaf.data = buffer_get_attr(&oct->buffer, iattr); + leaf.id = buffer_absolute_attr_index(&oct->buffer, iattr); leaf.depth = child_depth; leaf.is_leaf = 1; @@ -696,7 +292,7 @@ svx_octree_for_each_leaf } else { struct stack_entry* top = stack + istack; - top->inode = octree_buffer_get_child_node_index + top->inode = buffer_get_child_node_index (&oct->buffer, entry.inode, ichild); d3_set(top->low, low); d3_set(top->hsz, child_hsz); @@ -717,8 +313,8 @@ svx_octree_at void* context, struct svx_voxel* voxel) { - struct octree_index inode; - struct octree_index iattr; + struct buffer_index inode; + struct buffer_index iattr; struct svx_voxel vox = SVX_VOXEL_NULL; double scale_exp2; double low[3]; @@ -762,14 +358,14 @@ svx_octree_at d3_set(vox.upper, oct->upper); if(filter) { vox.data = oct->root_attr; - vox.id = octree_buffer_absolute_attr_index(&oct->buffer, oct->buffer.attr_head); + vox.id = buffer_absolute_attr_index(&oct->buffer, oct->buffer.attr_head); if(!filter(&vox, position, context)) { *voxel = vox; goto exit; } } scale_exp2 = 0.5; inode = oct->root; for(;;) { - struct octree_xnode* node = octree_buffer_get_node(&oct->buffer, inode); + struct buffer_xnode* node = buffer_get_node(&oct->buffer, inode); int ichild; uint8_t ichild_flag; double mid[3]; @@ -793,7 +389,7 @@ svx_octree_at vox.is_leaf = (node->is_leaf & ichild_flag) != 0; if(filter || vox.is_leaf) { - iattr = octree_buffer_get_child_attr_index(&oct->buffer, inode, ichild); + iattr = buffer_get_child_attr_index(&oct->buffer, inode, ichild); /* Setup the voxel */ vox.lower[0] = low[0] * ocsz[0] + oct->oclow[0]; @@ -802,8 +398,8 @@ svx_octree_at vox.upper[0] = vox.lower[0] + ocsz[0] * scale_exp2; vox.upper[1] = vox.lower[1] + ocsz[1] * scale_exp2; vox.upper[2] = vox.lower[2] + ocsz[2] * scale_exp2; - vox.data = octree_buffer_get_attr(&oct->buffer, iattr); - vox.id = octree_buffer_absolute_attr_index(&oct->buffer, iattr); + vox.data = buffer_get_attr(&oct->buffer, iattr); + vox.id = buffer_absolute_attr_index(&oct->buffer, iattr); vox.is_leaf = (node->is_leaf & ichild_flag) != 0; if(vox.is_leaf || !filter(&vox, position, context)) { @@ -812,7 +408,7 @@ svx_octree_at } } - inode = octree_buffer_get_child_node_index(&oct->buffer, inode, ichild); + inode = buffer_get_child_node_index(&oct->buffer, inode, ichild); scale_exp2 *= 0.5; } diff --git a/src/svx_octree.h b/src/svx_octree.h @@ -16,7 +16,7 @@ #ifndef SVX_OCTREE_H #define SVX_OCTREE_H -#include "svx_octree_buffer.h" +#include "svx_buffer.h" #include <rsys/ref_count.h> struct svx_octree { @@ -26,8 +26,8 @@ struct svx_octree { double oclow[3], ocupp[3]; /* Adjusted World space AABB of the octree */ double ocsize[3]; /* World space size of the octree */ - struct octree_buffer buffer; - struct octree_index root; /* Index toward the children of the root */ + struct buffer buffer; + struct buffer_index root; /* Index toward the children of the root */ ALIGN(16) char root_attr[SVX_MAX_SIZEOF_VOXEL]; /* Attribute of the root */ size_t nleaves; /* #leaves */ diff --git a/src/svx_octree_buffer.c b/src/svx_octree_buffer.c @@ -1,198 +0,0 @@ -/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "svx_octree_buffer.h" - -#include <rsys/mem_allocator.h> - -#include <unistd.h> - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static INLINE res_T -ensure_allocated_nodes(struct octree_buffer* buf, const size_t nnodes) -{ - char* node_page = NULL; - size_t nnode_pages = 0; - res_T res = RES_OK; - ASSERT(buf); - - if(buf->node_head.ipage != OCTREE_INDEX_NULL.ipage - && buf->node_head.inode + nnodes <= buf->pagesize/sizeof(struct octree_xnode)) - goto exit; - - nnode_pages = darray_page_size_get(&buf->node_pages); - if(nnode_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; } - ASSERT(nnode_pages == buf->node_head.ipage + 1); - - /* Alloc and register a node page containing the node and the far indices */ - node_page = MEM_ALLOC(buf->allocator, buf->pagesize); - if(!node_page) { res = RES_MEM_ERR; goto error; } - res = darray_page_push_back(&buf->node_pages, &node_page); - if(res != RES_OK) goto error; - - buf->node_head.inode = 0; - buf->node_head.ipage = (uint32_t)nnode_pages; - -exit: - return res; -error: - if(node_page) MEM_RM(buf->allocator, node_page); - CHK(darray_page_resize(&buf->node_pages, nnode_pages) == RES_OK); - goto exit; -} - -static INLINE res_T -ensure_allocated_attrs(struct octree_buffer* buf, const size_t nattrs) -{ - char* attr_page = NULL; - size_t nattr_pages = 0; - res_T res = RES_OK; - ASSERT(buf); - - if(buf->attr_head.ipage != OCTREE_INDEX_NULL.ipage - && buf->attr_head.inode + nattrs <= buf->pagesize/buf->voxsize) - goto exit; - - nattr_pages = darray_page_size_get(&buf->attr_pages); - if(nattr_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; } - ASSERT(nattr_pages == buf->attr_head.ipage + 1); - - /* Alloc and register a attr page */ - attr_page = MEM_ALLOC(buf->allocator, buf->pagesize); - if(!attr_page) { res = RES_MEM_ERR; goto error; } - res = darray_page_push_back(&buf->attr_pages, &attr_page); - if(res != RES_OK) goto error; - - buf->attr_head.inode = 0; - buf->attr_head.ipage = (uint32_t)nattr_pages; - -exit: - return res; -error: - if(attr_page) MEM_RM(buf->allocator, attr_page); - CHK(darray_page_resize(&buf->attr_pages, nattr_pages) == RES_OK); - goto exit; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -void -octree_buffer_init - (struct mem_allocator* allocator, - const size_t voxel_size, - struct octree_buffer* buf) -{ - ASSERT(buf && allocator); - memset(buf, 0, sizeof(struct octree_buffer)); - buf->pagesize = (size_t)sysconf(_SC_PAGESIZE); - buf->voxsize = voxel_size; - darray_page_init(allocator, &buf->node_pages); - darray_page_init(allocator, &buf->attr_pages); - buf->node_head = OCTREE_INDEX_NULL; - buf->attr_head = OCTREE_INDEX_NULL; - buf->allocator = allocator; - CHK(buf->voxsize <= buf->pagesize); -} - -void -octree_buffer_release(struct octree_buffer* buf) -{ - ASSERT(buf); - octree_buffer_clear(buf); - darray_page_release(&buf->node_pages); - darray_page_release(&buf->attr_pages); -} - -res_T -octree_buffer_alloc_nodes - (struct octree_buffer* buf, - const size_t nnodes, - struct octree_index* first_node) -{ - res_T res = RES_OK; - ASSERT(buf && first_node); - - if(nnodes > buf->pagesize / sizeof(struct octree_xnode)) - return RES_MEM_ERR; - - res = ensure_allocated_nodes(buf, nnodes); - if(res != RES_OK) return res; - - *first_node = buf->node_head; - buf->node_head.inode = (uint16_t)(buf->node_head.inode + nnodes); - return RES_OK; - -} - -res_T -octree_buffer_alloc_attrs - (struct octree_buffer* buf, - const size_t nattrs, - struct octree_index* first_attr) -{ - res_T res = RES_OK; - ASSERT(buf && first_attr); - - if(nattrs > buf->pagesize / buf->voxsize) return RES_MEM_ERR; - - res = ensure_allocated_attrs(buf, nattrs); - if(res != RES_OK) return res; - - *first_attr = buf->attr_head; - buf->attr_head.inode = (uint16_t)(buf->attr_head.inode + nattrs); - return RES_OK; -} - -res_T -octree_buffer_alloc_far_index - (struct octree_buffer* buf, - struct octree_index* id) -{ - size_t remaining_size; - size_t skipped_nnodes; - STATIC_ASSERT(sizeof(struct octree_index) >= sizeof(struct octree_xnode), - Unexpected_type_size); - - remaining_size = buf->pagesize - buf->node_head.inode*sizeof(struct octree_xnode); - - /* Not enough memory in the current page */ - if(sizeof(struct octree_index) > remaining_size) return RES_MEM_ERR; - - *id = buf->node_head; - skipped_nnodes = sizeof(struct octree_index) / sizeof(struct octree_xnode); - buf->node_head.inode = (uint16_t)(buf->node_head.inode + skipped_nnodes); - return RES_OK; -} - -void -octree_buffer_clear(struct octree_buffer* buf) -{ - size_t i; - ASSERT(buf); - FOR_EACH(i, 0, darray_page_size_get(&buf->node_pages)) { - MEM_RM(buf->allocator, darray_page_data_get(&buf->node_pages)[i]); - } - FOR_EACH(i, 0, darray_page_size_get(&buf->attr_pages)) { - MEM_RM(buf->allocator, darray_page_data_get(&buf->attr_pages)[i]); - } - darray_page_purge(&buf->node_pages); - darray_page_purge(&buf->attr_pages); - buf->node_head = OCTREE_INDEX_NULL; - buf->attr_head = OCTREE_INDEX_NULL; -} - diff --git a/src/svx_octree_buffer.h b/src/svx_octree_buffer.h @@ -1,248 +0,0 @@ -/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef SVX_OCTREE_BUFFER_H -#define SVX_OCTREE_BUFFER_H - -#include "svx_c.h" -#include <rsys/dynamic_array.h> - -/* - * Buffer containing the data of an octree. These data are partitioned in fixed - * size memory pages whose capacity is defined on buffer initialisation with - * respect to the page size of the system. - * - * The children of a node are stored consecutively into a page. The parent node - * directly references its first valid children excepted if they lie in a - * different page. In this case, the node references a `struct octree_index', - * stored into the same page, that defines the absolute position of its first - * valid child into the whole list of node pages - * - * The data of the nodes are stored in their own memory pages. The attribs of - * the children of a node are stored consecutively into a page. If the page - * identifier of the attribs is the same of the page into which their parent - * node lies, then the node saves the index toward the first valid attrib into - * the page of attribs. In the other case, the node references a `struct - * octree_index', stored into the same page of the node, that defines the - * absolute position of its first valid attrib into the buffer of attribs. - */ - -#define OCTREE_XNODE_FLAG_FAR_INDEX (1u<<15) -#define OCTREE_XNODE_MAX_CHILDREN_OFFSET (OCTREE_XNODE_FLAG_FAR_INDEX-1) -#define OCTREE_XNODE_MASK OCTREE_XNODE_MAX_CHILDREN_OFFSET - -struct octree_xnode { - /* Offset to retrieve the children. If the OCTREE_XNODE_FLAG_FAR_INDEX bit is - * not set, the children are stored in the same page at the position `offset - * & OCTREE_XNODE_MASK'. If OCTREE_XNODE_FLAG_FAR_INDEX is set, `offset & - * OCTREE_XNODE_MASK' reference an octree_index toward the node children */ - uint16_t node_offset; - uint16_t attr_offset; - uint8_t is_valid; /* Mask defining if the children are valid */ - uint8_t is_leaf; /* Mask defining if the children are leaves */ - uint16_t dummy__; /* Ensure a size of 8 Bytes */ -}; -STATIC_ASSERT(sizeof(struct octree_xnode) == 8, - Unexpected_sizeof_octree_xnode); - -#define OCTREE_INDEX_IPAGE_MAX UINT32_MAX -#define OCTREE_INDEX_INODE_MAX UINT16_MAX - -struct octree_index { - uint32_t ipage; /* Identifier of the page */ - uint16_t inode; /* Identifier of the node in the page */ - uint16_t dummy__; /* Padding to ensure the octree index is 8 bytes lenght */ -}; -STATIC_ASSERT(sizeof(struct octree_index) == 8, - Unexpected_sizeof_octree_index); -#define OCTREE_INDEX_NULL__ {UINT32_MAX, UINT16_MAX, UINT16_MAX} -static const struct octree_index OCTREE_INDEX_NULL = OCTREE_INDEX_NULL__; -#define OCTREE_INDEX_EQ(A, B) ((A)->inode==(B)->inode && (A)->ipage==(B)->ipage) - -/* Define the dynamic array of pages */ -#define DARRAY_NAME page -#define DARRAY_DATA char* -#include <rsys/dynamic_array.h> - -struct octree_buffer { - size_t pagesize; /* Memory page size in bytes */ - size_t voxsize; /* Memory size of a voxel in bytes */ - - struct darray_page node_pages; /* List of pages storing nodes */ - struct darray_page attr_pages; /* List of pages storing node attributes */ - struct octree_index node_head; /* Index of the next valid node */ - struct octree_index attr_head; /* Index of the next valid attr */ - - struct mem_allocator* allocator; -}; - -extern LOCAL_SYM void -octree_buffer_init - (struct mem_allocator* allocator, - const size_t sizeof_voxel, /* Size in bytes of a voxel */ - struct octree_buffer* buf); - -extern LOCAL_SYM void -octree_buffer_release - (struct octree_buffer* buf); - -extern LOCAL_SYM res_T -octree_buffer_alloc_nodes - (struct octree_buffer* buf, - const size_t nnodes, - struct octree_index* first_node); /* Index toward the 1st allocated node */ - -extern LOCAL_SYM res_T -octree_buffer_alloc_attrs - (struct octree_buffer* buf, - const size_t nattrs, - struct octree_index* first_attr); /* Index toward the 1st allocated attrib */ - -/* Allocate an octree_index in the current buffer page. Return RES_MEM_ERR if - * the node index cannot be allocated in the current page. In this case one - * have to alloc new nodes */ -extern LOCAL_SYM res_T -octree_buffer_alloc_far_index - (struct octree_buffer* buf, - struct octree_index* id); /* Index toward the allocated far index */ - -extern LOCAL_SYM void -octree_buffer_clear - (struct octree_buffer* buf); - -static FINLINE int -octree_buffer_is_empty(const struct octree_buffer* buf) -{ - ASSERT(buf); - return darray_page_size_get(&buf->node_pages) == 0; -} - -static FINLINE struct octree_xnode* -octree_buffer_get_node - (struct octree_buffer* buf, - const struct octree_index id) -{ - char* mem; - ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode)); - ASSERT(id.ipage < darray_page_size_get(&buf->node_pages)); - mem = darray_page_data_get(&buf->node_pages)[id.ipage]; - mem += id.inode * sizeof(struct octree_xnode); - return (struct octree_xnode*)mem; -} - -static FINLINE void* -octree_buffer_get_attr - (struct octree_buffer* buf, - const struct octree_index id) -{ - char* mem; - ASSERT(buf && id.inode < buf->pagesize/buf->voxsize); - ASSERT(id.ipage < darray_page_size_get(&buf->attr_pages)); - mem = darray_page_data_get(&buf->attr_pages)[id.ipage]; - mem += id.inode * buf->voxsize; - return mem; -} - -static FINLINE struct octree_index* -octree_buffer_get_far_index - (struct octree_buffer* buf, - const struct octree_index id) -{ - char* mem; - ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode)); - ASSERT(id.ipage < darray_page_size_get(&buf->node_pages)); - mem = darray_page_data_get(&buf->node_pages)[id.ipage]; - mem += id.inode * sizeof(struct octree_xnode); - return (struct octree_index*)mem; -} - -static FINLINE struct octree_index -octree_buffer_get_child_node_index - (struct octree_buffer* buf, - const struct octree_index id, - const int ichild) /* in [0, 7] */ -{ - struct octree_index child_id = OCTREE_INDEX_NULL; - struct octree_xnode* node = NULL; - uint16_t offset; - const int ichild_flag = BIT(ichild); - int ichild_off; - uint8_t mask; - - ASSERT(ichild >= 0 && ichild < 8 && buf); - - node = octree_buffer_get_node(buf, id); - mask = (uint8_t)(node->is_valid & ~node->is_leaf); - ASSERT(mask & ichild_flag); - - /* Compute the child offset from the first child node */ - ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask)); - - offset = node->node_offset & OCTREE_XNODE_MASK; - if(!(node->node_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) { - child_id.ipage = id.ipage; - child_id.inode = (uint16_t)(offset + ichild_off); - } else { - char* mem = darray_page_data_get(&buf->node_pages)[id.ipage]; - child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode))); - child_id.inode = (uint16_t)(child_id.inode + ichild_off); - } - return child_id; -} - -static FINLINE struct octree_index -octree_buffer_get_child_attr_index - (struct octree_buffer* buf, - const struct octree_index id, - const int ichild) /* In [0, 7] */ -{ - struct octree_index child_id = OCTREE_INDEX_NULL; - struct octree_xnode* node = NULL; - uint16_t offset; - const int ichild_flag = BIT(ichild); - int ichild_off; - uint8_t mask; - - ASSERT(ichild >= 0 && ichild < 8 && buf); - - node = octree_buffer_get_node(buf, id); - mask = node->is_valid; - ASSERT(mask & ichild_flag); - - /* Compute the attr offset from the first child node */ - ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask)); - - offset = node->attr_offset & OCTREE_XNODE_MASK; - if(!(node->attr_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) { - child_id.ipage = id.ipage; - child_id.inode = (uint16_t)(offset + ichild_off); - } else { - char* mem = darray_page_data_get(&buf->node_pages)[id.ipage]; - child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode))); - child_id.inode = (uint16_t)(child_id.inode + ichild_off); - } - return child_id; -} - -static FINLINE size_t -octree_buffer_absolute_attr_index - (const struct octree_buffer* buf, - const struct octree_index index) -{ - ASSERT(buf); - return index.ipage * buf->pagesize/buf->voxsize + index.inode; -} - -#endif /* SVX_OCTREE_BUFFER_H */ diff --git a/src/svx_octree_trace_ray.c b/src/svx_octree_trace_ray.c @@ -57,7 +57,7 @@ uitof(const uint32_t ui) static FINLINE void setup_hit (struct svx_octree* oct, - const struct octree_index iattr, /* Index toward the voxel attributes */ + const struct buffer_index iattr, /* Index toward the voxel attributes */ const double distance, /* Hit distance */ const float corner[3], /* Corner of the current voxel in [1, 2]^3 space */ const double scale_exp2, /* Size of the voxel in [1, 2]^3] space */ @@ -71,9 +71,9 @@ setup_hit ASSERT(distance > 0); hit->distance = distance; - hit->voxel.data = octree_buffer_get_attr(&oct->buffer, iattr); + hit->voxel.data = buffer_get_attr(&oct->buffer, iattr); hit->voxel.depth = depth, - hit->voxel.id = octree_buffer_absolute_attr_index(&oct->buffer, iattr); + hit->voxel.id = buffer_absolute_attr_index(&oct->buffer, iattr); hit->voxel.is_leaf = is_leaf; /* Transform the voxel aabb in the [0, 1]^3 normalized space and flip it wrt @@ -175,10 +175,10 @@ trace_ray { #define SCALE_MAX 23 /* #mantisse bits */ struct stack_entry { - struct octree_index inode; + struct buffer_index inode; double t_max; } stack[SCALE_MAX + 1/*Dummy entry use to avoid invalid read*/]; - struct octree_index inode; + struct buffer_index inode; double t_min, t_max; float corner[3]; float scale_exp2; @@ -219,7 +219,7 @@ trace_ray /* Octree traversal */ scale_max = scale + 1; while(scale < scale_max && t_min < t_max) { - const struct octree_xnode* node = octree_buffer_get_node(&oct->buffer, inode); + const struct buffer_xnode* node = buffer_get_node(&oct->buffer, inode); double t_corner[3]; double t_max_corner; double t_max_child; @@ -245,7 +245,7 @@ trace_ray struct svx_hit hit_tmp; const double dst = t_min <= ray->range[0] ? t_max_child : t_min; const size_t depth = SCALE_MAX - scale; - const struct octree_index iattr = octree_buffer_get_child_attr_index + const struct buffer_index iattr = buffer_get_child_attr_index (&oct->buffer, inode, (int)ichild_adjusted); setup_hit(oct, iattr, dst, corner, scale_exp2, depth, is_leaf, @@ -286,7 +286,7 @@ trace_ray stack[scale].inode = inode; /* Get the node index of the traversed child */ - inode = octree_buffer_get_child_node_index + inode = buffer_get_child_node_index (&oct->buffer, inode, (int)ichild_adjusted); /* Define the id and the lower left corner of the first grand child */ diff --git a/src/svx_tree_builder.h b/src/svx_tree_builder.h @@ -0,0 +1,456 @@ +/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef TREE_DIMENSION + +#ifndef SVX_TREE_BUILDER_H +#define SVX_TREE_BUILDER_H + +#include "svx_buffer.h" + +#define TREE_DEPTH_MAX 16 /* Maximum depth of a tree */ + +struct voxel { + uint64_t mcode; /* Morton code of the voxel */ + void* data; /* Data of the voxel */ +}; +static const struct voxel VOXEL_NULL = {0, NULL}; + +#endif /* SVX_TREE_BUILDER_H */ +#else /* TREE_DIMENSION */ + +#if (TREE_DIMENSION<=0) || (TREE_DIMENSION>3) + #error "Invalid TREE_DIMENSION value" +#endif + +#if TREE_DIMENSION == 1 + #define XD(Name) CONCAT(bintree_, Name) +#elif TREE_DIMENSION == 2 + #define XD(Name) CONCAT(quadtree_, Name) +#elif TREE_DIMENSION == 3 + #define XD(Name) CONCAT(octree_, Name) +#else + #error "Invalid TREE_DIMENSION value" +#endif + +#define NCHILDREN BIT(TREE_DIMENSION) + +struct XD(node) { + struct buffer_index ichild_node; /* Index of the 1st child node */ + struct buffer_index ichild_attr; /* Index of the 1st child attr */ + uint8_t is_valid; /* Mask defining whether the children are valid or not */ + uint8_t is_leaf; /* Mask defining whether the children are leaves or not */ + ALIGN(16) char data[NCHILDREN][SVX_MAX_SIZEOF_VOXEL]; /* Data of the leaves */ +}; + +/* Stacked children of an tree node */ +struct XD(stack) { + struct XD(node) nodes[NCHILDREN]; /* List of registered children */ + uint8_t mask; /* Mask of valid children nodes (0 = empty) */ +}; + +struct XD(builder) { + struct XD(stack) stacks[TREE_DEPTH_MAX]; + struct buffer* buffer; + + const struct svx_voxel_desc* desc; + size_t nleaves; /* Number of emitted leaves */ + + int tree_depth; /* Maximum tree desc */ + int non_empty_lvl; /* Index of the 1st non empty level */ + + uint64_t mcode; /* Morton code of the voxel */ +}; + +/******************************************************************************* + * Stack functions + ******************************************************************************/ +#ifndef NDEBUG +static void +XD(check)(struct buffer* buf, const struct buffer_index root) +{ + const struct buffer_xnode* node; + int ichild; + ASSERT(buf); + + node = buffer_get_node(buf, root); + FOR_EACH(ichild, 0, NCHILDREN) { + const int ichild_flag = BIT(ichild); + if((node->is_valid & ichild_flag) == 0) continue; + + if(node->is_leaf & ichild_flag) { + struct buffer_index iattr; + iattr = buffer_get_child_attr_index(buf, root, ichild); + ASSERT(buffer_get_attr(buf, iattr) != NULL); + } else { + struct buffer_index child; + child = buffer_get_child_node_index(buf, root, ichild); + XD(check)(buf, child); + } + } +} +#endif + +static INLINE void +XD(stack_clear)(struct XD(stack)* stack) +{ + int inode; + FOR_EACH(inode, 0, NCHILDREN) { + int ichild; + stack->nodes[inode].is_leaf = 0; + stack->nodes[inode].is_valid = 0; + stack->nodes[inode].ichild_node = BUFFER_INDEX_NULL; + stack->nodes[inode].ichild_attr = BUFFER_INDEX_NULL; + FOR_EACH(ichild, 0, NCHILDREN) { + memset(stack->nodes[inode].data[ichild], 0, SVX_MAX_SIZEOF_VOXEL); + } + } + stack->mask = 0; +} + +/* Build a parent tree node from the registered stack nodes */ +static INLINE void +XD(stack_setup_node) + (struct XD(stack)* stack, + struct XD(node)* node, + const struct svx_voxel_desc* desc) +{ + int ichild; + ASSERT(stack && node && check_svx_voxel_desc(desc)); + + node->ichild_node = BUFFER_INDEX_NULL; + node->ichild_attr = BUFFER_INDEX_NULL; + node->is_valid = stack->mask; + node->is_leaf = 0; + + if(stack->mask == 0) return; /* Empty stack */ + + /* Try to merge the child's leaves */ + FOR_EACH(ichild, 0, NCHILDREN) { + const void* data[NCHILDREN]; + const uint8_t ichild_flag = (uint8_t)BIT(ichild); + struct XD(node)* child = stack->nodes + ichild; + int igrandchild; + + /* Fetch the grandchildren data */ + FOR_EACH(igrandchild, 0, NCHILDREN) { + data[igrandchild] = child->data[igrandchild]; + } + + desc->merge(node->data[ichild], data, NCHILDREN, desc->context); + + if(child->is_leaf == (BIT(NCHILDREN)-1)/*all active bitmask*/ + && desc->challenge_merge(data, NCHILDREN, desc->context)) { + /* The node becomes a leaf : the children does not exist anymore */ + node->is_leaf |= ichild_flag; + stack->mask ^= ichild_flag; + } + } +} + +static res_T +XD(stack_write) + (struct XD(stack)* stack, /* Node to write */ + struct buffer* buf, /* Buffer where nodes are written */ + struct buffer_index* out_index, /* Index of the first written node */ + size_t* out_nleaves) /* #writen leaves */ +{ + struct buffer_index nodes_id = BUFFER_INDEX_NULL; + struct XD(node)* node = NULL; + size_t nleaves = 0; + int inode; + res_T res = RES_OK; + ASSERT(stack && buf && out_index && out_nleaves); + + /* No registered nodes, this means that the nodes were merged in an higher + * level */ + if(!stack->mask) goto exit; + + /* Write the attrib of the children */ + FOR_EACH(inode, 0, NCHILDREN) { + char* data = NULL; + size_t nattrs = 0; + size_t nvoxs = 0; + int ichild = 0; + + if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */ + + node = stack->nodes + inode; + + nattrs = (size_t)popcount(node->is_valid); + ASSERT(nattrs > 0); + + res = buffer_alloc_attrs(buf, nattrs, &node->ichild_attr); + if(res != RES_OK) goto error; + + data = buffer_get_attr(buf, node->ichild_attr); + nvoxs = 0; + FOR_EACH(ichild, 0, NCHILDREN) { + if(!(node->is_valid & BIT(ichild))) continue; + memcpy(data + nvoxs*buf->voxsize, node->data[ichild], buf->voxsize); + ++nvoxs; + } + ASSERT(nvoxs == nattrs); + nleaves += (size_t)popcount(node->is_leaf); + } + + do { + struct buffer_index index = BUFFER_INDEX_NULL; + struct buffer_xnode* xnodes = NULL; + const size_t nnodes = (size_t)popcount(stack->mask); + size_t ixnode = 0; + + /* Alloc the tree nodes */ + res = buffer_alloc_nodes(buf, (size_t)nnodes, &nodes_id); + if(res != RES_OK) goto error; + xnodes = buffer_get_node(buf, nodes_id); + + FOR_EACH(inode, 0, NCHILDREN) { + uint16_t attr_offset = UINT16_MAX; + uint16_t node_offset = UINT16_MAX; + + if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */ + node = stack->nodes + inode; + + /* Setup the offset toward the children and children attribs */ + if(node->ichild_node.ipage == nodes_id.ipage) { + node_offset = node->ichild_node.inode; + } + /* The page id of the children is not the same as that of node */ + if(node_offset > BUFFER_XNODE_MAX_CHILDREN_OFFSET) { + res = buffer_alloc_far_index(buf, &index); + if(res != RES_OK) break; + *buffer_get_far_index(buf, index) = node->ichild_node; + node_offset = BUFFER_XNODE_FLAG_FAR_INDEX | index.inode; + } + + /* Setup the offset toward the children attribs */ + if(node->ichild_attr.ipage == nodes_id.ipage) { + attr_offset = node->ichild_attr.inode; + } + + /* The page id of the attribs is not tthe same as that of node */ + if(attr_offset > BUFFER_XNODE_FLAG_FAR_INDEX) { + res = buffer_alloc_far_index(buf, &index); + if(res != RES_OK) break; + *buffer_get_far_index(buf, index) = node->ichild_attr; + attr_offset = BUFFER_XNODE_FLAG_FAR_INDEX | index.inode; + } + + xnodes[ixnode].node_offset = node_offset; + xnodes[ixnode].attr_offset = attr_offset; + xnodes[ixnode].is_valid = node->is_valid; + xnodes[ixnode].is_leaf = node->is_leaf; + ++ixnode; + } + /* inode < NCHILDREN <=> not enough memory in the current page. A far index + * could not be stored in the same page of its associated node. The write + * process was stoped. Rewrite the whole stacked nodes in a new page. */ + } while(inode < NCHILDREN); + + +exit: + /* Return the index toward the first writen nodes */ + *out_index = nodes_id; + *out_nleaves = nleaves; + return res; +error: + goto exit; +} + + +/******************************************************************************* + * Builder functions + ******************************************************************************/ +static res_T +XD(builder_init) + (struct XD(builder)* bldr, + const size_t definition, + const struct svx_voxel_desc* desc, + struct buffer* buffer) +{ + int ilvl; + res_T res = RES_OK; + ASSERT(bldr && IS_POW2(definition) && check_svx_voxel_desc(desc)); + memset(bldr, 0, sizeof(struct XD(builder))); + + /* Compute the maximum depth of the tree */ + bldr->tree_depth = log2i((int)definition); + if(bldr->tree_depth > TREE_DEPTH_MAX) { + res = RES_MEM_ERR; + goto error; + } + + /* Init the per tree level stack */ + FOR_EACH(ilvl, 0, bldr->tree_depth) { + XD(stack_clear)(&bldr->stacks[ilvl]); + } + + buffer_clear(buffer); + bldr->nleaves = 0; + bldr->desc = desc; + bldr->buffer = buffer; + bldr->non_empty_lvl = bldr->tree_depth - 1; + +exit: + return res; +error: + goto exit; + +} + +static res_T +XD(builder_add_voxel) + (struct XD(builder)* bldr, + const struct voxel* vox) +{ + uint64_t mcode_xor; + int inode; + int ichild; + uint8_t ichild_flag; + res_T res = RES_OK; + ASSERT(bldr && vox && vox->mcode >= bldr->mcode); + + /* Define if the bits in [4 .. 63] of the previous and the next Morton + * codes are the same */ + mcode_xor = bldr->mcode ^ vox->mcode; + + /* The next voxel is not in the current node */ + if(mcode_xor >= 8) { + size_t ilvl; + + inode = (bldr->mcode >> TREE_DIMENSION) & (NCHILDREN-1); + bldr->stacks[0].mask |= (uint8_t)BIT(inode); + + /* Flush the stack of the tree level that does not contain the next voxel. + * The last tree level is actually the level that contains the root node + * while the penultimate describes the root node itself. These 2 levels + * contain all the voxels and can be skipped */ + FOR_EACH(ilvl, 0, bldr->tree_depth-2/*The 2 last voxels contain all voxels*/) { + struct XD(node)* stack_node; + uint64_t mcode_max_lvl; + size_t nleaves; + + /* Compute the maximum morton code value for the current tree level */ + mcode_max_lvl = + NCHILDREN/*#children*/ + * (1lu<<(TREE_DIMENSION*(ilvl+1)))/*#voxels per children*/; + + if(mcode_xor < mcode_max_lvl) break; + + /* Retrieve the node index of the next level */ + inode = (bldr->mcode >> (TREE_DIMENSION*(ilvl+2))) & (NCHILDREN-1); + + /* The next voxel is not in the ilvl^th stack. Setup the parent node of the + * nodes registered into the stack */ + stack_node = &bldr->stacks[ilvl+1].nodes[inode]; + XD(stack_setup_node)(&bldr->stacks[ilvl], stack_node, bldr->desc); + bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode); + + /* Write the nodes of the stack of the current tree level into the buf */ + res = XD(stack_write) + (&bldr->stacks[ilvl], bldr->buffer, &stack_node->ichild_node, &nleaves); + if(res != RES_OK) goto error; + + bldr->nleaves += nleaves; + if(nleaves) bldr->non_empty_lvl = MMIN(bldr->non_empty_lvl, (int)ilvl); + + /* Reset the current stack */ + XD(stack_clear)(&bldr->stacks[ilvl]); + } + } + + /* Retrieve the index of the current voxel and of its parent */ + ichild = vox->mcode & (NCHILDREN-1); + inode = (vox->mcode >> TREE_DIMENSION) & (NCHILDREN-1); + ichild_flag = (uint8_t)BIT(ichild); + + /* Register the voxel */ + memcpy(bldr->stacks[0].nodes[inode].data[ichild], vox->data, bldr->desc->size); + bldr->stacks[0].nodes[inode].is_valid |= ichild_flag; + bldr->stacks[0].nodes[inode].is_leaf |= ichild_flag; + + /* Update morton code of the last registered voxel */ + bldr->mcode = vox->mcode; + +exit: + return res; +error: + goto exit; +} + +static INLINE res_T +XD(builder_finalize) + (struct XD(builder)* bldr, + struct buffer_index* root_id, + void* root_data) +{ + const void* data[8]; + size_t inode; + size_t nleaves; + int ilvl; + int ichild; + res_T res = RES_OK; + ASSERT(bldr); + + inode = (bldr->mcode >> TREE_DIMENSION) & (NCHILDREN-1); + bldr->stacks[0].mask |= (uint8_t)BIT(inode); + + /* Flush the stacked nodes */ + FOR_EACH(ilvl, 0, bldr->tree_depth-1) { + struct XD(node)* parent_node; + + if(bldr->stacks[ilvl].mask == 0) continue; + + /* Retrieve the node index of the next level */ + inode = (bldr->mcode >> (TREE_DIMENSION*(ilvl+2))) & (NCHILDREN-1); + + /* Setup the parent node of the nodes registered into the current stack */ + parent_node = &bldr->stacks[ilvl+1].nodes[inode]; /* Fetch the parent node */ + XD(stack_setup_node)(&bldr->stacks[ilvl], parent_node, bldr->desc); + bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode); + + /* Write the stacked nodes of the current level */ + res = XD(stack_write) + (&bldr->stacks[ilvl], bldr->buffer, &parent_node->ichild_node, &nleaves); + if(res != RES_OK) goto error; + bldr->nleaves += nleaves; + } + + ilvl = bldr->tree_depth-1; /* Root level */ + + /* Write the root node */ + res = XD(stack_write)(&bldr->stacks[ilvl], bldr->buffer, root_id, &nleaves); + if(res != RES_OK) goto error; + bldr->nleaves += nleaves; + + /* Setup the root attribs */ + ASSERT(bldr->stacks[ilvl].mask == 1); /* Only the root node is active */ + FOR_EACH(ichild, 0, NCHILDREN) { + data[ichild] = bldr->stacks[ilvl].nodes[0].data[ichild]; + } + bldr->desc->merge(root_data, data, NCHILDREN, bldr->desc->context); + +exit: + return res; +error: + goto exit; +} + +#undef TREE_DIMENSION +#undef NCHILDREN +#undef XD + +#endif /* !TREE_DIMENSION */