VTK/2DAPI

From KitwarePublic
< VTK
Jump to navigationJump to search

The 2D API is currently composed of two levels, a concreate class called vtkContext2D that is called by the paint functions of components operating within the 2D API, and a vtkContextDevice2D which is called by the vtkContext2D to actually draw to a context. The vtkContext2D contains a pointer to the derived class of the vtkContextDevice2D to do low level painting. This is the class that must be implemented for a new backend to be supported. The vtkContext2D builds up more complex 2D constructs on top of the basic constructs implemented in the device.

Currently we only have an OpenGL context device, but this could change in the future.

2D API

The class relationship diagram is shown above. This a bridge design pattern where Abstraction is vtkContext2D, Implementor is vtkContextDevice2D and ConcreteImplementors are vtkOpenGLContextDevice2D and vtkQtContextDevice2D. There is no RefinedAbstraction.

The headers for the two classes look as follows.

vtkContext2D

class VTK_CHARTS_EXPORT vtkContext2D : public vtkObject
{
public:
  vtkTypeRevisionMacro(vtkContext2D, vtkObject);
  virtual void PrintSelf(ostream &os, vtkIndent indent);
  static vtkContext2D *New();
  
  // Begin painting, a valid context device is required
  bool Begin(vtkContextDevice2D *device);

  // Perform any cleanup that might be necessary
  bool End();

  // Line drawing functions
  void DrawLine(float x1, float y1, float x2, float y2);
  void DrawLine(vtkPoints2D *points);
  void DrawPoly(float *x, float *y, int n);
  void DrawPoly(vtkPoints2D *points);
  void DrawRectangle(float x, float y, float width, float height);
  void DrawRectangle(float *p);
  void DrawRectangle(vtkPoints2D *points);

  // Point drawing functions
  void DrawPoint(float x, float y);
  void DrawPoints(float *x, float *y, int n);
  void DrawPoints(vtkPoints2D *points);

  // Manage the state of the painter
  void SetColor(int r, int g, int b, int a);
  void SetPointSize(float size);
  void SetLineWidth(float width);

protected:
  vtkContext2D();
  ~vtkContext2D();
};

vtkContextDevice2D (Abstract)

While the vtkContextDevice2D class is an abstract class. The header for the equivalent functionality is,

class VTK_CHARTS_EXPORT vtkContextDevice2D : public vtkObject
{
public:
  vtkTypeRevisionMacro(vtkContextDevice2D, vtkObject);
  virtual void PrintSelf(ostream &os, vtkIndent indent);
  static vtkContextDevice2D *New();
  
  // Set up the paint device context
  virtual void Begin(vtkRenderer* renderer) { }
  
  // Clean anything up once rendering has been completed
  virtual void End() { }

  // Line drawing functions
  virtual void DrawPoly(vtkPoints2D *points) = 0;

  // Point drawing functions
  virtual void DrawPoints(vtkPoints2D *points) = 0;

  // Manage the state of the paint device
  virtual void SetColor(int r, int g, int b, int a) = 0;
  virtual void SetPointSize(float size) = 0;
  virtual void SetLineWidth(float width) = 0;

protected:
  vtkContextDevice2D();
  ~vtkContextDevice2D();
};

Drawing Marks

The Views and Charts page contains details of a higher level API for drawing marks. Modifying this slightly to sit above the vtkContext2D API would allow a backend agnostic programmable glyph renderer.

class vtkPointMark
{
public:
  virtual int GetNumberOfParameters();

  virtual const char* GetParameterName( int param );
  virtual int GetParameterHandle( const char* paramName );
  virtual vtkInformation* GetParameterInformation( int param );
  virtual vtkInformation* GetParameterInformation( const char* name );

  virtual void ResetParameters();

  virtual void BindParameter( int param, vtkVariant& value );
  virtual void BindParameter( const char* paramName, vtkVariant& value );

  virtual void BindParameter( int param, vtkAbstractArray* values, int component );
  virtual void BindParameter( const char* paramName, vtkAbstractArray* values, int component );

  virtual int GetParameterBinding(
    int param, vtkVariant& constVal, vtkAbstractArray*& arrayVal, int& component );
  virtual int GetParameterBinding(
    const char* paramName, vtkVariant& constVal, vtkAbstractArray*& arrayVal, int& component );

  virtual void DrawMarks( vtkContext2D* context,
    vtkDataArray* xCoords, int xComponent,
    vtkDataArray* yCoords, int yComponent,
    vtkIdType start, vtkIdType end, vtkIdType stride );

protected:
  void GetParameterValuesForTuple( vtkIdType pt, vtkVariantArray* pvals );
  virtual void SetupParameters() = 0;
};

Performance Considerations

One of the goals of this project is to create charts that scale well to large data sets. I have been examining several techniques to accomplish this goal, mainly centered around passing arrays to drawing functions to draw multiple 2D primitives using only one function call. If we use a virtual base class for the context device then it is important to be able to draw large numbers of points without multiple function calls. Even without virtual function calls, the function call overhead can become the bottleneck, i.e. OpenGL glVertex3fv versus glVertexPointer.

Most rendering backends expect coordinates to be packed in memory in certain ways. One of the most common is as a 1D array with of length tuple * number of points. The vtkPoints2D and vtkPoints (3D) classes achieve this packing in their underlying data structure. Using this data structure will lead to the best performance, otherwise the context must repack the arrays (such as data coming from multiple columns in a table).

This is the motivation for using functions with signatures that take multiple points. In my initial testing this has worked well with millions of points and maps well to OpenGL's API. I will work on adding some benchmarking to properly quantify how well this scales. It would be an interesting way to summarize the performance of various architectures.

void DrawPoints(vtkPoints2D *points);