/*========================================================================= Program: Visualization Toolkit Module: vtkOpenGLContextDevice2DPrivate.h Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ /** * @class vtkOpenGL2ContextDevice2DPrivate * @brief Private class with storage and * utility functions for the vtkOpenGLContextDevice2D. * * * This class is for internal use only, it should not be included from anything * outside of the vtkCharts kit. It provides a shared private class that can be * used by vtkOpenGLContextDevice2D and derived classes. * * @warning * Internal use only. * * @sa * vtkOpenGLContextDevice2D vtkOpenGL2ContextDevice2D */ #ifndef vtkOpenGLContextDevice2DPrivate_h #define vtkOpenGLContextDevice2DPrivate_h #include "vtkOpenGLContextDevice2D.h" #include "vtkAbstractMapper.h" #include "vtkColor.h" #include "vtkFreeTypeTools.h" #include "vtkTextProperty.h" #include "vtkTextRenderer.h" #include "vtkTexture.h" #include "vtkStdString.h" #include "vtkUnicodeString.h" #include #include #include // .NAME vtkTextureImageCache - store vtkTexture and vtkImageData identified by // a unique key. // .SECTION Description // Creating and initializing a texture can be time consuming, // vtkTextureImageCache offers the ability to reuse them as much as possible. template class vtkTextureImageCache { public: struct CacheData { vtkSmartPointer ImageData; vtkSmartPointer Texture; // Use to generate texture coordinates. Computing this is as expensive as // rendering the texture, so we cache it. vtkTextRenderer::Metrics Metrics; }; //@{ /** * CacheElement associates a unique key to some cache. */ struct CacheElement: public std::pair { // Default constructor CacheElement() : std::pair(Key(), CacheData()){} // Construct a partial CacheElement with no CacheData // This can be used for temporary CacheElement used to search a given // key into the cache list. CacheElement(const Key& key) : std::pair(key, CacheData()){} // Standard constructor of CacheElement CacheElement(const Key& key, const CacheData& cacheData) : std::pair(key, cacheData){} // Operator tuned to be used when searching into the cache list using // std::find() bool operator==(const CacheElement& other)const { // Here we cheat and make the comparison only on the key, this allows // us to use std::find() to search for a given key. return this->first == other.first; } }; //@} /** * Construct a texture image cache with a maximum number of texture of 50. */ vtkTextureImageCache() { this->MaxSize = 50; } /** * Search the cache list to see if a given key already exists. Returns true * if the key is found, false otherwise. */ bool IsKeyInCache(const Key& key)const { return std::find(this->Cache.begin(), this->Cache.end(), key) != this->Cache.end(); } /** * Return the cache associated to a key. If the key doesn't exist yet in the * cache list, create a new cache. * The returned cache is moved at the beginning of the cache list for faster * search next time. The most use cache is faster to be searched. */ CacheData& GetCacheData(const Key& key); //@{ /** * Release all the OpenGL Pixel Buffer Object(PBO) associated with the * textures of the cache list. */ void ReleaseGraphicsResources(vtkWindow* window) { typename std::list::iterator it; for (it = this->Cache.begin(); it != this->Cache.end(); ++it) { it->second.Texture->ReleaseGraphicsResources(window); } } //@} protected: //@{ /** * Add a new cache entry into the cache list. Enforce the MaxSize size of the * list by removing the least used cache if needed. */ CacheData& AddCacheData(const Key& key, const CacheData& cacheData) { assert(!this->IsKeyInCache(key)); if (this->Cache.size() >= this->MaxSize) { this->Cache.pop_back(); } this->Cache.push_front(CacheElement(key, cacheData)); return this->Cache.begin()->second; } //@} /** * List of a pair of key and cache data. */ std::list Cache; //@{ /** * Maximum size the cache list can be. */ size_t MaxSize; }; //@} template typename vtkTextureImageCache::CacheData& vtkTextureImageCache ::GetCacheData(const Key& key) { typename std::list::iterator it = std::find(this->Cache.begin(), this->Cache.end(), CacheElement(key)); if (it != this->Cache.end()) { return it->second; } CacheData cacheData; cacheData.ImageData = vtkSmartPointer::New(); cacheData.Texture = vtkSmartPointer::New(); cacheData.Texture->SetInputData(cacheData.ImageData); return this->AddCacheData(key, cacheData); } // .NAME TextPropertyKey - unique key for a vtkTextProperty and text // .SECTION Description // Uniquely describe a pair of vtkTextProperty and text. template struct TextPropertyKey { //@{ /** * Transform a text property into an unsigned long */ static vtkTypeUInt32 GetIdFromTextProperty(vtkTextProperty* tprop) { size_t id; vtkFreeTypeTools *ftt = vtkFreeTypeTools::GetInstance(); ftt->MapTextPropertyToId(tprop, &id); // The hash is really a uint32 that gets cast to a size_t in // MapTextPropertyToId, so this possible truncation is safe. // Yay legacy APIs. vtkTypeUInt32 hash = static_cast(id); // Ensure that the above implementation assumption still holds. If it // doesn't we'll need to rework this cache class a bit. assert("Hash is really a uint32" && static_cast(hash) == id); // Since we cache the text metrics (which includes orientation and alignment // info), we'll need to store the alignment options, since // MapTextPropertyToId intentionally ignores these: int tmp = tprop->GetJustification(); hash = vtkFreeTypeTools::HashBuffer(&tmp, sizeof(int), hash); tmp = tprop->GetVerticalJustification(); hash = vtkFreeTypeTools::HashBuffer(&tmp, sizeof(int), hash); return hash; } //@} //@{ /** * Creates a TextPropertyKey. */ TextPropertyKey(vtkTextProperty* textProperty, const StringType& text, int dpi) { this->TextPropertyId = GetIdFromTextProperty(textProperty); this->FontSize = textProperty->GetFontSize(); double color[3]; textProperty->GetColor(color); this->Color.Set(static_cast(color[0] * 255), static_cast(color[1] * 255), static_cast(color[2] * 255), static_cast(textProperty->GetOpacity() * 255)); this->Text = text; this->DPI = dpi; } //@} /** * Compares two TextPropertyKeys with each other. Returns true if they are * identical: same text and text property */ bool operator==(const TextPropertyKey& other)const { return this->TextPropertyId == other.TextPropertyId && this->FontSize == other.FontSize && this->Text == other.Text && this->Color[0] == other.Color[0] && this->Color[1] == other.Color[1] && this->Color[2] == other.Color[2] && this->Color[3] == other.Color[3] && this->DPI == other.DPI; } unsigned short FontSize; vtkColor4ub Color; // States in the function not to use more than 32 bits - int works fine here. vtkTypeUInt32 TextPropertyId; StringType Text; int DPI; }; typedef TextPropertyKey UTF8TextPropertyKey; typedef TextPropertyKey UTF16TextPropertyKey; class vtkOpenGLContextDevice2D::Private { public: Private() { this->Texture = nullptr; this->TextureProperties = vtkContextDevice2D::Linear | vtkContextDevice2D::Stretch; this->SpriteTexture = nullptr; this->SavedDepthTest = GL_TRUE; this->SavedAlphaTest = GL_TRUE; this->SavedStencilTest = GL_TRUE; this->SavedBlend = GL_TRUE; this->SavedDrawBuffer = 0; this->SavedClearColor[0] = this->SavedClearColor[1] = this->SavedClearColor[2] = this->SavedClearColor[3] = 0.0f; this->TextCounter = 0; this->GLExtensionsLoaded = true; this->GLSL = true; this->PowerOfTwoTextures = false; } ~Private() { if (this->Texture) { this->Texture->Delete(); this->Texture = nullptr; } if (this->SpriteTexture) { this->SpriteTexture->Delete(); this->SpriteTexture = nullptr; } } void SaveGLState(bool colorBuffer = false) { this->SavedDepthTest = glIsEnabled(GL_DEPTH_TEST); if (colorBuffer) { this->SavedAlphaTest = glIsEnabled(GL_ALPHA_TEST); this->SavedStencilTest = glIsEnabled(GL_STENCIL_TEST); this->SavedBlend = glIsEnabled(GL_BLEND); glGetFloatv(GL_COLOR_CLEAR_VALUE, this->SavedClearColor); glGetIntegerv(GL_DRAW_BUFFER, &this->SavedDrawBuffer); } } void RestoreGLState(bool colorBuffer = false) { this->SetGLCapability(GL_DEPTH_TEST, this->SavedDepthTest); if (colorBuffer) { this->SetGLCapability(GL_ALPHA_TEST, this->SavedAlphaTest); this->SetGLCapability(GL_STENCIL_TEST, this->SavedStencilTest); this->SetGLCapability(GL_BLEND, this->SavedBlend); if(this->SavedDrawBuffer != GL_BACK_LEFT) { glDrawBuffer(this->SavedDrawBuffer); } int i = 0; bool colorDiffer = false; while(!colorDiffer && i < 4) { colorDiffer=this->SavedClearColor[i++] != 0.0; } if(colorDiffer) { glClearColor(this->SavedClearColor[0], this->SavedClearColor[1], this->SavedClearColor[2], this->SavedClearColor[3]); } } } void SetGLCapability(GLenum capability, GLboolean state) { if (state) { glEnable(capability); } else { glDisable(capability); } } float* TexCoords(float* f, int n) { float* texCoord = new float[2*n]; float minX = f[0]; float minY = f[1]; float maxX = f[0]; float maxY = f[1]; float* fptr = f; for(int i = 0; i < n; ++i) { minX = fptr[0] < minX ? fptr[0] : minX; maxX = fptr[0] > maxX ? fptr[0] : maxX; minY = fptr[1] < minY ? fptr[1] : minY; maxY = fptr[1] > maxY ? fptr[1] : maxY; fptr+=2; } fptr = f; if (this->TextureProperties & vtkContextDevice2D::Repeat) { const double* textureBounds = this->Texture->GetInput()->GetBounds(); float rangeX = (textureBounds[1] - textureBounds[0]) ? textureBounds[1] - textureBounds[0] : 1.; float rangeY = (textureBounds[3] - textureBounds[2]) ? textureBounds[3] - textureBounds[2] : 1.; for (int i = 0; i < n; ++i) { texCoord[i*2] = (fptr[0]-minX) / rangeX; texCoord[i*2+1] = (fptr[1]-minY) / rangeY; fptr+=2; } } else // this->TextureProperties & vtkContextDevice2D::Stretch { float rangeX = (maxX - minX)? maxX - minX : 1.f; float rangeY = (maxY - minY)? maxY - minY : 1.f; for (int i = 0; i < n; ++i) { texCoord[i*2] = (fptr[0]-minX)/rangeX; texCoord[i*2+1] = (fptr[1]-minY)/rangeY; fptr+=2; } } return texCoord; } vtkVector2i FindPowerOfTwo(const vtkVector2i& size) { vtkVector2i pow2(1, 1); for (int i = 0; i < 2; ++i) { while (pow2[i] < size[i]) { pow2[i] *= 2; } } return pow2; } GLuint TextureFromImage(vtkImageData *image, vtkVector2f& texCoords) { if (image->GetScalarType() != VTK_UNSIGNED_CHAR) { vtkGenericWarningMacro("Invalid image format: expected unsigned char."); return 0; } int bytesPerPixel = image->GetNumberOfScalarComponents(); int size[3]; image->GetDimensions(size); vtkVector2i newImg = this->FindPowerOfTwo(vtkVector2i(size[0], size[1])); for (int i = 0; i < 2; ++i) { texCoords[i] = size[i] / float(newImg[i]); } unsigned char *dataPtr = new unsigned char[newImg[0] * newImg[1] * bytesPerPixel]; unsigned char *origPtr = static_cast(image->GetScalarPointer()); for (int i = 0; i < newImg[0]; ++i) { for (int j = 0; j < newImg[1]; ++j) { for (int k = 0; k < bytesPerPixel; ++k) { if (i < size[0] && j < size[1]) { dataPtr[i * bytesPerPixel + j * newImg[0] * bytesPerPixel + k] = origPtr[i * bytesPerPixel + j * size[0] * bytesPerPixel + k]; } else { dataPtr[i * bytesPerPixel + j * newImg[0] * bytesPerPixel + k] = k == 3 ? 0 : 255; } } } } GLuint tmpIndex(0); GLint glFormat = bytesPerPixel == 3 ? GL_RGB : GL_RGBA; GLint glInternalFormat = bytesPerPixel == 3 ? GL_RGB8 : GL_RGBA8; glGenTextures(1, &tmpIndex); glBindTexture(GL_TEXTURE_2D, tmpIndex); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexImage2D(GL_TEXTURE_2D, 0 , glInternalFormat, newImg[0], newImg[1], 0, glFormat, GL_UNSIGNED_BYTE, static_cast(dataPtr)); delete [] dataPtr; return tmpIndex; } GLuint TextureFromImage(vtkImageData *image) { if (image->GetScalarType() != VTK_UNSIGNED_CHAR) { cout << "Error = not an unsigned char..." << endl; return 0; } int bytesPerPixel = image->GetNumberOfScalarComponents(); int size[3]; image->GetDimensions(size); unsigned char *dataPtr = static_cast(image->GetScalarPointer()); GLuint tmpIndex(0); GLint glFormat = bytesPerPixel == 3 ? GL_RGB : GL_RGBA; GLint glInternalFormat = bytesPerPixel == 3 ? GL_RGB8 : GL_RGBA8; glGenTextures(1, &tmpIndex); glBindTexture(GL_TEXTURE_2D, tmpIndex); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexImage2D(GL_TEXTURE_2D, 0 , glInternalFormat, size[0], size[1], 0, glFormat, GL_UNSIGNED_BYTE, static_cast(dataPtr)); return tmpIndex; } vtkTexture *Texture; unsigned int TextureProperties; vtkTexture *SpriteTexture; // Store the previous GL state so that we can restore it when complete GLboolean SavedDepthTest; GLboolean SavedAlphaTest; GLboolean SavedStencilTest; GLboolean SavedBlend; GLint SavedDrawBuffer; GLfloat SavedClearColor[4]; int TextCounter; vtkVector2i Dim; vtkVector2i Offset; bool GLExtensionsLoaded; bool GLSL; bool PowerOfTwoTextures; //@{ /** * Cache for text images. Generating texture for strings is expensive, * we cache the textures here for a faster reuse. */ mutable vtkTextureImageCache TextTextureCache; mutable vtkTextureImageCache MathTextTextureCache; }; //@} /////////////////////////////////////////////////////////////////////////////////// /** * @class vtkOpenGL2ContextDevice2DCellArrayHelper * @brief Private class with storage and utility functions for the * vtkOpenGLContextDevice2D. * * This class is for internal use only, it should not be included from anything * outside of the vtkCharts kit. It provides a shared private class that can be * used by vtkOpenGLContextDevice2D and derived classes. * * The helper class is used to directly render each of the vtkCellArray instances * contained in a vtkPolyData object instance without the use of an external mapper. * * @warning Currently only renders two types of vtkPolyData primitives; Lines and * Polygons. * * @warning Internal use only. * * @sa vtkOpenGL2ContextDevice2D */ class vtkOpenGLContextDevice2D::CellArrayHelper { public: enum CellType { LINE = 1, POLYGON //TRIANGLE_STRIPS }; CellArrayHelper (vtkOpenGLContextDevice2D* device) : Device(device) , Points(nullptr) , PointIds(nullptr) , Colors(nullptr) , NumPointsCell(0) { }; /** * Draw primitives as specified by cellType. */ void Draw (int cellType, vtkCellArray* cellArray, vtkPoints* points, float x, float y, float scale, int scalarMode, vtkUnsignedCharArray* colors = nullptr) { this->Points = points; this->Colors = colors; this->CellColors->SetNumberOfComponents(colors->GetNumberOfComponents()); switch (cellType) { case LINE: this->DrawLines(cellArray, scalarMode, x, y, scale); break; case POLYGON: this->DrawPolygons(cellArray, scalarMode, x, y, scale); break; } }; private: CellArrayHelper(const CellArrayHelper&) = delete; void operator=(const CellArrayHelper&) = delete; /** * Cache points and colors of the current cell in arrays. */ void MapCurrentCell (float const posX, float const posY, float const scale, vtkIdType cellId, int scalarMode) { this->CellPoints.reserve(this->NumPointsCell * 2); /* 2 components */ this->CellColors->SetNumberOfTuples(this->NumPointsCell); /* RGBA */ for (int i = 0; i < this->NumPointsCell; i++) { double point[3]; this->Points->GetPoint(this->PointIds[i], point); // Only 2D meshes are supported float const x = static_cast(point[0]) + posX; float const y = static_cast(point[1]) + posY; this->CellPoints.push_back(x * scale); this->CellPoints.push_back(y * scale); // Grab specific point / cell colors vtkIdType mappedColorId = VTK_SCALAR_MODE_USE_POINT_DATA; switch (scalarMode) { case VTK_SCALAR_MODE_USE_POINT_DATA: mappedColorId = this->PointIds[i]; break; case VTK_SCALAR_MODE_USE_CELL_DATA: mappedColorId = cellId; break; default: std::cerr << "Scalar mode not supported!" << std::endl; break; } this->CellColors->SetTuple(i, mappedColorId, this->Colors); } }; /** * Batch all of the line primitives in an array and draw them using * ContextDevice2D::DrawLines. The batched array is cached and only reloaded if * the vtkCellArray has changed. */ void DrawLines(vtkCellArray* cellArray, int scalarMode, float const x, float const y, float const scale) { if (cellArray->GetMTime() > this->LinesLoadingTime) { this->Lines.clear(); this->LineColors->Reset(); // Pre-allocate batched array vtkIdType const numVertices = cellArray->GetNumberOfCells() * 2;// points/line this->Lines.reserve(numVertices * 2); // components this->LineColors->SetNumberOfComponents(this->Colors->GetNumberOfComponents()); this->LineColors->SetNumberOfTuples(numVertices); vtkIdType cellId = 0; vtkIdType vertOffset = 0; for (cellArray->InitTraversal(); cellArray->GetNextCell(this->NumPointsCell, this->PointIds); cellId++) { this->MapCurrentCell(x, y, scale, cellId, scalarMode); // Accumulate the current cell in the batched array for (int i = 0; i < this->NumPointsCell; i++) { this->Lines.push_back(this->CellPoints[2 * i]); this->Lines.push_back(this->CellPoints[2 * i + 1]); double* color4 = this->CellColors->GetTuple(i); this->LineColors->InsertTuple4(vertOffset + i, color4[0], color4[1], color4[2], color4[3]); } vertOffset += this->NumPointsCell; this->CellColors->Reset(); this->CellPoints.clear(); } this->LinesLoadingTime.Modified(); } this->Device->DrawLines(&this->Lines[0], this->Lines.size() / 2, static_cast(this->LineColors->GetVoidPointer(0)), this->LineColors->GetNumberOfComponents()); }; /** * Pre-computes the total number of polygon vertices after converted into triangles. * vertices to pre-allocate the batch arrays. */ vtkIdType GetCountTriangleVertices(vtkCellArray* cellArray) { vtkIdType cellId = 0; vtkIdType numTriVert = 0; for (cellArray->InitTraversal(); cellArray->GetNextCell(this->NumPointsCell, this->PointIds); cellId++) { numTriVert += 3 * (this->NumPointsCell - 2); }; return numTriVert; }; /** * Convert all of the polygon primitives into triangles and draw them as a batch using * ContextDevice2D::DrawTriangles. The batched array is cached and only reloaded if * the vtkCellArray has changed. */ void DrawPolygons(vtkCellArray* cellArray, int scalarMode, float const x, float const y, float const scale) { if (cellArray->GetMTime() > this->PolygonsLoadingTime) { this->PolyTri.clear(); this->PolyColors->Reset(); // Pre-allocate batched array vtkIdType const totalTriVert = this->GetCountTriangleVertices(cellArray); this->PolyTri.reserve(totalTriVert * 2); // components this->PolyColors->SetNumberOfComponents(this->Colors->GetNumberOfComponents()); this->PolyColors->SetNumberOfTuples(totalTriVert); // Traverse polygons and convert to triangles vtkIdType cellId = 0; vtkIdType vertOffset = 0; this->PolyColors->SetNumberOfComponents(this->Colors->GetNumberOfComponents()); for (cellArray->InitTraversal(); cellArray->GetNextCell(this->NumPointsCell, this->PointIds); cellId++) { this->MapCurrentCell(x, y, scale, cellId, scalarMode); // Convert current cell (polygon) to triangles for (int i = 0; i < this->NumPointsCell - 2; i++) { this->PolyTri.push_back(this->CellPoints[0]); this->PolyTri.push_back(this->CellPoints[1]); this->PolyTri.push_back(this->CellPoints[i * 2 + 2]); this->PolyTri.push_back(this->CellPoints[i * 2 + 3]); this->PolyTri.push_back(this->CellPoints[i * 2 + 4]); this->PolyTri.push_back(this->CellPoints[i * 2 + 5]); // Insert triangle vertex color vtkIdType const triangOffset = vertOffset + 3 * i; double* color4 = this->CellColors->GetTuple(0); this->PolyColors->InsertTuple4(triangOffset, color4[0], color4[1], color4[2], color4[3]); color4 = this->CellColors->GetTuple(i + 1); this->PolyColors->InsertTuple4(triangOffset + 1, color4[0], color4[1], color4[2], color4[3]); color4 = this->CellColors->GetTuple(i + 2); this->PolyColors->InsertTuple4(triangOffset + 2, color4[0], color4[1], color4[2], color4[3]); } vertOffset += 3 * (this->NumPointsCell - 2); // Triangle verts current cell this->CellColors->Reset(); this->CellPoints.clear(); } this->PolygonsLoadingTime.Modified(); } this->Device->CoreDrawTriangles(this->PolyTri, static_cast(this->PolyColors->GetVoidPointer(0)), 4); }; vtkOpenGLContextDevice2D* Device; vtkPoints* Points; vtkIdType* PointIds; vtkUnsignedCharArray* Colors; //@{ /** * Current vtkPolyData cell. */ vtkIdType NumPointsCell; std::vector CellPoints; vtkNew CellColors; //@} //@{ /** * Cached polygon primitives (as triangles). */ std::vector PolyTri; vtkNew PolyColors; vtkTimeStamp PolygonsLoadingTime; //@} //@{ /** * Cached line primitives. */ std::vector Lines; vtkNew LineColors; vtkTimeStamp LinesLoadingTime; //@} }; #endif // VTKOPENGLCONTEXTDEVICE2DPRIVATE_H // VTK-HeaderTest-Exclude: vtkOpenGLContextDevice2DPrivate.h