Notice to GitKraken users: A vulnerability has been found in the SSH key generation of GitKraken versions 7.6.0 to 8.0.0 (https://www.gitkraken.com/blog/weak-ssh-key-fix). If you use GitKraken and have generated a SSH key using one of these versions, please remove it both from your local workstation and from your LRZ GitLab profile.

21.10.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit ee6e96a5 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Extended GeometryData API:

* Added optional picking information to GeometryData API
* GeometryRenderer processor renders the picking information into a separate texture if present
* geometrydata.h contains a stub for a possible rewrite of the API (more flexible buffer layout)
parent 26c8c5a6
...@@ -64,21 +64,19 @@ namespace campvis { ...@@ -64,21 +64,19 @@ namespace campvis {
} }
FaceGeometry* FaceGeometry::clone() const { FaceGeometry* FaceGeometry::clone() const {
return new FaceGeometry(_vertices, _textureCoordinates, _colors, _normals); FaceGeometry* toReturn = new FaceGeometry(_vertices, _textureCoordinates, _colors, _normals);
toReturn->setPickingInformation(_pickingInformation);
return toReturn;
} }
size_t FaceGeometry::getLocalMemoryFootprint() const { size_t FaceGeometry::getLocalMemoryFootprint() const {
size_t sum = 0; size_t sum = 0;
if (_verticesBuffer != 0) for (size_t i = 0; i < NUM_BUFFERS; ++i) {
sum += sizeof(tgt::BufferObject); if (_buffers[i] != nullptr)
if (_texCoordsBuffer != 0) sum += sizeof(tgt::BufferObject);
sum += sizeof(tgt::BufferObject); }
if (_colorsBuffer != 0)
sum += sizeof(tgt::BufferObject);
if (_normalsBuffer != 0)
sum += sizeof(tgt::BufferObject);
return sizeof(*this) + sum + (sizeof(tgt::vec3) * (_vertices.size() + _textureCoordinates.size() + _normals.size())) + (sizeof(tgt::vec4) * _colors.size()); return sizeof(*this) + sum + (sizeof(tgt::vec3) * (_vertices.size() + _textureCoordinates.size() + _normals.size())) + (sizeof(tgt::vec4) * (_colors.size() + _pickingInformation.size()));
} }
size_t FaceGeometry::size() const { size_t FaceGeometry::size() const {
...@@ -101,6 +99,16 @@ namespace campvis { ...@@ -101,6 +99,16 @@ namespace campvis {
return _textureCoordinates; return _textureCoordinates;
} }
const std::vector<tgt::col4>& FaceGeometry::getPickingInformation() const {
return _pickingInformation;
}
void FaceGeometry::setPickingInformation(const std::vector<tgt::col4>& pickingInformation) {
tgtAssert(pickingInformation.size() == 0 || pickingInformation.size() == _vertices.size(), "Number of picking informations does not match number of vertices!");
_pickingInformation = pickingInformation;
_buffersDirty = true;
}
const tgt::vec3& FaceGeometry::getFaceNormal() const { const tgt::vec3& FaceGeometry::getFaceNormal() const {
return _faceNormal; return _faceNormal;
} }
...@@ -124,6 +132,8 @@ namespace campvis { ...@@ -124,6 +132,8 @@ namespace campvis {
vao.setVertexAttributePointer(2, _colorsBuffer); vao.setVertexAttributePointer(2, _colorsBuffer);
if (_normalsBuffer) if (_normalsBuffer)
vao.setVertexAttributePointer(3, _normalsBuffer); vao.setVertexAttributePointer(3, _normalsBuffer);
if (_pickingBuffer)
vao.setVertexAttributePointer(4, _pickingBuffer);
LGL_ERROR; LGL_ERROR;
glDrawArrays(mode, 0, static_cast<GLsizei>(_vertices.size())); glDrawArrays(mode, 0, static_cast<GLsizei>(_vertices.size()));
...@@ -150,6 +160,10 @@ namespace campvis { ...@@ -150,6 +160,10 @@ namespace campvis {
_normalsBuffer = new tgt::BufferObject(tgt::BufferObject::ARRAY_BUFFER, tgt::BufferObject::USAGE_STATIC_DRAW); _normalsBuffer = new tgt::BufferObject(tgt::BufferObject::ARRAY_BUFFER, tgt::BufferObject::USAGE_STATIC_DRAW);
_normalsBuffer->data(&_normals.front(), _normals.size() * sizeof(tgt::vec3), tgt::BufferObject::FLOAT, 3); _normalsBuffer->data(&_normals.front(), _normals.size() * sizeof(tgt::vec3), tgt::BufferObject::FLOAT, 3);
} }
if (! _pickingInformation.empty()) {
_pickingBuffer = new tgt::BufferObject(tgt::BufferObject::ARRAY_BUFFER, tgt::BufferObject::USAGE_STATIC_DRAW);
_pickingBuffer->data(&_pickingInformation.front(), _pickingInformation.size() * sizeof(tgt::col4), tgt::BufferObject::UNSIGNED_BYTE, 4);
}
} }
catch (tgt::Exception& e) { catch (tgt::Exception& e) {
LERROR("Error creating OpenGL Buffer objects: " << e.what()); LERROR("Error creating OpenGL Buffer objects: " << e.what());
...@@ -177,6 +191,7 @@ namespace campvis { ...@@ -177,6 +191,7 @@ namespace campvis {
std::vector<tgt::vec3> verts, texCoords, norms; std::vector<tgt::vec3> verts, texCoords, norms;
std::vector<tgt::vec4> cols; std::vector<tgt::vec4> cols;
std::vector<tgt::col4> picks;
size_t lastIndex = _vertices.size() - 1; size_t lastIndex = _vertices.size() - 1;
float lastDistance = distanceToPlane(_vertices.back(), p, pNormal, epsilon); float lastDistance = distanceToPlane(_vertices.back(), p, pNormal, epsilon);
...@@ -195,6 +210,8 @@ namespace campvis { ...@@ -195,6 +210,8 @@ namespace campvis {
cols.push_back(tgt::mix(_colors[lastIndex], _colors[i], t)); cols.push_back(tgt::mix(_colors[lastIndex], _colors[i], t));
if (!_normals.empty()) if (!_normals.empty())
norms.push_back(tgt::mix(_normals[lastIndex], _normals[i], t)); norms.push_back(tgt::mix(_normals[lastIndex], _normals[i], t));
if (!_pickingInformation.empty())
picks.push_back(_pickingInformation[i]);
} }
// case 2: last vertex inside, this vertex outside clip region => clip // case 2: last vertex inside, this vertex outside clip region => clip
else if (lastDistance <= 0 && currrentDistance > 0) { else if (lastDistance <= 0 && currrentDistance > 0) {
...@@ -207,6 +224,8 @@ namespace campvis { ...@@ -207,6 +224,8 @@ namespace campvis {
cols.push_back(tgt::mix(_colors[lastIndex], _colors[i], t)); cols.push_back(tgt::mix(_colors[lastIndex], _colors[i], t));
if (!_normals.empty()) if (!_normals.empty())
norms.push_back(tgt::mix(_normals[lastIndex], _normals[i], t)); norms.push_back(tgt::mix(_normals[lastIndex], _normals[i], t));
if (!_pickingInformation.empty())
picks.push_back(_pickingInformation[lastIndex]);
} }
// case 1.2 + case 3: current vertix in front of plane => keep // case 1.2 + case 3: current vertix in front of plane => keep
...@@ -218,13 +237,17 @@ namespace campvis { ...@@ -218,13 +237,17 @@ namespace campvis {
cols.push_back(_colors[i]); cols.push_back(_colors[i]);
if (!_normals.empty()) if (!_normals.empty())
norms.push_back(_normals[i]); norms.push_back(_normals[i]);
if (!_pickingInformation.empty())
picks.push_back(_pickingInformation[i]);
} }
lastIndex = i; lastIndex = i;
lastDistance = currrentDistance; lastDistance = currrentDistance;
} }
return FaceGeometry(verts, texCoords, cols, norms); FaceGeometry toReturn(verts, texCoords, cols, norms);
toReturn.setPickingInformation(picks);
return toReturn;
} }
tgt::Bounds FaceGeometry::getWorldBounds() const { tgt::Bounds FaceGeometry::getWorldBounds() const {
...@@ -238,6 +261,10 @@ namespace campvis { ...@@ -238,6 +261,10 @@ namespace campvis {
return ! _textureCoordinates.empty(); return ! _textureCoordinates.empty();
} }
bool FaceGeometry::hasPickingInformation() const {
return !_pickingInformation.empty();
}
void FaceGeometry::applyTransformationToVertices(const tgt::mat4& t) { void FaceGeometry::applyTransformationToVertices(const tgt::mat4& t) {
for (size_t i = 0; i < _vertices.size(); ++i) { for (size_t i = 0; i < _vertices.size(); ++i) {
tgt::vec4 tmp = t * tgt::vec4(_vertices[i], 1.f); tgt::vec4 tmp = t * tgt::vec4(_vertices[i], 1.f);
...@@ -247,4 +274,5 @@ namespace campvis { ...@@ -247,4 +274,5 @@ namespace campvis {
_buffersDirty = true; _buffersDirty = true;
} }
} }
\ No newline at end of file
...@@ -105,6 +105,18 @@ namespace campvis { ...@@ -105,6 +105,18 @@ namespace campvis {
* \return _textureCoordinates * \return _textureCoordinates
*/ */
const std::vector<tgt::vec3>& getTextureCoordinates() const; const std::vector<tgt::vec3>& getTextureCoordinates() const;
/**
* The list of picking information colors, may be empty.
* \return _pickingInformation
*/
const std::vector<tgt::col4>& getPickingInformation() const;
/**
* Sets the picking information of this geometry to \a pickingInformation
* \param pickingInformation The new list of picking information for this geometry
*/
void setPickingInformation(const std::vector<tgt::col4>& pickingInformation);
/** /**
* The normal vector of this face. * The normal vector of this face.
...@@ -134,6 +146,8 @@ namespace campvis { ...@@ -134,6 +146,8 @@ namespace campvis {
virtual tgt::Bounds getWorldBounds() const; virtual tgt::Bounds getWorldBounds() const;
/// \see GeometryData::hasTextureCoordinates /// \see GeometryData::hasTextureCoordinates
virtual bool hasTextureCoordinates() const; virtual bool hasTextureCoordinates() const;
/// \see GeometryData::hasPickingInformation
virtual bool hasPickingInformation() const;
/// \see GeometryData::applyTransformationToVertices /// \see GeometryData::applyTransformationToVertices
virtual void applyTransformationToVertices(const tgt::mat4& t); virtual void applyTransformationToVertices(const tgt::mat4& t);
...@@ -149,6 +163,8 @@ namespace campvis { ...@@ -149,6 +163,8 @@ namespace campvis {
std::vector<tgt::vec4> _colors; ///< The list of vertex colors, may be empty. std::vector<tgt::vec4> _colors; ///< The list of vertex colors, may be empty.
std::vector<tgt::vec3> _normals; ///< The list of vertex normals, may be empty. std::vector<tgt::vec3> _normals; ///< The list of vertex normals, may be empty.
std::vector<tgt::col4> _pickingInformation; ///< The list of picking information colors, max be empty.
tgt::vec3 _faceNormal; ///< The normal vector of this face. tgt::vec3 _faceNormal; ///< The normal vector of this face.
static const std::string loggerCat_; static const std::string loggerCat_;
......
...@@ -37,20 +37,22 @@ namespace campvis { ...@@ -37,20 +37,22 @@ namespace campvis {
GeometryData::GeometryData() GeometryData::GeometryData()
: AbstractData() : AbstractData()
, _buffersDirty(true) , _buffersDirty(true)
, _verticesBuffer(0) , _verticesBuffer(nullptr)
, _texCoordsBuffer(0) , _texCoordsBuffer(nullptr)
, _colorsBuffer(0) , _colorsBuffer(nullptr)
, _normalsBuffer(0) , _normalsBuffer(nullptr)
, _pickingBuffer(nullptr)
{ {
} }
GeometryData::GeometryData(const GeometryData& rhs) GeometryData::GeometryData(const GeometryData& rhs)
: AbstractData(rhs) : AbstractData(rhs)
, _buffersDirty(true) , _buffersDirty(true)
, _verticesBuffer(0) , _verticesBuffer(nullptr)
, _texCoordsBuffer(0) , _texCoordsBuffer(nullptr)
, _colorsBuffer(0) , _colorsBuffer(nullptr)
, _normalsBuffer(0) , _normalsBuffer(nullptr)
, _pickingBuffer(nullptr)
{ {
} }
...@@ -74,21 +76,23 @@ namespace campvis { ...@@ -74,21 +76,23 @@ namespace campvis {
void GeometryData::deleteBuffers() const { void GeometryData::deleteBuffers() const {
for (int i = 0; i < NUM_BUFFERS; ++i) { for (int i = 0; i < NUM_BUFFERS; ++i) {
delete _buffers[i]; delete _buffers[i];
_buffers[i] = 0; _buffers[i] = nullptr;
} }
} }
size_t GeometryData::getVideoMemoryFootprint() const { size_t GeometryData::getVideoMemoryFootprint() const {
size_t sum = 0; size_t sum = 0;
if (_verticesBuffer != 0) if (_verticesBuffer != nullptr)
sum += _verticesBuffer->getBufferSize(); sum += _verticesBuffer->getBufferSize();
if (_texCoordsBuffer != 0) if (_texCoordsBuffer != nullptr)
sum += _texCoordsBuffer->getBufferSize(); sum += _texCoordsBuffer->getBufferSize();
if (_colorsBuffer != 0) if (_colorsBuffer != nullptr)
sum += _colorsBuffer->getBufferSize(); sum += _colorsBuffer->getBufferSize();
if (_normalsBuffer != 0) if (_normalsBuffer != nullptr)
sum += _normalsBuffer->getBufferSize(); sum += _normalsBuffer->getBufferSize();
if (_pickingBuffer != nullptr)
sum += _pickingBuffer->getBufferSize();
return sum; return sum;
} }
...@@ -109,4 +113,8 @@ namespace campvis { ...@@ -109,4 +113,8 @@ namespace campvis {
return _normalsBuffer; return _normalsBuffer;
} }
const tgt::BufferObject* GeometryData::getPickingBuffer() const {
return _pickingBuffer;
}
} }
\ No newline at end of file
...@@ -36,6 +36,37 @@ namespace tgt { ...@@ -36,6 +36,37 @@ namespace tgt {
} }
namespace campvis { namespace campvis {
class CAMPVIS_CORE_API GeometryDataBase {
public:
/// Enumeration for defining semantics of stored buffer data
enum ElementSemantic {
VERTEX = 0, ///< Vextex data
TEXTURE_COORDINATE = 1, ///< Texture coordinate data
COLOR = 2, ///< Color data
NORMAL = 3, ///< Normal data
PICKING_INFORMATION = 4 ///< Picking information
};
/// Enumeration for defining the host data type of the element
enum ElementHostType {
UINT8,
UINT16,
UINT32,
FLOAT,
VEC2,
VEC3,
VEC4
};
};
template<GeometryDataBase::ElementSemantic SEMANTIC>
struct GeometryDataTraits {};
template<>
struct GeometryDataTraits<GeometryDataBase::VERTEX> {
typedef tgt::vec3 HostType;
};
/** /**
* Abstract base class for geometry data in CAMPVis. * Abstract base class for geometry data in CAMPVis.
* *
...@@ -50,7 +81,7 @@ namespace campvis { ...@@ -50,7 +81,7 @@ namespace campvis {
* - Vertex normals: Vertex attribute 3 * - Vertex normals: Vertex attribute 3
* *
*/ */
class CAMPVIS_CORE_API GeometryData : public AbstractData, public IHasWorldBounds { class CAMPVIS_CORE_API GeometryData : public AbstractData, public GeometryDataBase, public IHasWorldBounds {
public: public:
/** /**
* Constructor * Constructor
...@@ -91,12 +122,24 @@ namespace campvis { ...@@ -91,12 +122,24 @@ namespace campvis {
*/ */
virtual tgt::Bounds getWorldBounds() const = 0; virtual tgt::Bounds getWorldBounds() const = 0;
template<GeometryDataBase::ElementSemantic SEMANTIC>
const std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* getElementData() const;
template<GeometryDataBase::ElementSemantic SEMANTIC>
void setElementData(std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* elementData);
/** /**
* Returns whether the geometry has texture coordinates. * Returns whether the geometry has texture coordinates.
* \return True if this geometry sets texture coordinates during rendering. * \return True if this geometry sets texture coordinates during rendering.
*/ */
virtual bool hasTextureCoordinates() const = 0; virtual bool hasTextureCoordinates() const = 0;
/**
* Returns whether this geometry has picking information.
* \return True if this geometry sets picking information during rendering.
*/
virtual bool hasPickingInformation() const = 0;
/** /**
* Applies the transformation matrix \a t to each vertex of this geometry. * Applies the transformation matrix \a t to each vertex of this geometry.
* \param t Transformation matrix to apply * \param t Transformation matrix to apply
...@@ -131,6 +174,13 @@ namespace campvis { ...@@ -131,6 +174,13 @@ namespace campvis {
*/ */
const tgt::BufferObject* getNormalsBuffer() const; const tgt::BufferObject* getNormalsBuffer() const;
/**
* Returns the Pointer to the OpenGL Buffer with the vertex normals.
* May be 0 if none are present or not yet created.
* \return _normalsBuffer
*/
const tgt::BufferObject* getPickingBuffer() const;
/// \see AbstractData::getVideoMemoryFootprint() /// \see AbstractData::getVideoMemoryFootprint()
virtual size_t getVideoMemoryFootprint() const; virtual size_t getVideoMemoryFootprint() const;
...@@ -140,10 +190,13 @@ namespace campvis { ...@@ -140,10 +190,13 @@ namespace campvis {
*/ */
void deleteBuffers() const; void deleteBuffers() const;
std::vector<void*> _elementPointers;
// mutable to support const lazy initialization // mutable to support const lazy initialization
mutable bool _buffersDirty; ///< Flag whether the buffers are dirty (i.e. need to be (re)initialized) mutable bool _buffersDirty; ///< Flag whether the buffers are dirty (i.e. need to be (re)initialized)
enum { NUM_BUFFERS = 4 }; ///< Number of buffers in _buffers array enum { NUM_BUFFERS = 5 }; ///< Number of buffers in _buffers array
union { union {
struct { struct {
...@@ -151,6 +204,7 @@ namespace campvis { ...@@ -151,6 +204,7 @@ namespace campvis {
mutable tgt::BufferObject* _texCoordsBuffer; ///< Pointer to the OpenGL Buffer with the vertex texture coordinates mutable tgt::BufferObject* _texCoordsBuffer; ///< Pointer to the OpenGL Buffer with the vertex texture coordinates
mutable tgt::BufferObject* _colorsBuffer; ///< Pointer to the OpenGL Buffer with the vertex colors mutable tgt::BufferObject* _colorsBuffer; ///< Pointer to the OpenGL Buffer with the vertex colors
mutable tgt::BufferObject* _normalsBuffer; ///< Pointer to the OpenGL Buffer with the vertex normals mutable tgt::BufferObject* _normalsBuffer; ///< Pointer to the OpenGL Buffer with the vertex normals
mutable tgt::BufferObject* _pickingBuffer; ///< Pointer to the OpenGL Buffer with the picking information
}; };
mutable tgt::BufferObject* _buffers[NUM_BUFFERS]; ///< Array of all buffers mutable tgt::BufferObject* _buffers[NUM_BUFFERS]; ///< Array of all buffers
...@@ -161,6 +215,27 @@ namespace campvis { ...@@ -161,6 +215,27 @@ namespace campvis {
static const std::string loggerCat_; static const std::string loggerCat_;
}; };
template<GeometryDataBase::ElementSemantic SEMANTIC>
const std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* GeometryData::getElementData() const {
if (_elementPointers.size() >= SEMANTIC) {
return static_cast< std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* >(_elementPointers[SEMANTIC]);
}
return nullptr;
}
template<GeometryDataBase::ElementSemantic SEMANTIC>
void GeometryData::setElementData(std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* elementData) {
if (_elementPointers.size() < SEMANTIC + 1)
_elementPointers.resize(SEMANTIC, nullptr);
void* oldPtr = _elementPointers[SEMANTIC];
if (oldPtr != elementData)
delete static_cast< std::vector<typename GeometryDataTraits<SEMANTIC>::HostType>* >(oldPtr);
_elementPointers[SEMANTIC] = elementData;
}
} }
#endif // GEOMETRYDATA_H__ #endif // GEOMETRYDATA_H__
...@@ -114,6 +114,15 @@ namespace campvis { ...@@ -114,6 +114,15 @@ namespace campvis {
return true; return true;
} }
bool GeometryDataCollection::hasPickingInformation() const {
for (size_t i = 0; i < _geometries.size(); ++i) {
if (! _geometries[i]->hasPickingInformation())
return false;
}
return true;
}
void GeometryDataCollection::applyTransformationToVertices(const tgt::mat4& t) { void GeometryDataCollection::applyTransformationToVertices(const tgt::mat4& t) {
for (size_t i = 0; i < _geometries.size(); ++i) for (size_t i = 0; i < _geometries.size(); ++i)
_geometries[i]->applyTransformationToVertices(t); _geometries[i]->applyTransformationToVertices(t);
...@@ -136,5 +145,4 @@ namespace campvis { ...@@ -136,5 +145,4 @@ namespace campvis {
return sum; return sum;
} }
} }
\ No newline at end of file
...@@ -109,6 +109,12 @@ namespace campvis { ...@@ -109,6 +109,12 @@ namespace campvis {
*/ */
virtual bool hasTextureCoordinates() const; virtual bool hasTextureCoordinates() const;
/**
* Returns whether all geometries of the collection have picking information.
* \return True if all geometries of the collection set picking information during rendering.
*/
virtual bool hasPickingInformation() const;
/** /**
* Applies the transformation matrix \a t to each vertex of this geometry. * Applies the transformation matrix \a t to each vertex of this geometry.
* \param t Transformation matrix to apply * \param t Transformation matrix to apply
......
...@@ -84,6 +84,7 @@ namespace campvis { ...@@ -84,6 +84,7 @@ namespace campvis {
_textureCoordinates = rhs._textureCoordinates; _textureCoordinates = rhs._textureCoordinates;
_colors = rhs._colors; _colors = rhs._colors;
_normals = rhs._normals; _normals = rhs._normals;
_pickingInformation = rhs._pickingInformation;
// delete old VBOs and null pointers // delete old VBOs and null pointers
deleteIndicesBuffer(); deleteIndicesBuffer();
...@@ -91,23 +92,28 @@ namespace campvis { ...@@ -91,23 +92,28 @@ namespace campvis {
return *this; return *this;
} }
const std::vector<tgt::col4>& IndexedMeshGeometry::getPickingInformation() const {
return _pickingInformation;
}
void IndexedMeshGeometry::setPickingInformation(const std::vector<tgt::col4>& pickingInformation) {
tgtAssert(pickingInformation.size() == 0 || pickingInformation.size() == _vertices.size(), "Number of picking informations does not match number of vertices!");
_pickingInformation = pickingInformation;
_buffersDirty = true;
}
IndexedMeshGeometry* IndexedMeshGeometry::clone() const { IndexedMeshGeometry* IndexedMeshGeometry::clone() const {
return new IndexedMeshGeometry(_indices, _vertices, _textureCoordinates, _colors, _normals); IndexedMeshGeometry* toReturn = new IndexedMeshGeometry(_indices, _vertices, _textureCoordinates, _colors, _normals);
toReturn->setPickingInformation(_pickingInformation);
return toReturn;
} }
size_t IndexedMeshGeometry::getLocalMemoryFootprint() const { size_t IndexedMeshGeometry::getLocalMemoryFootprint() const {
size_t sum = 0; size_t sum = 0;
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
if (_indicesBuffer != 0) if (_buffers[i] != nullptr)
sum += sizeof(tgt::BufferObject); sum += sizeof(tgt::BufferObject);
if (_verticesBuffer != 0) }
sum += sizeof(tgt::BufferObject);
if (_texCoordsBuffer != 0)
sum += sizeof(tgt::BufferObject);
if (_colorsBuffer != 0)
sum += sizeof(tgt::BufferObject);
if (_normalsBuffer != 0)
sum += sizeof(tgt::BufferObject);