Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions Common/DataModel/vtkCellArray.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -568,11 +568,15 @@ vtkCellArray::vtkCellArray()
vtkCellArray::~vtkCellArray() = default;
vtkStandardNewMacro(vtkCellArray);

#ifdef VTK_USE_64BIT_IDS
bool vtkCellArray::DefaultStorageIs64Bit = true;
#else
// fvtk width-relaxed rule ([[fvtk-int32-default-width-relaxed]]): default cell
// arrays to 32-bit offset/connectivity storage regardless of vtkIdType width.
// This halves the cell-array footprint (and memory bandwidth, the bottleneck on
// large meshes) vs stock VTK's 64-bit default. Every value-writing path
// auto-widens to 64-bit when a value cannot fit in int32, so meshes with
// >2^31 points/cells stay correct. Integer VALUES are identical to stock; only
// the container narrows. Use vtkCellArray::SetDefaultStorageIs64Bit(true) to
// restore stock behaviour.
bool vtkCellArray::DefaultStorageIs64Bit = false;
#endif

//=================== Begin Legacy Methods ===================================
// These should be deprecated at some point as they are confusing or very slow
Expand Down Expand Up @@ -806,6 +810,25 @@ void vtkCellArray::Append(vtkCellArray* src, vtkIdType pointOffset)
{
if (src->GetNumberOfCells() > 0)
{
// After append, the largest offset = our connectivity size + src's, and the
// largest connectivity id = src's max id + pointOffset. Widen if either
// overflows int32 (GetRange is cached on src; this is a bulk op already).
if (this->StorageType == StorageTypes::Int32)
{
vtkIdType maxVal = this->GetNumberOfConnectivityIds() + src->GetNumberOfConnectivityIds();
vtkDataArray* srcConn = src->GetConnectivityArray();
if (srcConn && srcConn->GetNumberOfValues() > 0)
{
double r[2];
srcConn->GetRange(r, 0);
const vtkIdType m = static_cast<vtkIdType>(r[1]) + pointOffset;
if (m > maxVal)
{
maxVal = m;
}
}
this->WidenStorageForValue(maxVal);
}
this->Dispatch(AppendImpl{}, src, pointOffset);
}
}
Expand Down Expand Up @@ -1435,13 +1458,27 @@ void vtkCellArray::ReplaceCellAtId(vtkIdType cellId, vtkIdList* list)
void vtkCellArray::ReplaceCellAtId(
vtkIdType cellId, vtkIdType cellSize, const vtkIdType cellPoints[])
{
// Replaces connectivity ids in place; widen if any new id overflows int32.
if (this->StorageType == StorageTypes::Int32)
{
vtkIdType maxVal = 0;
for (vtkIdType i = 0; i < cellSize; ++i)
{
if (cellPoints[i] > maxVal)
{
maxVal = cellPoints[i];
}
}
this->WidenStorageForValue(maxVal);
}
this->Dispatch(ReplaceCellAtIdImpl{}, cellId, cellSize, cellPoints);
}

//------------------------------------------------------------------------------
void vtkCellArray::ReplaceCellPointAtId(
vtkIdType cellId, vtkIdType cellPointIndex, vtkIdType newPointId)
{
this->WidenStorageForValue(newPointId);
this->Dispatch(ReplaceCellPointAtIdImpl{}, cellId, cellPointIndex, newPointId);
}

Expand Down Expand Up @@ -1489,6 +1526,27 @@ void vtkCellArray::AppendLegacyFormat(vtkIdTypeArray* data, vtkIdType ptOffset)
//------------------------------------------------------------------------------
void vtkCellArray::AppendLegacyFormat(const vtkIdType* data, vtkIdType len, vtkIdType ptOffset)
{
// Legacy data is [npts, id0..., npts2, id0...]. Upper-bound the largest value
// this will store: offsets grow by at most `len`, and connectivity ids are
// data values + ptOffset. Scanning `data` for its max (incl. the small count
// entries -- a harmless overestimate) bounds both. Widen once if needed.
if (this->StorageType == StorageTypes::Int32)
{
vtkIdType maxData = 0;
for (vtkIdType i = 0; i < len; ++i)
{
if (data[i] > maxData)
{
maxData = data[i];
}
}
vtkIdType maxVal = this->GetNumberOfConnectivityIds() + len;
if (maxData + ptOffset > maxVal)
{
maxVal = maxData + ptOffset;
}
this->WidenStorageForValue(maxVal);
}
this->Dispatch(AppendLegacyFormatImpl{}, data, len, ptOffset);
}

