diff --git a/Filters/Geometry/vtkGeometryFilter.cxx b/Filters/Geometry/vtkGeometryFilter.cxx index 9af1e5a5..921b1258 100644 --- a/Filters/Geometry/vtkGeometryFilter.cxx +++ b/Filters/Geometry/vtkGeometryFilter.cxx @@ -32,6 +32,7 @@ #include "vtkStructuredData.h" #include "vtkStructuredGrid.h" #include "vtkTetra.h" +#include "vtkTypeInt32Array.h" // fvtk: width-relaxed int32 OriginalPointIds storage #include "vtkUniformGrid.h" #include "vtkUnsignedCharArray.h" #include "vtkUnstructuredGrid.h" @@ -2808,19 +2809,11 @@ struct CharacterizeGrid }; //------------------------------------------------------------------------------ -// Threaded creation to generate array of originating point ids. -template -void PassPointIds(const char* name, vtkIdType numInputPts, vtkIdType numOutputPts, - TInputIdType* ptMap, vtkPointData* outPD) +// Threaded populate of the originating-point-id array (templated on the output +// container's value type so the same scatter drives int32 or int64 storage). +template +void PassPointIdsFill(TOutId* origIds, vtkIdType numInputPts, TInputIdType* ptMap) { - vtkNew origPtIds; - origPtIds->SetName(name); - origPtIds->SetNumberOfComponents(1); - origPtIds->SetNumberOfTuples(numOutputPts); - outPD->AddArray(origPtIds); - vtkIdType* origIds = origPtIds->GetPointer(0); - - // Now threaded populate the array vtkSMPTools::For(0, numInputPts, [&origIds, &ptMap](vtkIdType ptId, vtkIdType endPtId) { @@ -2828,12 +2821,43 @@ void PassPointIds(const char* name, vtkIdType numInputPts, vtkIdType numOutputPt { if (ptMap[ptId] >= 0) { - origIds[ptMap[ptId]] = ptId; + origIds[ptMap[ptId]] = static_cast(ptId); } } }); } +//------------------------------------------------------------------------------ +// Threaded creation to generate array of originating point ids. +template +void PassPointIds(const char* name, vtkIdType numInputPts, vtkIdType numOutputPts, + TInputIdType* ptMap, vtkPointData* outPD) +{ + // fvtk: width-relaxed storage. The values are input point ids (sacred); the + // CONTAINER is int32 when every id fits in 0x7FFFFFFF, else int64. Halves the + // array footprint on large extracted surfaces. The bitexact gate width- + // normalizes integer arrays, and the render hardware-selection path reads this + // passthrough array width-agnostically (vtkOpenGL*PolyDataMapper). + if (numInputPts <= static_cast(0x7FFFFFFF)) + { + vtkNew origPtIds; + origPtIds->SetName(name); + origPtIds->SetNumberOfComponents(1); + origPtIds->SetNumberOfTuples(numOutputPts); + outPD->AddArray(origPtIds); + PassPointIdsFill(origPtIds->GetPointer(0), numInputPts, ptMap); + } + else + { + vtkNew origPtIds; + origPtIds->SetName(name); + origPtIds->SetNumberOfComponents(1); + origPtIds->SetNumberOfTuples(numOutputPts); + outPD->AddArray(origPtIds); + PassPointIdsFill(origPtIds->GetPointer(0), numInputPts, ptMap); + } +} + //------------------------------------------------------------------------------ // Threaded compositing of originating cell ids. template diff --git a/Rendering/OpenGL2/vtkOpenGLBatchedPolyDataMapper.cxx b/Rendering/OpenGL2/vtkOpenGLBatchedPolyDataMapper.cxx index c89eaef0..3c4d56b7 100644 --- a/Rendering/OpenGL2/vtkOpenGLBatchedPolyDataMapper.cxx +++ b/Rendering/OpenGL2/vtkOpenGLBatchedPolyDataMapper.cxx @@ -489,8 +489,10 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel // do we need to do anything to the point id data? if (currPass == vtkHardwareSelector::POINT_ID_LOW24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -521,7 +523,7 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel vtkIdType outval = inval; if (pointArrayId && static_cast(inval) <= pointArrayId->GetMaxId()) { - outval = pointArrayId->GetValue(inval); + outval = static_cast(pointArrayId->GetComponent(inval, 0)); } plowdata[pos] = outval & 0xff; plowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -533,8 +535,10 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel if (currPass == vtkHardwareSelector::POINT_ID_HIGH24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -557,7 +561,7 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel vtkIdType outval = inval; if (pointArrayId) { - outval = pointArrayId->GetValue(inval); + outval = static_cast(pointArrayId->GetComponent(inval, 0)); } phighdata[pos] = (outval & 0xff000000) >> 24; phighdata[pos + 1] = (outval & 0xff00000000) >> 32; @@ -606,8 +610,9 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel if (currPass == vtkHardwareSelector::CELL_ID_LOW24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* clowdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); bool hasHighCellIds = sel->HasHighCellIds(); @@ -639,7 +644,7 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel glBatchElement->CellCellMap->ConvertOpenGLCellIdToVTKCellId(pointPicking, inval); if (cellArrayId && outval <= cellArrayId->GetMaxId()) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } clowdata[pos] = outval & 0xff; clowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -651,8 +656,9 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel if (currPass == vtkHardwareSelector::CELL_ID_HIGH24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* chighdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); @@ -675,7 +681,7 @@ void vtkOpenGLBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSel glBatchElement->CellCellMap->ConvertOpenGLCellIdToVTKCellId(pointPicking, inval); if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } chighdata[pos] = (outval & 0xff000000) >> 24; chighdata[pos + 1] = (outval & 0xff00000000) >> 32; diff --git a/Rendering/OpenGL2/vtkOpenGLLowMemoryBatchedPolyDataMapper.cxx b/Rendering/OpenGL2/vtkOpenGLLowMemoryBatchedPolyDataMapper.cxx index d32622d3..fbae4f71 100644 --- a/Rendering/OpenGL2/vtkOpenGLLowMemoryBatchedPolyDataMapper.cxx +++ b/Rendering/OpenGL2/vtkOpenGLLowMemoryBatchedPolyDataMapper.cxx @@ -514,8 +514,10 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa // do we need to do anything to the point id data? if (currPass == vtkHardwareSelector::POINT_ID_LOW24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -545,7 +547,7 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa vtkIdType outval = inval; if (pointArrayId && static_cast(inval) <= pointArrayId->GetMaxId()) { - outval = pointArrayId->GetValue(inval); + outval = static_cast(pointArrayId->GetComponent(inval, 0)); } plowdata[pos] = outval & 0xff; plowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -557,8 +559,10 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa if (currPass == vtkHardwareSelector::POINT_ID_HIGH24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -580,7 +584,7 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa vtkIdType outval = inval; if (pointArrayId) { - outval = pointArrayId->GetValue(inval); + outval = static_cast(pointArrayId->GetComponent(inval, 0)); } phighdata[pos] = (outval & 0xff000000) >> 24; phighdata[pos + 1] = (outval & 0xff00000000) >> 32; @@ -617,8 +621,9 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa if (currPass == vtkHardwareSelector::CELL_ID_LOW24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* clowdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); bool hasHighCellIds = sel->HasHighCellIds(); @@ -647,7 +652,7 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa vtkIdType outval = inval; if (cellArrayId && outval <= cellArrayId->GetMaxId()) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } clowdata[pos] = outval & 0xff; clowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -659,8 +664,9 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa if (currPass == vtkHardwareSelector::CELL_ID_HIGH24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* chighdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); @@ -680,7 +686,7 @@ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHa vtkIdType outval = inval; if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } chighdata[pos] = (outval & 0xff000000) >> 24; chighdata[pos + 1] = (outval & 0xff00000000) >> 32; diff --git a/Rendering/OpenGL2/vtkOpenGLLowMemoryPolyDataMapper.cxx b/Rendering/OpenGL2/vtkOpenGLLowMemoryPolyDataMapper.cxx index d0a1370d..dbdf40ff 100644 --- a/Rendering/OpenGL2/vtkOpenGLLowMemoryPolyDataMapper.cxx +++ b/Rendering/OpenGL2/vtkOpenGLLowMemoryPolyDataMapper.cxx @@ -3268,8 +3268,10 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::POINT_ID_LOW24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -3290,7 +3292,7 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; - vtkIdType outval = pointArrayId->GetValue(inval); + vtkIdType outval = static_cast(pointArrayId->GetComponent(inval, 0)); plowdata[pos] = outval & 0xff; plowdata[pos + 1] = (outval & 0xff00) >> 8; plowdata[pos + 2] = (outval & 0xff0000) >> 16; @@ -3300,8 +3302,10 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::POINT_ID_HIGH24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (int32-or-int64 passthrough array); see + // vtkOpenGLPolyDataMapper for the bit-exactness argument. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -3319,7 +3323,7 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; - vtkIdType outval = pointArrayId->GetValue(inval); + vtkIdType outval = static_cast(pointArrayId->GetComponent(inval, 0)); phighdata[pos] = (outval & 0xff000000) >> 24; phighdata[pos + 1] = (outval & 0xff00000000) >> 32; phighdata[pos + 2] = (outval & 0xff0000000000) >> 40; @@ -3356,8 +3360,9 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( // process the cellid array? if (currPass == vtkHardwareSelector::CELL_ID_LOW24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* clowdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); @@ -3379,7 +3384,7 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( vtkIdType outval = inval; if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } clowdata[pos] = outval & 0xff; clowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -3390,8 +3395,9 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::CELL_ID_HIGH24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* chighdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); @@ -3410,7 +3416,7 @@ void vtkOpenGLLowMemoryPolyDataMapper::ProcessSelectorPixelBuffers( vtkIdType outval = inval; if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } chighdata[pos] = (outval & 0xff000000) >> 24; chighdata[pos + 1] = (outval & 0xff00000000) >> 32; @@ -3434,8 +3440,9 @@ void vtkOpenGLLowMemoryPolyDataMapper::UpdateMaximumPointCellIds(vtkRenderer* re vtkIdType maxPointId = mesh->GetPoints()->GetNumberOfPoints() - 1; if (mesh && mesh->GetPointData()) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(mesh->GetPointData()->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id read (GetRange works on any vtkDataArray). + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(mesh->GetPointData()->GetArray(this->PointIdArrayName)) : nullptr; if (pointArrayId) { @@ -3451,8 +3458,9 @@ void vtkOpenGLLowMemoryPolyDataMapper::UpdateMaximumPointCellIds(vtkRenderer* re vtkIdType maxCellId = mesh->GetNumberOfCells() - 1; if (mesh && mesh->GetCellData()) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(mesh->GetCellData()->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id read (GetRange works on any vtkDataArray). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(mesh->GetCellData()->GetArray(this->CellIdArrayName)) : nullptr; if (cellArrayId) { diff --git a/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx b/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx index c0c0156d..84f88d02 100644 --- a/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx +++ b/Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx @@ -3408,8 +3408,10 @@ void vtkOpenGLPolyDataMapper::UpdateMaximumPointCellIds(vtkRenderer* ren, vtkAct vtkIdType maxPointId = this->CurrentInput->GetPoints()->GetNumberOfPoints() - 1; if (this->CurrentInput && this->CurrentInput->GetPointData()) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast( + // fvtk: width-agnostic id read (GetRange works on any vtkDataArray); the + // int32-or-int64 passthrough array must size the id-bit allocation correctly. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast( this->CurrentInput->GetPointData()->GetArray(this->PointIdArrayName)) : nullptr; if (pointArrayId) @@ -3444,8 +3446,9 @@ void vtkOpenGLPolyDataMapper::UpdateMaximumPointCellIds(vtkRenderer* ren, vtkAct if (this->CurrentInput && this->CurrentInput->GetCellData()) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast( + // fvtk: width-agnostic id read (GetRange works on any vtkDataArray). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast( this->CurrentInput->GetCellData()->GetArray(this->CellIdArrayName)) : nullptr; if (cellArrayId) @@ -4746,8 +4749,14 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::POINT_ID_LOW24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id-array read. The point-id passthrough array may be + // stored as int32 (width-relaxed) or int64; fetch as vtkDataArray and read + // values via GetComponent so an int32 container is NOT silently dropped (a + // vtkArrayDownCast on an int32 array returns null -> the id + // remap would be skipped). Point/cell indices are < 2^53 so the double round + // trip is exact, identical to the former vtkIdType GetValue. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -4768,7 +4777,7 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; - vtkIdType outval = pointArrayId->GetValue(inval); + vtkIdType outval = static_cast(pointArrayId->GetComponent(inval, 0)); plowdata[pos] = outval & 0xff; plowdata[pos + 1] = (outval & 0xff00) >> 8; plowdata[pos + 2] = (outval & 0xff0000) >> 16; @@ -4778,8 +4787,14 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::POINT_ID_HIGH24) { - vtkIdTypeArray* pointArrayId = this->PointIdArrayName - ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) + // fvtk: width-agnostic id-array read. The point-id passthrough array may be + // stored as int32 (width-relaxed) or int64; fetch as vtkDataArray and read + // values via GetComponent so an int32 container is NOT silently dropped (a + // vtkArrayDownCast on an int32 array returns null -> the id + // remap would be skipped). Point/cell indices are < 2^53 so the double round + // trip is exact, identical to the former vtkIdType GetValue. + vtkDataArray* pointArrayId = this->PointIdArrayName + ? vtkDataArray::SafeDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? @@ -4797,7 +4812,7 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; - vtkIdType outval = pointArrayId->GetValue(inval); + vtkIdType outval = static_cast(pointArrayId->GetComponent(inval, 0)); phighdata[pos] = (outval & 0xff000000) >> 24; phighdata[pos + 1] = (outval & 0xff00000000) >> 32; phighdata[pos + 2] = (outval & 0xff0000000000) >> 40; @@ -4846,8 +4861,9 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( // process the cellid array? if (currPass == vtkHardwareSelector::CELL_ID_LOW24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id-array read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* clowdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); @@ -4872,7 +4888,7 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( this->CellCellMap->ConvertOpenGLCellIdToVTKCellId(this->PointPicking, inval); if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } clowdata[pos] = outval & 0xff; clowdata[pos + 1] = (outval & 0xff00) >> 8; @@ -4883,8 +4899,9 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( if (currPass == vtkHardwareSelector::CELL_ID_HIGH24) { - vtkIdTypeArray* cellArrayId = this->CellIdArrayName - ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) + // fvtk: width-agnostic id-array read (see point-id note above). + vtkDataArray* cellArrayId = this->CellIdArrayName + ? vtkDataArray::SafeDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* chighdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); @@ -4906,7 +4923,7 @@ void vtkOpenGLPolyDataMapper::ProcessSelectorPixelBuffers( this->CellCellMap->ConvertOpenGLCellIdToVTKCellId(this->PointPicking, inval); if (cellArrayId) { - outval = cellArrayId->GetValue(outval); + outval = static_cast(cellArrayId->GetComponent(outval, 0)); } chighdata[pos] = (outval & 0xff000000) >> 24; chighdata[pos + 1] = (outval & 0xff00000000) >> 32; diff --git a/ci/run-renderexact.sh b/ci/run-renderexact.sh index c83682ba..0acbd34d 100755 --- a/ci/run-renderexact.sh +++ b/ci/run-renderexact.sh @@ -58,3 +58,12 @@ cd "$SRC/tests/renderexact" # Pixel-exact diff + GL-driver-match gate (exits non-zero on any diff / mismatch). # Use the stock venv's python — compare_render.py needs numpy. /tmp/rx-stock/bin/python compare_render.py "$OUT/stock" "$OUT/fvtk" + +# Hardware-selection (picking) parity: prove the GPU selection path remaps picked +# point ids through the width-relaxed int32 vtkOriginalPointIds array to the SAME +# original ids as stock VTK's int64 array. This path is invisible to the pixel +# gate above, so it gets its own selected-id comparison under the same EGL driver. +mkdir -p "$OUT/sel-stock" "$OUT/sel-fvtk" +/tmp/rx-stock/bin/python run_select.py "$OUT/sel-stock" +/tmp/rx-fvtk/bin/python run_select.py "$OUT/sel-fvtk" +/tmp/rx-stock/bin/python compare_select.py "$OUT/sel-stock" "$OUT/sel-fvtk" diff --git a/tests/bitexact/ops.py b/tests/bitexact/ops.py index c5b153c7..f13ee53e 100644 --- a/tests/bitexact/ops.py +++ b/tests/bitexact/ops.py @@ -1195,6 +1195,10 @@ def op_triangle(dtype, size): def op_geometry(dtype, size): g = vtkGeometryFilter() g.SetInputData(make_volume(size, dtype)) + # fvtk: emit vtkOriginalPointIds so the width-relaxed int32 id-array storage + # (vtkGeometryFilter::PassPointIds) is validated against stock (values match, + # int32 vs int64 container normalized by the compare gate). + g.PassThroughPointIdsOn() g.Update() return g.GetOutput() @@ -1664,6 +1668,7 @@ def op_geometry_ugrid(dtype, size): # inline operator[] connectivity-read optimization in vtkCellArray.h). g = vtkGeometryFilter() g.SetInputData(make_hex_ugrid(size, dtype)) + g.PassThroughPointIdsOn() # fvtk: validate int32 vtkOriginalPointIds storage g.Update() return g.GetOutput() @@ -1678,6 +1683,7 @@ def op_geometry_ugrid_mixed(dtype, size): # Run on float32 AND float64 to cover both typed point-copy paths. g = vtkGeometryFilter() g.SetInputData(make_mixed_ugrid(size, dtype)) + g.PassThroughPointIdsOn() # fvtk: validate int32 vtkOriginalPointIds storage g.Update() return g.GetOutput() diff --git a/tests/renderexact/compare_select.py b/tests/renderexact/compare_select.py new file mode 100644 index 00000000..ab07bfde --- /dev/null +++ b/tests/renderexact/compare_select.py @@ -0,0 +1,75 @@ +"""Compare hardware-selection (picking) output between two run_select.py dirs. + +Asserts that the set of selected ORIGINAL point ids is identical between stock +VTK and fvtk for every scene. The ids are integer values (width-relaxed): fvtk's +source vtkOriginalPointIds array is int32, stock's is int64, but the selected +VALUES must match exactly. A mismatch means fvtk's mapper failed to remap picked +ids through the int32 passthrough array (the regression this gate guards). + +Also enforces: + * the selection is non-empty (an empty selection would trivially "match"), + * neither backend errored, + * (informational) the fvtk source array really is int32 -- proving the + discriminating condition holds; a warning, not a failure, so the gate keeps + working if the storage policy ever changes. + +Usage: python compare_select.py (exit 1 on any diff) +""" +from __future__ import annotations + +import json +import os +import sys + +import numpy as np + + +def _load(d): + with open(os.path.join(d, "manifest.json")) as fh: + return json.load(fh) + + +def main(): + stock_dir, fvtk_dir = sys.argv[1], sys.argv[2] + sm = _load(stock_dir) + fm = _load(fvtk_dir) + + scenes = sorted(set(sm["cases"]) | set(fm["cases"])) + n_fail = 0 + for name in scenes: + sc = sm["cases"].get(name, {}) + fc = fm["cases"].get(name, {}) + if "error" in sc or "error" in fc: + print(f"FAIL {name}: backend error stock={sc.get('error')} fvtk={fc.get('error')}") + n_fail += 1 + continue + s_ids = np.load(os.path.join(stock_dir, name + ".npz"))["selected_point_ids"] + f_ids = np.load(os.path.join(fvtk_dir, name + ".npz"))["selected_point_ids"] + if s_ids.size == 0: + print(f"FAIL {name}: stock selection is EMPTY (non-discriminating)") + n_fail += 1 + continue + if s_ids.shape != f_ids.shape or not np.array_equal( + s_ids.astype(np.int64), f_ids.astype(np.int64) + ): + only_s = np.setdiff1d(s_ids, f_ids) + only_f = np.setdiff1d(f_ids, s_ids) + print( + f"FAIL {name}: selected original point ids differ " + f"(stock n={s_ids.size}, fvtk n={f_ids.size}; " + f"only-stock={only_s[:8].tolist()}, only-fvtk={only_f[:8].tolist()})" + ) + n_fail += 1 + continue + # Informational: confirm the discriminating int32 storage is in effect. + fdt = fc.get("orig_pointids_dtype") + note = "" if fdt == "int32" else f" [warn: fvtk dtype={fdt}, expected int32]" + print(f"OK {name}: {s_ids.size} selected ids match " + f"(stock dtype={sc.get('orig_pointids_dtype')}, fvtk dtype={fdt}){note}") + + print(f"[compare_select] {len(scenes) - n_fail}/{len(scenes)} scenes matched") + return 1 if n_fail else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/renderexact/run_select.py b/tests/renderexact/run_select.py new file mode 100644 index 00000000..2b6b69d2 --- /dev/null +++ b/tests/renderexact/run_select.py @@ -0,0 +1,202 @@ +"""Standalone driver: hardware-selection (picking) parity for fvtk vs stock VTK. + +Sister to ``run_render.py``. Where that gate proves pixels match, this one proves +the GPU **hardware-selection** path maps picked pixels back to the SAME original +point ids on both backends -- the path that ``run_render.py`` never exercises. + +Why this gate exists: fvtk stores the ``vtkOriginalPointIds`` passthrough array of +``vtkGeometryFilter`` in an int32 container (width-relaxed) when the ids fit, +where stock VTK uses int64. The render hardware-selector remaps a picked pixel's +raw VTK point id to the value in that array. If a mapper fetched the array with a +width-specific ``vtkArrayDownCast`` (null on int32), the remap +would be silently skipped and picking would return the WRONG ids -- a break that +neither the bit-exact nor the pixel-exact gate can see. The fvtk mappers were +reworked to read the array width-agnostically; this driver is the gate for that. + +Scene design mirrors run_render.py's determinism rules (fixed offscreen EGL +window, fixed camera, deterministic integer-algebra geometry, no MSAA), so both +backends drive an identical GL command stream and the only thing that can differ +is the id-array read. The scene extracts the surface of a hex lattice so the +surviving surface points are a NON-IDENTITY subset of the input points -- i.e. +``vtkOriginalPointIds[surfaceId] != surfaceId`` -- which makes the test +discriminating: a dropped int32 remap yields surface ids, not original ids. + +Usage: python run_select.py [scene_filter] +""" +from __future__ import annotations + +import json +import os +import sys + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import render_ops # noqa: E402 (reuse _renderer/_fixed_camera/_new_window) + +from vtkmodules.vtkCommonCore import vtkPoints # noqa: E402 +from vtkmodules.vtkCommonDataModel import ( # noqa: E402 + vtkUnstructuredGrid, + vtkDataObject, + VTK_HEXAHEDRON, +) +from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter # noqa: E402 +from vtkmodules.vtkRenderingCore import ( # noqa: E402 + vtkActor, + vtkPolyDataMapper, + vtkHardwareSelector, +) +from vtkmodules.util.numpy_support import vtk_to_numpy # noqa: E402 + +# Register the GL backend factory overrides (same as run_render.py). +import vtkmodules.vtkRenderingOpenGL2 # noqa: F401,E402 + + +def _hex_lattice(n): + """An n x n x n point lattice meshed with (n-1)^3 hexahedra. The surface + extraction will drop the interior points, so vtkOriginalPointIds becomes a + non-identity map (the property this gate relies on).""" + grid = vtkUnstructuredGrid() + pts = vtkPoints() + for k in range(n): + for j in range(n): + for i in range(n): + # centered, unit-ish extent; pure integer algebra -> identical + # coordinates on both backends. + pts.InsertNextPoint( + (i - (n - 1) / 2.0) * 0.4, + (j - (n - 1) / 2.0) * 0.4, + (k - (n - 1) / 2.0) * 0.4, + ) + grid.SetPoints(pts) + + def pid(i, j, k): + return (k * n + j) * n + i + + grid.Allocate((n - 1) ** 3) + for k in range(n - 1): + for j in range(n - 1): + for i in range(n - 1): + ids = [ + pid(i, j, k), + pid(i + 1, j, k), + pid(i + 1, j + 1, k), + pid(i, j + 1, k), + pid(i, j, k + 1), + pid(i + 1, j, k + 1), + pid(i + 1, j + 1, k + 1), + pid(i, j + 1, k + 1), + ] + grid.InsertNextCell(VTK_HEXAHEDRON, 8, ids) + return grid + + +def scene_surface_pointpick(): + """Surface of a hex lattice, with the mapper wired to remap picked point ids + through the vtkOriginalPointIds passthrough array.""" + grid = _hex_lattice(6) + geom = vtkGeometryFilter() + geom.SetInputData(grid) + geom.PassThroughPointIdsOn() + geom.PassThroughCellIdsOn() + geom.Update() + surf = geom.GetOutput() + + m = vtkPolyDataMapper() + m.SetInputData(surf) + m.SetPointIdArrayName("vtkOriginalPointIds") + m.SetCellIdArrayName("vtkOriginalCellIds") + a = vtkActor() + a.SetMapper(m) + a.GetProperty().SetPointSize(3.0) + + ren = render_ops._renderer() + ren.AddActor(a) + render_ops._fixed_camera(ren, dist=3.0) + rw = render_ops._new_window(ren) + return ren, rw, surf + + +SCENES = { + "surface_pointpick": scene_surface_pointpick, +} + + +def _select_point_ids(ren, rw): + """Run a full-viewport hardware POINT selection; return the sorted-unique set + of selected ids (these are the REMAPPED original ids because the mapper has a + PointIdArrayName set).""" + w, h = rw.GetSize() + sel = vtkHardwareSelector() + sel.SetRenderer(ren) + sel.SetArea(0, 0, w - 1, h - 1) + sel.SetFieldAssociation(vtkDataObject.FIELD_ASSOCIATION_POINTS) + result = sel.Select() + ids = [] + n_nodes = result.GetNumberOfNodes() + for i in range(n_nodes): + node = result.GetNode(i) + sl = node.GetSelectionList() + if sl is not None and sl.GetNumberOfTuples() > 0: + ids.append(np.asarray(vtk_to_numpy(sl)).astype(np.int64).ravel()) + if ids: + allids = np.concatenate(ids) + else: + allids = np.empty(0, dtype=np.int64) + return np.unique(allids), n_nodes + + +def main(): + out = sys.argv[1] + scene_filter = sys.argv[2] if len(sys.argv) > 2 else None + os.makedirs(out, exist_ok=True) + + from vtkmodules.vtkCommonCore import vtkVersion + import vtkmodules + + manifest = {"cases": {}} + n_ok = n_err = 0 + + for name, fn in SCENES.items(): + if scene_filter and scene_filter not in name: + continue + try: + ren, rw, surf = fn() + rw.Render() + ids, n_nodes = _select_point_ids(ren, rw) + orig = surf.GetPointData().GetArray("vtkOriginalPointIds") + orig_dtype = str(vtk_to_numpy(orig).dtype) if orig is not None else "MISSING" + np.savez(os.path.join(out, name + ".npz"), selected_point_ids=ids) + manifest["cases"][name] = { + "scene": name, + "n_selected": int(ids.size), + "n_nodes": int(n_nodes), + "orig_pointids_dtype": orig_dtype, + "selected_min": int(ids.min()) if ids.size else -1, + "selected_max": int(ids.max()) if ids.size else -1, + } + rw.Finalize() + n_ok += 1 + except Exception as e: # noqa: BLE001 + manifest["cases"][name] = {"scene": name, "error": repr(e)} + n_err += 1 + print(f"ERROR {name}: {e!r}", file=sys.stderr) + + manifest["provenance"] = { + "numpy": np.__version__, + "vtk_version": vtkVersion.GetVTKVersion(), + "vtkmodules_file": getattr(vtkmodules, "__file__", "?"), + } + with open(os.path.join(out, "manifest.json"), "w") as fh: + json.dump(manifest, fh, indent=2, sort_keys=True) + + print( + f"[run_select] backend={manifest['provenance']['vtkmodules_file']} " + f"vtk={manifest['provenance']['vtk_version']}" + ) + print(f"[run_select] wrote {n_ok} scenes, {n_err} errors -> {out}") + return 1 if n_err else 0 + + +if __name__ == "__main__": + sys.exit(main())