// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkOpenGLIndexBufferObject.h"
#include "vtkObjectFactory.h"

#include "vtkArrayDispatch.h"
#include "vtkArrayDispatchDataSetArrayList.h"
#include "vtkBatch.h"
#include "vtkCellArray.h"
#include "vtkDataArrayRange.h"
#include "vtkPoints.h"
#include "vtkPolygon.h" // for the shared ear-clip triangulation
#include "vtkSMPTools.h"
#include "vtkUnsignedCharArray.h"

#include "vtk_glad.h"

#include <set>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkOpenGLIndexBufferObject);

vtkOpenGLIndexBufferObject::vtkOpenGLIndexBufferObject()
{
  this->IndexCount = 0;
  this->SetType(vtkOpenGLIndexBufferObject::ElementArrayBuffer);
}

vtkOpenGLIndexBufferObject::~vtkOpenGLIndexBufferObject() = default;

namespace
{
struct AppendTrianglesBatchData
{
  vtkIdType TrianglesOffset;

  AppendTrianglesBatchData()
    : TrianglesOffset(0)
  {
  }
  ~AppendTrianglesBatchData() = default;
  AppendTrianglesBatchData& operator+=(const AppendTrianglesBatchData& other)
  {
    this->TrianglesOffset += other.TrianglesOffset;
    return *this;
  }
  AppendTrianglesBatchData operator+(const AppendTrianglesBatchData& other) const
  {
    AppendTrianglesBatchData result = *this;
    result += other;
    return result;
  }
};
using AppendTrianglesBatch = vtkBatch<AppendTrianglesBatchData>;
using AppendTrianglesBatches = vtkBatches<AppendTrianglesBatchData>;

// The ear-clip triangulation used to build rendering primitives lives on
// vtkPolygon (vtkPolygon::EarClipPolygon3D / EarClipTriangleCount) so the
// low-memory cell-to-primitive mapper uses the exact same implementation. The
// helpers are templated, so each call site instantiates zero-copy on the
// concrete point/cell range types with no abstraction overhead.
// A worker functor. The calculation is implemented in the function template
// for operator().
template <typename TPointsArray, typename TOffsets, typename TConnectivity>
struct AppendTrianglesFunctor
{
  TPointsArray* Points;
  TOffsets* Offsets;
  TConnectivity* Connectivity;
  std::vector<unsigned int>* IndexArray;
  std::vector<unsigned char>* EdgeArray;
  unsigned char* EdgeFlags;
  vtkIdType VOffset;

  AppendTrianglesBatches TriangleBatches;

  AppendTrianglesFunctor(TPointsArray* points, TOffsets* offsets, TConnectivity* connectivity,
    std::vector<unsigned int>* indexArray, std::vector<unsigned char>* edgeArray,
    unsigned char* edgeFlags, vtkIdType vOffset)
    : Points(points)
    , Offsets(offsets)
    , Connectivity(connectivity)
    , IndexArray(indexArray)
    , EdgeArray(edgeArray)
    , EdgeFlags(edgeFlags)
    , VOffset(vOffset)
  {
    // initialize batches
    this->TriangleBatches.Initialize(offsets->GetNumberOfValues() - 1, 1000);
  }

  void Initialize() {}

  void operator()(vtkIdType beginBatchId, vtkIdType endBatchId)
  {
    auto offsets = vtk::DataArrayValueRange<1, vtkIdType>(this->Offsets);

    for (vtkIdType batchId = beginBatchId; batchId < endBatchId; ++batchId)
    {
      AppendTrianglesBatch& batch = this->TriangleBatches[batchId];
      auto& batchNumberOfTriangles = batch.Data.TrianglesOffset;
      for (vtkIdType cellId = batch.BeginId; cellId < batch.EndId; ++cellId)
      {
        const auto cellSize = offsets[cellId + 1] - offsets[cellId];
        if (cellSize < 3)
        {
          continue;
        }
        // Any triangulation of a simple polygon with n vertices produces
        // exactly n - 2 triangles, so the fan path (size 3/4) and the ear-clip
        // path (size >= 5) yield the same triangle count. Degenerate triangles
        // are intentionally kept (the ear-clip path pads its output with
        // degenerate triangles to make up for coincident vertices it compacts
        // away), so the count is purely combinatorial and matches both the emit
        // phase and the cell-to-VTK-cell map.
        batchNumberOfTriangles += cellSize - 2;
      }
    }
  }

