Commit e7f8c7fc authored by David Flynn's avatar David Flynn
Browse files

geom: refactor octree coding into seperate files

The geometry coder is quite large, especially with trisoup and has no
benefit to being a header only implementation.  This commit moves the
geometry octree coder out of the header files and into
geometry_octree_{en,de}coder.cpp.
parent 1aa17083
......@@ -70,6 +70,7 @@ file(GLOB PROJECT_INC_FILES
"PCCTMC3Encoder.h"
"RAHT.h"
"TMC3.h"
"geometry_octree.h"
"hls.h"
"io_hls.h"
"io_tlv.h"
......@@ -90,6 +91,9 @@ file(GLOB PROJECT_CPP_FILES
"OctreeNeighMap.cpp"
"RAHT.cpp"
"TMC3.cpp"
"geometry_octree.cpp"
"geometry_octree_decoder.cpp"
"geometry_octree_encoder.cpp"
"io_hls.cpp"
"io_tlv.cpp"
"osspecific.cpp"
......
......@@ -37,6 +37,7 @@
#include "PCCMath.h"
#include "PCCTMC3Common.h"
#include "geometry_octree.h"
#include "ringbuf.h"
#include "tables.h"
......
......@@ -52,35 +52,6 @@ namespace pcc {
const uint32_t PCCTMC3MaxPredictionNearestNeighborCount = 3;
const uint32_t PCCTMC3SymbolCount = 64;
const int MAX_NUM_DM_LEAF_POINTS = 2;
struct PCCOctree3Node {
// 3D position of the current node's origin (local x,y,z = 0).
PCCVector3<uint32_t> pos;
// Range of point indexes spanned by node
uint32_t start;
uint32_t end;
// address of the current node in 3D morton order.
uint64_t mortonIdx;
// pattern denoting occupied neighbour nodes.
// 32 8 (y)
// |/
// 2--n--1 (x)
// /|
// 4 16 (z)
uint8_t neighPattern = 0;
// The current node's number of siblings plus one.
// ie, the number of child nodes present in this node's parent.
uint8_t numSiblingsPlus1;
// The occupancy map used describing the current node and its siblings.
uint8_t siblingOccupancy;
};
// Structure for sorting weights.
struct WeightWithIndex {
float weight;
......@@ -550,147 +521,6 @@ PCCComputePredictors(
}
}
//---------------------------------------------------------------------------
// Update the neighbour pattern flags for a node and the 'left' neighbour on
// each axis. This update should be applied to each newly inserted node.
//
// @param siblingRestriction limits neighbours to direct siblings of child
inline void
updateGeometryNeighState(
bool siblingRestriction,
const ringbuf<PCCOctree3Node>::iterator& bufEnd,
int64_t numNodesNextLvl,
int childSizeLog2,
PCCOctree3Node& child,
int childIdx,
uint8_t neighPattern,
uint8_t parentOccupancy)
{
uint64_t midx;
if (!siblingRestriction) {
midx = child.mortonIdx = mortonAddr(child.pos, childSizeLog2);
}
static const struct {
int childIdxBitPos;
int axis;
int patternFlagUs;
int patternFlagThem;
} neighParamMap[] = {
{4, 2, 1 << 1, 1 << 0}, // x
{2, 1, 1 << 2, 1 << 3}, // y
{1, 0, 1 << 4, 1 << 5}, // z
};
for (const auto& param : neighParamMap) {
// skip expensive check if parent's flags indicate adjacent neighbour
// is not present.
if ((childIdx & param.childIdxBitPos) == 0) {
// $axis co-ordinate = 0
if (parentOccupancy & (1 << (childIdx + param.childIdxBitPos)))
child.neighPattern |= param.patternFlagThem;
if (!(neighPattern & param.patternFlagUs))
continue;
} else {
if (parentOccupancy & (1 << (childIdx - param.childIdxBitPos)))
child.neighPattern |= param.patternFlagUs;
// no external search is required for $axis co-ordinate = 1
continue;
}
if (siblingRestriction)
continue;
// calculate the morton address of the 'left' neighbour,
// the delta is then used as the starting position for a search
int64_t mortonIdxNeigh =
morton3dAxisDec(midx, param.axis) & ~0x8000000000000000ull;
int64_t mortonDelta = midx - mortonIdxNeigh;
if (mortonDelta < 0) {
// no neighbour due to being in zero-th col/row/plane
continue;
}
// NB: fifo already contains current node, no point searching it
auto posEnd = bufEnd;
std::advance(posEnd, -1);
auto posStart = bufEnd;
std::advance(posStart, -std::min(numNodesNextLvl, mortonDelta + 2));
auto found = std::lower_bound(
posStart, posEnd, mortonIdxNeigh,
[](const PCCOctree3Node& node, uint64_t mortonIdx) {
return node.mortonIdx < mortonIdx;
});
// NB: found is always valid (see posEnd) => can skip check.
if (found->mortonIdx != mortonIdxNeigh) {
// neighbour isn't present => must have been empty
continue;
}
// update both node's neighbour pattern
// NB: neighours being present implies occupancy
child.neighPattern |= param.patternFlagUs;
found->neighPattern |= param.patternFlagThem;
}
}
//---------------------------------------------------------------------------
// Determine if direct coding is permitted.
// If tool is enabled:
// - Block must not be near the bottom of the tree
// - The parent / grandparent are sparsely occupied
inline bool
isDirectModeEligible(
bool featureEnabled,
int nodeSizeLog2,
const PCCOctree3Node& node,
const PCCOctree3Node& child)
{
return featureEnabled && (nodeSizeLog2 >= 2) && (node.neighPattern == 0)
&& (child.numSiblingsPlus1 == 1) && (node.numSiblingsPlus1 <= 2);
}
//---------------------------------------------------------------------------
struct CtxModelOctreeOccupancy {
o3dgc::Adaptive_Bit_Model b0[10];
o3dgc::Adaptive_Bit_Model b1[2 * 10];
o3dgc::Adaptive_Bit_Model b2[4 * 10];
o3dgc::Adaptive_Bit_Model b3[8 * 10];
o3dgc::Adaptive_Bit_Model b4[75];
o3dgc::Adaptive_Bit_Model b5[112];
o3dgc::Adaptive_Bit_Model b6[117];
o3dgc::Adaptive_Bit_Model b7[127];
CtxModelOctreeOccupancy()
{
for (auto& ctx : b0)
ctx.reset(true);
for (auto& ctx : b1)
ctx.reset(true);
for (auto& ctx : b2)
ctx.reset(true);
for (auto& ctx : b3)
ctx.reset(true);
for (auto& ctx : b4)
ctx.reset(true);
for (auto& ctx : b5)
ctx.reset(true);
for (auto& ctx : b6)
ctx.reset(true);
for (auto& ctx : b7)
ctx.reset(true);
}
};
//---------------------------------------------------------------------------
} // namespace pcc
......
......@@ -38,6 +38,7 @@
#include <assert.h>
#include <functional>
#include <map>
#include <queue>
#include <string>
#include "ringbuf.h"
......@@ -302,431 +303,9 @@ private:
return 0;
}
//-------------------------------------------------------------------------
// Decode the number of points in a leaf node of the octree.
int decodePositionLeafNumPoints(
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Adaptive_Bit_Model& ctxSinglePointPerBlock,
o3dgc::Static_Bit_Model& ctxEquiProb,
o3dgc::Adaptive_Bit_Model& ctxPointCountPerBlock)
{
const bool isSinglePoint =
arithmeticDecoder->decode(ctxSinglePointPerBlock) != 0;
int count = 1;
if (!isSinglePoint) {
count = 1
+ arithmeticDecoder->ExpGolombDecode(
0, ctxEquiProb, ctxPointCountPerBlock);
}
return count;
}
//-------------------------------------------------------------------------
// map the @occupancy pattern bits to take into account symmetries in the
// neighbour configuration @neighPattern.
//
uint8_t mapGeometryOccupancyInv(uint8_t occupancy, uint8_t neighPattern)
{
switch (kOccMapRotateXIdFromPattern[neighPattern]) {
case 1: occupancy = kOccMapRotateX270[occupancy]; break;
case 2: occupancy = kOccMapRotateX270Y180[occupancy]; break;
case 3: occupancy = kOccMapRotateX090Y180[occupancy]; break;
}
if (kOccMapRotateYIdFromPattern[neighPattern]) {
occupancy = kOccMapRotateY090[occupancy];
}
bool flag_ud = (neighPattern & 16) && !(neighPattern & 32);
if (flag_ud) {
occupancy = kOccMapMirrorXY[occupancy];
}
switch (kOccMapRotateZIdFromPatternXY[neighPattern & 15]) {
case 1: occupancy = kOccMapRotateZ270[occupancy]; break;
case 2: occupancy = kOccMapRotateZ180[occupancy]; break;
case 3: occupancy = kOccMapRotateZ090[occupancy]; break;
}
return occupancy;
}
//---------------------------------------------------------------------------
// decode occupancy bits (neighPattern10 == 0 case)
int decodeOccupancyNeighZ(
o3dgc::Arithmetic_Codec* arithmeticDecoder,
CtxModelOctreeOccupancy& ctxOccupancy)
{
int numOccupiedAcc = 0;
int bit;
int occupancy = 0;
bit = arithmeticDecoder->decode(ctxOccupancy.b0[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 1;
bit = arithmeticDecoder->decode(ctxOccupancy.b1[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 7;
bit = arithmeticDecoder->decode(ctxOccupancy.b2[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 5;
bit = arithmeticDecoder->decode(ctxOccupancy.b3[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 3;
bit = arithmeticDecoder->decode(ctxOccupancy.b4[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 2;
bit = arithmeticDecoder->decode(ctxOccupancy.b5[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 4;
// NB: There must be at least two occupied child nodes
// -- avoid coding the occupancyB if it is implied.
bit = 1;
if (numOccupiedAcc >= 1)
bit = arithmeticDecoder->decode(ctxOccupancy.b6[numOccupiedAcc]);
numOccupiedAcc += bit;
occupancy |= bit << 6;
bit = 1;
if (numOccupiedAcc >= 2)
bit = arithmeticDecoder->decode(ctxOccupancy.b7[numOccupiedAcc]);
occupancy |= bit << 0;
return occupancy;
}
//---------------------------------------------------------------------------
// decode occupancy bits (neighPattern10 != 0 case)
int decodeOccupancyNeighNZ(
o3dgc::Arithmetic_Codec* arithmeticDecoder,
CtxModelOctreeOccupancy& ctxOccupancy,
int neighPattern10)
{
int occupancy = 0;
int partialOccupancy = 0;
int idx;
int bit;
idx = neighPattern10;
bit = arithmeticDecoder->decode(ctxOccupancy.b0[idx]);
partialOccupancy |= bit << 0;
occupancy |= bit << 1;
idx = (neighPattern10 << 1) + partialOccupancy;
bit = arithmeticDecoder->decode(ctxOccupancy.b1[idx]);
partialOccupancy |= bit << 1;
occupancy |= bit << 7;
idx = (neighPattern10 << 2) + partialOccupancy;
bit = arithmeticDecoder->decode(ctxOccupancy.b2[idx]);
partialOccupancy |= bit << 2;
occupancy |= bit << 5;
idx = (neighPattern10 << 3) + partialOccupancy;
bit = arithmeticDecoder->decode(ctxOccupancy.b3[idx]);
partialOccupancy |= bit << 3;
occupancy |= bit << 3;
// todo(df): merge constants into lut.
idx = ((neighPattern10 - 1) << 4) + partialOccupancy;
idx = kOccMapBit4CtxIdx[idx] - 1 + 5;
bit = arithmeticDecoder->decode(ctxOccupancy.b4[idx]);
partialOccupancy |= bit << 4;
occupancy |= bit << 2;
idx = ((neighPattern10 - 1) << 5) + partialOccupancy;
idx = kOccMapBit5CtxIdx[idx] - 1 + 6;
bit = arithmeticDecoder->decode(ctxOccupancy.b5[idx]);
partialOccupancy |= bit << 5;
occupancy |= bit << 4;
int neighPattern7 = kNeighPattern10to7[neighPattern10];
idx = ((neighPattern7 - 1) << 6) + partialOccupancy;
idx = kOccMapBit6CtxIdx[idx] - 1 + 7;
bit = arithmeticDecoder->decode(ctxOccupancy.b6[idx]);
partialOccupancy += bit << 6;
occupancy |= bit << 6;
int neighPattern5 = kNeighPattern7to5[neighPattern7];
idx = ((neighPattern5 - 1) << 7) + partialOccupancy;
idx = kOccMapBit7CtxIdx[idx] - 1 + 8;
// NB: if firt 7 bits are 0, then the last is implicitly 1.
bit = 1;
if (partialOccupancy)
bit = arithmeticDecoder->decode(ctxOccupancy.b7[idx]);
occupancy |= bit << 0;
return occupancy;
}
//-------------------------------------------------------------------------
// decode node occupancy bits
//
uint32_t decodeGeometryOccupancy(
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Adaptive_Bit_Model& ctxSingleChild,
o3dgc::Static_Bit_Model& ctxEquiProb,
CtxModelOctreeOccupancy& ctxOccupancy,
const PCCOctree3Node& node0)
{
// neighbouring configuration with reduction from 64 to 10
int neighPattern = node0.neighPattern;
int neighPattern10 = kNeighPattern64to10[neighPattern];
// decode occupancy pattern
uint32_t occupancy;
if (neighPattern10 == 0) {
// neighbour empty and only one point => decode index, not pattern
if (arithmeticDecoder->decode(ctxSingleChild)) {
uint32_t cnt = arithmeticDecoder->decode(ctxEquiProb);
cnt |= arithmeticDecoder->decode(ctxEquiProb) << 1;
cnt |= arithmeticDecoder->decode(ctxEquiProb) << 2;
occupancy = 1 << cnt;
} else {
occupancy = decodeOccupancyNeighZ(arithmeticDecoder, ctxOccupancy);
}
} else {
occupancy = decodeOccupancyNeighNZ(
arithmeticDecoder, ctxOccupancy, neighPattern10);
occupancy = mapGeometryOccupancyInv(occupancy, neighPattern);
}
return occupancy;
}
//-------------------------------------------------------------------------
// Decode a position of a point in a given volume.
PCCVector3<uint32_t> decodePointPosition(
int nodeSizeLog2,
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Static_Bit_Model& ctxPointPosBlock)
{
PCCVector3<uint32_t> delta{};
for (int i = nodeSizeLog2; i > 0; i--) {
delta <<= 1;
delta[0] |= arithmeticDecoder->decode(ctxPointPosBlock);
delta[1] |= arithmeticDecoder->decode(ctxPointPosBlock);
delta[2] |= arithmeticDecoder->decode(ctxPointPosBlock);
}
return delta;
}
//-------------------------------------------------------------------------
// Direct coding of position of points in node (early tree termination).
// Decoded points are written to @outputPoints
// Returns the number of points emitted.
template<class OutputIt>
int decodeDirectPosition(
int nodeSizeLog2,
const PCCOctree3Node& node,
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Adaptive_Bit_Model& ctxBlockSkipTh,
o3dgc::Adaptive_Bit_Model& ctxNumIdcmPointsEq1,
o3dgc::Static_Bit_Model& ctxPointPosBlock,
OutputIt outputPoints)
{
bool isDirectMode = arithmeticDecoder->decode(ctxBlockSkipTh);
if (!isDirectMode) {
return 0;
}
int numPoints = 1;
if (arithmeticDecoder->decode(ctxNumIdcmPointsEq1))
numPoints++;
for (int i = 0; i < numPoints; i++) {
// convert node-relative position to world position
PCCVector3<uint32_t> pos = node.pos
+ decodePointPosition(nodeSizeLog2, arithmeticDecoder,
ctxPointPosBlock);
*(outputPoints++) = {double(pos[0]), double(pos[1]), double(pos[2])};
}
return numPoints;
}
//-------------------------------------------------------------------------
void decodePositions(const PayloadBuffer& buf, PCCPointSet3& pointCloud)
{
assert(buf.type == PayloadType::kGeometryBrick);
int gbhSize;
GeometryBrickHeader gbh = parseGbh(*_gps, buf, &gbhSize);
minPositions.x() = gbh.geomBoxOrigin.x();
minPositions.y() = gbh.geomBoxOrigin.y();
minPositions.z() = gbh.geomBoxOrigin.z();
pointCloud.resize(gbh.geom_num_points);
o3dgc::Arithmetic_Codec arithmeticDecoder(
int(buf.size()) - gbhSize,
reinterpret_cast<uint8_t*>(const_cast<char*>(buf.data() + gbhSize)));
arithmeticDecoder.start_decoder();
o3dgc::Static_Bit_Model ctxEquiProb;
o3dgc::Adaptive_Bit_Model ctxSingleChild;
o3dgc::Adaptive_Bit_Model ctxSinglePointPerBlock;
o3dgc::Adaptive_Bit_Model ctxPointCountPerBlock;
o3dgc::Adaptive_Bit_Model ctxBlockSkipTh;
o3dgc::Adaptive_Bit_Model ctxNumIdcmPointsEq1;
CtxModelOctreeOccupancy ctxOccupancy;
// init main fifo
// -- worst case size is the last level containing every input poit
// and each point being isolated in the previous level.
pcc::ringbuf<PCCOctree3Node> fifo(pointCloud.getPointCount() + 1);
// the current node dimension (log2)
int nodeSizeLog2 = gbh.geom_max_node_size_log2;
// push the first node
fifo.emplace_back();
PCCOctree3Node& node00 = fifo.back();
node00.start = uint32_t(0);
node00.end = uint32_t(pointCloud.getPointCount());
node00.pos = uint32_t(0);
node00.neighPattern = 0;
node00.numSiblingsPlus1 = 8;
node00.siblingOccupancy = 0;
size_t processedPointCount = 0;
std::vector<uint32_t> values;
auto fifoCurrLvlEnd = fifo.end();
// this counter represents fifo.end() - fifoCurrLvlEnd().
// ie, the number of nodes added to the next level of the tree
int numNodesNextLvl = 0;
PCCVector3<uint32_t> occupancyAtlasOrigin(0xffffffff);
MortonMap3D occupancyAtlas;
if (_gps->neighbour_avail_boundary_log2) {
occupancyAtlas.resize(_gps->neighbour_avail_boundary_log2);
occupancyAtlas.clear();
}
for (; !fifo.empty(); fifo.pop_front()) {
if (fifo.begin() == fifoCurrLvlEnd) {
// transition to the next level
fifoCurrLvlEnd = fifo.end();
nodeSizeLog2--;
numNodesNextLvl = 0;
occupancyAtlasOrigin = 0xffffffff;
}
PCCOctree3Node& node0 = fifo.front();
if (_gps->neighbour_avail_boundary_log2) {
updateGeometryOccupancyAtlas(
node0.pos, nodeSizeLog2, fifo, fifoCurrLvlEnd, &occupancyAtlas,
&occupancyAtlasOrigin);
node0.neighPattern =
makeGeometryNeighPattern(node0.pos, nodeSizeLog2, occupancyAtlas);
}
// decode occupancy pattern
uint8_t occupancy = decodeGeometryOccupancy(
&arithmeticDecoder, ctxSingleChild, ctxEquiProb, ctxOccupancy, node0);
assert(occupancy > 0);
// population count of occupancy for IDCM
int numOccupied = popcnt(occupancy);
// split the current node
for (int i = 0; i < 8; i++) {
uint32_t mask = 1 << i;
if (!(occupancy & mask)) {
// child is empty: skip
continue;
}
int x = !!(i & 4);
int y = !!(i & 2);
int z = !!(i & 1);
int childSizeLog2 = nodeSizeLog2 - 1;
// point counts for leaf nodes are coded immediately upon
// encountering the leaf node.
if (childSizeLog2 == 0) {
int numPoints = 1;
if (!_gps->geom_unique_points_flag) {
numPoints = decodePositionLeafNumPoints(
&arithmeticDecoder, ctxSinglePointPerBlock, ctxEquiProb,
ctxPointCountPerBlock);
}
const PCCVector3D point(
node0.pos[0] + (x << childSizeLog2),
node0.pos[1] + (y << childSizeLog2),
node0.pos[2] + (z << childSizeLog2));
for (int i = 0; i < numPoints; ++i)
pointCloud[processedPointCount++] = point;
// do not recurse into leaf nodes
continue;
}
// create & enqueue new child.
fifo.emplace_back();
auto& child = fifo.back();
child.pos[0] = node0.pos[0] + (x << childSizeLog2);
child.pos[1] = node0.pos[1] + (y << childSizeLog2);