Expand Down
48 changes: 40 additions & 8 deletions Common/DataModel/vtkCellArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ class VTKCOMMONDATAMODEL_EXPORT VTK_MARSHALMANUAL vtkCellArray : public vtkAbstr
*/
void SetOffset(vtkIdType cellId, vtkIdType offset)
{
this->WidenStorageForValue(offset);
this->Offsets->SetComponent(cellId, 0, static_cast<double>(offset));
}

Expand Down Expand Up @@ -1539,6 +1540,23 @@ class VTKCOMMONDATAMODEL_EXPORT VTK_MARSHALMANUAL vtkCellArray : public vtkAbstr
private:
vtkCellArray(const vtkCellArray&) = delete;
void operator=(const vtkCellArray&) = delete;

// fvtk width-relaxed rule ([[fvtk-int32-default-width-relaxed]]): cell arrays
// default to 32-bit offset/connectivity storage (half the footprint of stock
// VTK's 64-bit default) and auto-widen to 64-bit only when a value cannot be
// represented in int32. This guard is called by every value-writing path
// before it stores; on the common path it is a single predicted-not-taken
// branch. `maxValue` must be the largest value that path is about to store
// (largest offset and/or connectivity id). Out-of-line ConvertTo64BitStorage
// runs only on the rare overflow.
void WidenStorageForValue(vtkIdType maxValue)
{
if (this->StorageType == StorageTypes::Int32 &&
maxValue > static_cast<vtkIdType>(0x7FFFFFFF))
{
this->ConvertTo64BitStorage();
}
}
};