  void Reduce()
  {
    const auto globalSum = this->TriangleBatches.BuildOffsetsAndGetGlobalSum();
    vtkIdType totalNumTriangles = globalSum.TrianglesOffset;
    const auto indexArraySize = this->IndexArray->size();
    const auto edgeArraySize = this->EdgeArray ? this->EdgeArray->size() : 0;
    this->IndexArray->resize(this->IndexArray->size() + totalNumTriangles * 3);
    if (this->EdgeArray)
    {
      this->EdgeArray->resize(this->EdgeArray->size() + totalNumTriangles);
    }

    vtkSMPTools::For(0, this->TriangleBatches.GetNumberOfBatches(),
      [&](vtkIdType beginBatchId, vtkIdType endBatchId)
      {
        auto points = vtk::DataArrayTupleRange<3>(this->Points);
        auto offsets = vtk::DataArrayValueRange<1, vtkIdType>(this->Offsets);
        auto connectivity = vtk::DataArrayValueRange<1, vtkIdType>(this->Connectivity);

        // Per-thread scratch buffers reused across all batches handled by this
        // worker lambda. Allocated lazily on first encounter of a cellSize >= 5
        // polygon.
        std::vector<int> earPrev;
        std::vector<int> earNext;
        std::vector<int> earRing;

        for (vtkIdType batchId = beginBatchId; batchId < endBatchId; ++batchId)
        {
          AppendTrianglesBatch& batch = this->TriangleBatches[batchId];
          auto trianglesOffset = batch.Data.TrianglesOffset;
          auto indexArray = this->IndexArray->data() + indexArraySize + trianglesOffset * 3;
          auto edgeArray =
            this->EdgeArray ? this->EdgeArray->data() + edgeArraySize + trianglesOffset : nullptr;
          for (vtkIdType cellId = batch.BeginId; cellId < batch.EndId; ++cellId)
          {
            const auto cellSize = offsets[cellId + 1] - offsets[cellId];
            if (cellSize < 3)
            {
              continue;
            }
            const auto cell = connectivity.begin() + offsets[cellId];
            if (cellSize >= 5)
            {
              // Ear-clip triangulation for polygons with 5 or more vertices.
              // Non-convex polygons (which the fan would tessellate incorrectly,
              // producing overlapping or out-of-polygon triangles) are handled
              // correctly here. The emit lambda below resolves polygon-local
              // indices to absolute vertex IDs and computes per-edge polygon-
              // boundary flags by checking adjacency in the original cell order.
              // boundaryMask is the 3-bit polygon-boundary edge mask computed by
              // the triangulator over the compacted ring (bit 0: edge A-B,
              // bit 1: B-C, bit 2: C-A). a/b/c are polygon-local vertex indices.
              vtkIdType emittedTriangles = 0;
              auto emit = [&](int a, int b, int c, int boundaryMask)
              {
                const auto idA = cell[a];
                const auto idB = cell[b];
                const auto idC = cell[c];
                *indexArray++ = static_cast<unsigned int>(idA + this->VOffset);
                *indexArray++ = static_cast<unsigned int>(idB + this->VOffset);
                *indexArray++ = static_cast<unsigned int>(idC + this->VOffset);
                if (edgeArray)
                {
                  if (this->EdgeFlags)
                  {
                    int mask =
                      this->EdgeFlags[idA] + this->EdgeFlags[idB] * 2 + this->EdgeFlags[idC] * 4;
                    *edgeArray++ = static_cast<unsigned char>(boundaryMask & mask);
                  }
                  else
                  {
                    *edgeArray++ = static_cast<unsigned char>(boundaryMask);
                  }
                }
                ++emittedTriangles;
              };
              vtkPolygon::EarClipPolygon3D(
                points, cell, static_cast<int>(cellSize), earPrev, earNext, earRing, emit);
              // EarClipPolygon3D compacts coincident vertices, so it emits fewer
              // than cellSize - 2 triangles when the polygon has degenerate
              // edges. Degenerate triangles are intentionally kept: pad the
              // output with zero-area (dummy) triangles so the emitted count
              // equals cellSize - 2, keeping alignment with the count phase and
              // the cell-to-VTK-cell map. Zero-area triangles rasterize to
              // nothing, and their edge flag is 0 so no edges are drawn.
              const auto degenerateId = static_cast<unsigned int>(cell[0] + this->VOffset);
              for (; emittedTriangles < cellSize - 2; ++emittedTriangles)
              {
                *indexArray++ = degenerateId;
                *indexArray++ = degenerateId;
                *indexArray++ = degenerateId;
                if (edgeArray)
                {
                  *edgeArray++ = 0;
                }
              }
            }
            else
            {
              // Fan path for cellSize 3 or 4. Correct for triangles and any
              // simple quad (convex or not). Degenerate triangles are
              // intentionally kept, so every fan triangle is emitted (must
              // match the count phase).
              const auto& id1 = cell[0];
              for (int i = 1; i < cellSize - 1; i++)
              {
                const auto& id2 = cell[i];
                const auto& id3 = cell[i + 1];

                *indexArray++ = static_cast<unsigned int>(id1 + this->VOffset);
                *indexArray++ = static_cast<unsigned int>(id2 + this->VOffset);
                *indexArray++ = static_cast<unsigned int>(id3 + this->VOffset);
                if (edgeArray)
                {
                  // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator)
                  int val = cellSize == 3 ? 7 : i == 1 ? 3 : i == cellSize - 2 ? 6 : 2;
                  if (this->EdgeFlags)
                  {
                    int mask = 0;
                    mask =
                      this->EdgeFlags[id1] + this->EdgeFlags[id2] * 2 + this->EdgeFlags[id3] * 4;
                    *edgeArray++ = val & mask;
                  }
                  else
                  {
                    *edgeArray++ = val;
                  }
                }
              }
            }
          }
        }
      });
  }
};

struct AppendTrianglesWorker
{

  template <typename TPointsArray, typename TOffsets, typename TConnectivity>
  void operator()(TPointsArray* points, TOffsets* offsets, TConnectivity* connectivity,
    std::vector<unsigned int>* indexArray, std::vector<unsigned char>* edgeArray,
    unsigned char* edgeFlags, vtkIdType vOffset)
  {
    AppendTrianglesFunctor<TPointsArray, TOffsets, TConnectivity> functor(
      points, offsets, connectivity, indexArray, edgeArray, edgeFlags, vOffset);
    vtkSMPTools::For(0, functor.TriangleBatches.GetNumberOfBatches(), functor);
  }
};
} // end anon namespace

// used to create an IBO for triangle primitives
void vtkOpenGLIndexBufferObject::AppendTriangleIndexBuffer(std::vector<unsigned int>& indexArray,
  vtkCellArray* cells, vtkPoints* points, vtkIdType vOffset, std::vector<unsigned char>* edgeArray,
  vtkDataArray* edgeFlags)
{
  unsigned char* ucef = nullptr;
  if (edgeFlags)
  {
    ucef = vtkArrayDownCast<vtkUnsignedCharArray>(edgeFlags)->GetPointer(0);
  }

  // Define our dispatcher
  using Dispatcher = vtkArrayDispatch::Dispatch3ByArray<vtkArrayDispatch::PointArrays,
    vtkArrayDispatch::OffsetsArrays, vtkArrayDispatch::ConnectivityArrays>;
  AppendTrianglesWorker worker;
  // Execute the dispatcher:
  if (!Dispatcher::Execute(points->GetData(), cells->GetOffsetsArray(),
        cells->GetConnectivityArray(), worker, &indexArray, edgeArray, ucef, vOffset))
  {
    // Fallback to the generic implementation.
    worker(points->GetData(), cells->GetOffsetsArray(), cells->GetConnectivityArray(), &indexArray,
      edgeArray, ucef, vOffset);
  }
}