template <typename ArrayT>
Expand Down Expand Up @@ -1841,6 +1859,20 @@ inline vtkIdType vtkCellArray::GetCellPointAtId(vtkIdType cellId, vtkIdType cell
inline vtkIdType vtkCellArray::InsertNextCell(vtkIdType npts, const vtkIdType* pts)
VTK_SIZEHINT(pts, npts)
{
// Widen to 64-bit if any stored value (the new offset = current connectivity
// size + npts, or any point id in pts) would overflow int32.
if (this->StorageType == StorageTypes::Int32)
{
vtkIdType maxVal = this->GetNumberOfConnectivityIds() + npts;
for (vtkIdType i = 0; i < npts; ++i)
{
if (pts[i] > maxVal)
{
maxVal = pts[i];
}
}
this->WidenStorageForValue(maxVal);
}
vtkIdType cellId;
this->Dispatch(vtkCellArray_detail::InsertNextCellImpl{}, npts, pts, cellId);
return cellId;
Expand All @@ -1849,6 +1881,9 @@ inline vtkIdType vtkCellArray::InsertNextCell(vtkIdType npts, const vtkIdType* p
//----------------------------------------------------------------------------
inline vtkIdType vtkCellArray::InsertNextCell(int npts)
{
// Incremental API: this writes only the new offset (= connectivity size +
// npts); connectivity ids arrive later via InsertCellPoint, guarded there.
this->WidenStorageForValue(this->GetNumberOfConnectivityIds() + npts);
vtkIdType cellId;
this->Dispatch(vtkCellArray_detail::InsertNextCellImpl{}, npts, cellId);
return cellId;
Expand All @@ -1857,32 +1892,29 @@ inline vtkIdType vtkCellArray::InsertNextCell(int npts)
//----------------------------------------------------------------------------
inline void vtkCellArray::InsertCellPoint(vtkIdType id)
{
this->WidenStorageForValue(id);
this->Dispatch(vtkCellArray_detail::InsertCellPointImpl{}, id);
}

//----------------------------------------------------------------------------
inline void vtkCellArray::UpdateCellCount(int npts)
{
// Updates the current cell's end offset (= connectivity size).
this->WidenStorageForValue(this->GetNumberOfConnectivityIds());
this->Dispatch(vtkCellArray_detail::UpdateCellCountImpl{}, npts);
}

//----------------------------------------------------------------------------
inline vtkIdType vtkCellArray::InsertNextCell(vtkIdList* pts)
{
vtkIdType cellId;
this->Dispatch(
vtkCellArray_detail::InsertNextCellImpl{}, pts->GetNumberOfIds(), pts->GetPointer(0), cellId);
return cellId;
return this->InsertNextCell(pts->GetNumberOfIds(), pts->GetPointer(0));
}

//----------------------------------------------------------------------------
inline vtkIdType vtkCellArray::InsertNextCell(vtkCell* cell)
{
vtkIdList* pts = cell->GetPointIds();
vtkIdType cellId;
this->Dispatch(
vtkCellArray_detail::InsertNextCellImpl{}, pts->GetNumberOfIds(), pts->GetPointer(0), cellId);
return cellId;
return this->InsertNextCell(pts->GetNumberOfIds(), pts->GetPointer(0));
}

//----------------------------------------------------------------------------
Expand Down
43 changes: 35 additions & 8 deletions IO/Geometry/vtkSTLReader.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "vtkSmartPointer.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringScanner.h"
#include "vtkTypeInt32Array.h"
#include "vtkUnsignedCharArray.h"

#include <algorithm>
Expand Down Expand Up @@ -493,17 +494,43 @@ int vtkSTLReader::ReadSTLFast(vtkResourceStream* stream, vtkPolyData* output)
}

// Finalize the cell array: shrink connectivity to the kept triangles and
// synthesize the uniform offset array (0, 3, 6, ...).
// synthesize the uniform offset array (0, 3, 6, ...). Per the fvtk int32-
// default rule ([[fvtk-int32-default-width-relaxed]]), store offsets and
// connectivity as int32 when every value fits -- numFile*3 bounds both the
// largest offset (nKept*3) and the largest vertex index (numPts <= nKept*3) --
// widening to int64 only for >2^31-element meshes. int32 halves the cell-array
// footprint; the values are identical.
connArr->SetNumberOfValues(nKept * 3);
vtkNew<vtkIdTypeArray> offArr;
offArr->SetNumberOfValues(nKept + 1);
vtkIdType* off = offArr->GetPointer(0);
for (vtkIdType i = 0; i <= nKept; ++i)
vtkNew<vtkCellArray> newPolys;
if (numFile <= static_cast<vtkTypeInt64>(0x7FFFFFFF) / 3)
{
off[i] = 3 * i;
vtkNew<vtkTypeInt32Array> conn32;
conn32->SetNumberOfValues(nKept * 3);
vtkTypeInt32* c32 = conn32->GetPointer(0);
for (vtkIdType i = 0; i < nKept * 3; ++i)
{
c32[i] = static_cast<vtkTypeInt32>(conn[i]);
}
vtkNew<vtkTypeInt32Array> off32;
off32->SetNumberOfValues(nKept + 1);
vtkTypeInt32* o32 = off32->GetPointer(0);
for (vtkIdType i = 0; i <= nKept; ++i)
{
o32[i] = static_cast<vtkTypeInt32>(3 * i);
}
newPolys->SetData(off32, conn32);
}
else
{
vtkNew<vtkIdTypeArray> offArr;
offArr->SetNumberOfValues(nKept + 1);
vtkIdType* off = offArr->GetPointer(0);
for (vtkIdType i = 0; i <= nKept; ++i)
{
off[i] = 3 * i;
}
newPolys->SetData(offArr, connArr);
}
vtkNew<vtkCellArray> newPolys;
newPolys->SetData(offArr, connArr);

const vtkIdType numPts = static_cast<vtkIdType>(merger.NumberOfVertices());
vtkNew<vtkFloatArray> coords;
Expand Down
22 changes: 17 additions & 5 deletions tests/bitexact/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,23 @@ def compare_case(stock_dir, fvtk_dir, key):
ok = True
for name in sorted(names_a):
x, y = a[name], b[name]
equal = bool(
x.shape == y.shape
and x.dtype == y.dtype
and np.array_equal(x, y)
)
# Width-relaxed comparison for INTEGER arrays (fvtk-wide int32-default
# rule): integer VALUES are sacred, but the storage CONTAINER WIDTH is
# negotiable -- fvtk defaults topology/index/offset arrays to int32 while
# stock VTK uses int64. So for integer-kind arrays compare values
# normalized to int64 and ignore the dtype tag. FLOAT (and other) arrays
# stay strict: identical dtype + exact bytes (maxULP=0).
if x.dtype.kind in "iu" and y.dtype.kind in "iu":
equal = bool(
x.shape == y.shape
and np.array_equal(x.astype(np.int64), y.astype(np.int64))
)
else:
equal = bool(
x.shape == y.shape
and x.dtype == y.dtype
and np.array_equal(x, y)
)
ulp = None
if x.dtype.kind == "f" and x.shape == y.shape and x.dtype == y.dtype:
ulp = _ulp_distance(x, y)
Expand Down
Loading