// used to create an IBO for triangle primitives
size_t vtkOpenGLIndexBufferObject::CreateTriangleIndexBuffer(vtkCellArray* cells, vtkPoints* points,
  std::vector<unsigned char>* edgeValues, vtkDataArray* edgeFlags)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }

  const bool hasOnlyTriangles =
    cells->GetNumberOfConnectivityIds() == cells->GetNumberOfCells() * 3;
  if ((cells->IsStorage32Bit() || cells->IsStorageFixedSize32Bit()) && hasOnlyTriangles)
  {
    // If connectivity ids are 32-bits and we only have triangles, upload them as-is.
    vtkCellArray::AOSArray32* array = cells->GetConnectivityAOSArray32();
    this->Upload(array->GetPointer(0), array->GetNumberOfValues(),
      vtkOpenGLIndexBufferObject::ElementArrayBuffer);
    this->IndexCount = array->GetNumberOfValues();
  }
  else
  {
    std::vector<unsigned int> indexArray;
    AppendTriangleIndexBuffer(indexArray, cells, points, 0, edgeValues, edgeFlags);
    this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
    this->IndexCount = indexArray.size();
  }

  return this->IndexCount;
}

// used to create an IBO for point primitives
void vtkOpenGLIndexBufferObject::AppendPointIndexBuffer(
  std::vector<unsigned int>& indexArray, vtkCellArray* cells, vtkIdType vOffset)
{
  const vtkIdType* indices(nullptr);
  vtkIdType npts(0);
  size_t targetSize = indexArray.size() + cells->GetNumberOfConnectivityIds();
  if (targetSize > indexArray.capacity())
  {
    targetSize = std::max<double>(targetSize, indexArray.capacity() * 1.5);
    indexArray.reserve(targetSize);
  }

  for (cells->InitTraversal(); cells->GetNextCell(npts, indices);)
  {
    for (int i = 0; i < npts; ++i)
    {
      indexArray.push_back(static_cast<unsigned int>(*(indices++) + vOffset));
    }
  }
}

// used to create an IBO for triangle primitives
size_t vtkOpenGLIndexBufferObject::CreatePointIndexBuffer(vtkCellArray* cells)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendPointIndexBuffer(indexArray, cells, 0);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

// used to create an IBO for primitives as lines.  This method treats each line segment
// as independent.  So for a triangle mesh you would get 6 verts per triangle
// 3 edges * 2 verts each.  With a line loop you only get 3 verts so half the storage.
// but... line loops are slower than line segments.
void vtkOpenGLIndexBufferObject::AppendTriangleLineIndexBuffer(
  std::vector<unsigned int>& indexArray, vtkCellArray* cells, vtkIdType vOffset)
{
  const vtkIdType* indices(nullptr);
  vtkIdType npts(0);
  size_t targetSize = indexArray.size() + 2 * cells->GetNumberOfConnectivityIds();
  if (targetSize > indexArray.capacity())
  {
    targetSize = std::max<double>(targetSize, indexArray.capacity() * 1.5);
    indexArray.reserve(targetSize);
  }

  for (cells->InitTraversal(); cells->GetNextCell(npts, indices);)
  {
    for (int i = 0; i < npts; ++i)
    {
      indexArray.push_back(static_cast<unsigned int>(indices[i] + vOffset));
      indexArray.push_back(static_cast<unsigned int>(indices[i < npts - 1 ? i + 1 : 0] + vOffset));
    }
  }
}

// used to create an IBO for primitives as lines.  This method treats each line segment
// as independent.  So for a triangle mesh you would get 6 verts per triangle
// 3 edges * 2 verts each.  With a line loop you only get 3 verts so half the storage.
// but... line loops are slower than line segments.
size_t vtkOpenGLIndexBufferObject::CreateTriangleLineIndexBuffer(vtkCellArray* cells)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendTriangleLineIndexBuffer(indexArray, cells, 0);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

// used to create an IBO for primitives as lines.  This method treats each
// line segment as independent.  So for a line strip you would get multiple
// line segments out
void vtkOpenGLIndexBufferObject::AppendLineIndexBuffer(
  std::vector<unsigned int>& indexArray, vtkCellArray* cells, vtkIdType vOffset)
{
  const vtkIdType* indices(nullptr);
  vtkIdType npts(0);

  // possibly adjust size
  if (cells->GetNumberOfConnectivityIds() > 2 * cells->GetNumberOfCells())
  {
    size_t targetSize =
      indexArray.size() + 2 * (cells->GetNumberOfConnectivityIds() - cells->GetNumberOfCells());
    if (targetSize > indexArray.capacity())
    {
      targetSize = std::max<double>(targetSize, indexArray.capacity() * 1.5);
      indexArray.reserve(targetSize);
    }
  }
  for (cells->InitTraversal(); cells->GetNextCell(npts, indices);)
  {
    for (int i = 0; i < npts - 1; ++i)
    {
      indexArray.push_back(static_cast<unsigned int>(indices[i] + vOffset));
      indexArray.push_back(static_cast<unsigned int>(indices[i + 1] + vOffset));
    }
  }
}

// used to create an IBO for primitives as lines.  This method treats each
// line segment as independent.  So for a line strip you would get multiple
// line segments out
size_t vtkOpenGLIndexBufferObject::CreateLineIndexBuffer(vtkCellArray* cells)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendLineIndexBuffer(indexArray, cells, 0);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

// used to create an IBO for triangle strips
size_t vtkOpenGLIndexBufferObject::CreateStripIndexBuffer(
  vtkCellArray* cells, bool wireframeTriStrips)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendStripIndexBuffer(indexArray, cells, 0, wireframeTriStrips);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

void vtkOpenGLIndexBufferObject::AppendStripIndexBuffer(std::vector<unsigned int>& indexArray,
  vtkCellArray* cells, vtkIdType vOffset, bool wireframeTriStrips)
{
  const vtkIdType* pts = nullptr;
  vtkIdType npts = 0;

  size_t triCount = cells->GetNumberOfConnectivityIds() - 2 * cells->GetNumberOfCells();
  size_t targetSize = wireframeTriStrips ? 2 * (triCount * 2 + 1) : triCount * 3;
  indexArray.reserve(targetSize);

  if (wireframeTriStrips)
  {
    for (cells->InitTraversal(); cells->GetNextCell(npts, pts);)
    {
      indexArray.push_back(static_cast<unsigned int>(pts[0] + vOffset));
      indexArray.push_back(static_cast<unsigned int>(pts[1] + vOffset));
      for (int j = 0; j < npts - 2; ++j)
      {
        indexArray.push_back(static_cast<unsigned int>(pts[j] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(pts[j + 2] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(pts[j + 1] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(pts[j + 2] + vOffset));
      }
    }
  }
  else
  {
    for (cells->InitTraversal(); cells->GetNextCell(npts, pts);)
    {
      for (int j = 0; j < npts - 2; ++j)
      {
        indexArray.push_back(static_cast<unsigned int>(pts[j] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(pts[j + 1 + j % 2] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(pts[j + 1 + (j + 1) % 2] + vOffset));
      }
    }
  }
}

// used to create an IBO for polys in wireframe with edge flags
void vtkOpenGLIndexBufferObject::AppendEdgeFlagIndexBuffer(
  std::vector<unsigned int>& indexArray, vtkCellArray* cells, vtkIdType vOffset, vtkDataArray* ef)
{
  const vtkIdType* pts(nullptr);
  vtkIdType npts(0);

  unsigned char* ucef = vtkArrayDownCast<vtkUnsignedCharArray>(ef)->GetPointer(0);

  // possibly adjust size
  if (cells->GetNumberOfConnectivityIds() > 2 * cells->GetNumberOfCells())
  {
    size_t targetSize =
      indexArray.size() + 2 * (cells->GetNumberOfConnectivityIds() - cells->GetNumberOfCells());
    if (targetSize > indexArray.capacity())
    {
      targetSize = std::max<double>(targetSize, indexArray.capacity() * 1.5);
      indexArray.reserve(targetSize);
    }
  }
  for (cells->InitTraversal(); cells->GetNextCell(npts, pts);)
  {
    for (int j = 0; j < npts; ++j)
    {
      if (ucef[pts[j]] && npts > 1) // draw this edge and poly is not degenerate
      {
        // determine the ending vertex
        vtkIdType nextVert = (j == npts - 1) ? pts[0] : pts[j + 1];
        indexArray.push_back(static_cast<unsigned int>(pts[j] + vOffset));
        indexArray.push_back(static_cast<unsigned int>(nextVert + vOffset));
      }
    }
  }
}

// used to create an IBO for polys in wireframe with edge flags
size_t vtkOpenGLIndexBufferObject::CreateEdgeFlagIndexBuffer(vtkCellArray* cells, vtkDataArray* ef)
{
  if (!cells->GetNumberOfCells())
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendEdgeFlagIndexBuffer(indexArray, cells, 0, ef);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

// used to create an IBO for point primitives
void vtkOpenGLIndexBufferObject::AppendVertexIndexBuffer(
  std::vector<unsigned int>& indexArray, vtkCellArray** cells, vtkIdType vOffset)
{
  const vtkIdType* indices(nullptr);
  vtkIdType npts(0);

  // we use a set to make them unique
  std::set<vtkIdType> vertsUsed;
  for (int j = 0; j < 4; j++)
  {
    for (cells[j]->InitTraversal(); cells[j]->GetNextCell(npts, indices);)
    {
      for (int i = 0; i < npts; ++i)
      {
        vertsUsed.insert(static_cast<unsigned int>(*(indices++) + vOffset));
      }
    }
  }

  // now put them into the vector
  size_t targetSize = indexArray.size() + vertsUsed.size();
  if (targetSize > indexArray.capacity())
  {
    targetSize = std::max<double>(targetSize, indexArray.capacity() * 1.5);
    indexArray.reserve(targetSize);
  }

  for (std::set<vtkIdType>::const_iterator i = vertsUsed.begin(); i != vertsUsed.end(); ++i)
  {
    indexArray.push_back(*i);
  }
}

// used to create an IBO for triangle primitives
size_t vtkOpenGLIndexBufferObject::CreateVertexIndexBuffer(vtkCellArray** cells)
{
  unsigned long totalCells = 0;
  for (int i = 0; i < 4; i++)
  {
    totalCells += cells[i]->GetNumberOfCells();
  }

  if (!totalCells)
  {
    this->IndexCount = 0;
    return 0;
  }
  std::vector<unsigned int> indexArray;
  AppendVertexIndexBuffer(indexArray, cells, 0);
  this->Upload(indexArray, vtkOpenGLIndexBufferObject::ElementArrayBuffer);
  this->IndexCount = indexArray.size();
  return indexArray.size();
}

//------------------------------------------------------------------------------
void vtkOpenGLIndexBufferObject::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}
VTK_ABI_NAMESPACE_END
