C++ API Reference
gpuCache/gpuCacheSample.h
#ifndef _gpuCacheSample_h_
#define _gpuCacheSample_h_
//-
//**************************************************************************/
// Copyright 2015 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
//**************************************************************************/
//+
//
// ShapeSample
//
// Topology and attribute samples.
//
#include <maya/MBoundingBox.h>
#include <maya/MMatrix.h>
#include <maya/MHWGeometry.h>
#include <Alembic/Util/Digest.h>
#include <mutex>
#include <vector>
#include <memory>
#include "gpuCacheConfig.h"
namespace GPUCache {
/*==============================================================================
* CLASS ArrayBase
*============================================================================*/
// Non-templated base class for hash-consed arrays.
class ArrayBase
{
public:
typedef Alembic::Util::Digest Digest;
/*----- classes -----*/
// Helper classes for implementing std::unordered_map of
// IndexBuffers.
struct Key
{
Key(const size_t bytes,
const Digest& digest
) : fBytes(bytes),
fDigest(digest)
{}
const size_t fBytes;
const Digest fDigest;
};
struct KeyHash
{
std::size_t operator()(Key const& key) const
{
std::size_t seed = 0;
GPUCache::hash_combine(seed, key.fBytes);
GPUCache::hash_combine(seed, key.fDigest.words[0]);
GPUCache::hash_combine(seed, key.fDigest.words[1]);
return seed;
}
};
struct KeyEqualTo
{
bool operator()(Key const& x,
Key const& y) const
{
return (x.fBytes == y.fBytes &&
x.fDigest == y.fDigest);
}
};
/*----- static member functions -----*/
typedef void (*Callback)(const Key& array);
// Registers a callback that will be invoked each time a new array is
// created.
static void registerCreationCallback(Callback callback);
// Unregisters a previously registered creation callback.
static void unregisterCreationCallback(Callback callback);
// Registers a callback that will be invoked each time an array is
// destructed.
static void registerDestructionCallback(Callback callback);
// Unregisters a previously registered destruction callback.
static void unregisterDestructionCallback(Callback callback);
/*----- member functions -----*/
virtual ~ArrayBase();
// The number of bytes in the array.
size_t bytes() const { return fKey.fBytes; }
// Returns the Murmur3 checksum of the array. This is used to
// accelerate lookups in containers.
Digest digest() const { return fKey.fDigest; }
// Returns the key of the array. This is used to accelerate
// lookups in containers.
const Key& key() const { return fKey; }
// Returns true if this instance directly implements the ReadableArray
// interface. See Array::getReadable()
bool isReadable() const { return fIsReadable; }
protected:
/*----- member functions -----*/
ArrayBase(size_t bytes, const Digest& digest, bool isReadable);
private:
/*----- data members -----*/
// Prohibited and not implemented.
ArrayBase(const ArrayBase&);
const ArrayBase& operator= (const ArrayBase&);
// We need to store this data member in the base class because
// they are used by the destructor.
const Key fKey;
// Is the data in this array owned by the gpuCache plugin? If not then the
// raw contents may only be accessed from the main thread.
const bool fIsReadable;
};
/*==============================================================================
* CLASS ArrayReadInterface
*============================================================================*/
// Provides a minimal interface for accessing the data of an Array. Hides the
// more complicated process of temporarily accessing the data in a
// non-ReadableArray. When one of these is returned, the data will be accessible
// and the pointer returned by get() will be valid, only until this instance is
// deleted.
template<typename T>
class ArrayReadInterface
{
public:
virtual const T* get() const = 0;
virtual ~ArrayReadInterface() {}
};
/*==============================================================================
* CLASS Array
*============================================================================*/
template <class T> class ReadableArray;
// A class that provides a minimalistic virtual interface to an
// array. This is used to encapsulate various memory management
// schemes.
template <class T>
class Array : public ArrayBase
{
public:
/*----- member functions -----*/
// Free this reference to the array. It might or might not delete
// the encapsulated array depending on the detail of the memory
// management. The destructor also takes care of removing its
// entry from the ArrayRegistry.
~Array() override;
// Gets a pointer to a readable array or basic read-interface. If this array is
// internally in a readable format then this is a simple cast. If this is a
// non-readable array, then this will convert back to a new readable array which
// may not be fast and is not safe outside the main thread. So these functions may
// not be called except on the main thread.
// See the comment in MayaBufferArray for more details.
virtual std::shared_ptr<const ArrayReadInterface<T> > getReadable() const = 0;
virtual std::shared_ptr<ReadableArray<T> > getReadableArray() const = 0;
// The number of elements in the array.
size_t size() const { return bytes() / sizeof(T); }
protected:
/*----- member functions -----*/
Array(size_t size, const Digest& digest, bool isReadable)
: ArrayBase(size * sizeof(T), digest, isReadable)
{}
};
// An array where the contents are available for reading from system memory. These are
// safe to use from outside the main thread.
template <typename T>
class ReadableArray : public Array<T>, public ArrayReadInterface<T>, public std::enable_shared_from_this<ReadableArray<T> >
{
public:
typedef typename Array<T>::Digest Digest;
~ReadableArray() override {}
// If overhead from enable_shared_from_this is a problem, then an alternative is
// to lookup using our own ArrayBase key into the ArrayRegistry. Or possibly to
// take a shared_ptr<Array> as a parameter and cast it to shared_ptr<ReadableArray>,
// verifying that it equals this.
std::shared_ptr<const ArrayReadInterface<T> > getReadable() const override
{
// enable_shared_from_this<ReadableArray<T>> is a dependant base, sp shared_from_this()
// fails to compile on Linux gcc as an unqualified symbol unless we add this->
return this->shared_from_this();
}
std::shared_ptr<ReadableArray<T> > getReadableArray() const override
{
// shared_from_this tries to stick you with a shared_ptr<const T> for const functions.
return const_cast<ReadableArray<T> *>(this)->shared_from_this();
}
protected:
ReadableArray(size_t size, const Digest& digest)
: Array<T>(size, digest, true)
{}
};
/*==============================================================================
* CLASS ArrayRegistry
*============================================================================*/
// A registry of all Array currently allocated in the process. This
// registry is used to ensure that we share arrays that have the same
// contents. The digest cryptographic hash-key is used to determine if
// two buffers have the same content.
//
// Note that using cryptographic hash-keys avoids the cost of having
// to compare full buffers but it opens-up the very small possibility
// that we end-up sharing unrelated buffers due to a hash-key
// collision. We therefore also include the size of the arrays in the
// lookup to make sure that a hash-key collision won't lead to access
// violations.
//
// ArrayRegistry is currently instantiated only for
// the index_t and float types.
//
// ArrayRegistry is thread-safe.
template <typename T>
class ArrayRegistry
{
public:
typedef Alembic::Util::Digest Digest;
// Returns the registry mutex. It must be held while calling
// lookup() and insert().
static std::mutex& mutex();
// If an array with the same digest and size is found in the registry,
// a pointer to that array is returned. Otherwise, a null pointer
// is returned.
//
// NOTE: the registry mutex must be held by the current thread
// while calling lookup().
// We store two separate sets of Arrays. One for readable arrays
// and one for non-readable. Callers specify which type they can accept
// (or both if they can take either) by selecting the appropriate lookup
// function.
// A readable and non-readable array with the same key are allowed to
// exist at the same time, though that should be a rare occurrence.
// See the comment in MayaBufferArray for more details on this.
static std::shared_ptr<Array<T> > lookup(
const Digest& digest,
size_t size
);
static std::shared_ptr<Array<T> > lookupNonReadable(
const Digest& digest,
size_t size
);
static std::shared_ptr<ReadableArray<T> > lookupReadable(
const Digest& digest,
size_t size
);
// Inserts the given array into the registry. This function
// assumes that lookup() has been called before to ensure that an
// array with the same content is not already present in the
// registry.
//
// NOTE: the registry mutex must be held by the current thread
// while calling insert().
static void insert(std::shared_ptr<Array<T> > array);
};
/*==============================================================================
* CLASS SharedArray
*============================================================================*/
// A wrapper around boost shared_array smart pointers.
template <class T>
class SharedArray : public ReadableArray<T>
{
public:
typedef typename Array<T>::Digest Digest;
/*----- static member functions -----*/
// Returns a pointer to a Array that has the same
// content as the buffer passed-in as determined by the computed
// digest hash-key.
static std::shared_ptr<ReadableArray<T> > create(
const GPUCache::shared_array<T>& data, size_t size);
// Alternate faster version if you already know the Digest hash.
static std::shared_ptr<ReadableArray<T> > create(
const GPUCache::shared_array<T>& data, Digest digest, size_t size);
/*----- member functions -----*/
~SharedArray() override;
const T* get() const override;
private:
/*----- member functions -----*/
// The constructor is declare private to force user to go through
// the create() factory member function.
struct MakeSharedEnabler;
SharedArray(
const GPUCache::shared_array<T>& data,
size_t size,
const Digest& digest
) : ReadableArray<T>(size, digest),
fData(data)
{}
/*----- data members -----*/
const GPUCache::shared_array<T> fData;
};
/*==============================================================================
* CLASS IndexBuffer
*============================================================================*/
// A buffer containing vertex attribute data.
class IndexBuffer
{
public:
typedef Alembic::Util::Digest Digest;
typedef unsigned int index_t;
typedef std::shared_ptr<const ReadableArray<index_t> > ReadableArrayPtr;
typedef std::shared_ptr<const ArrayReadInterface<index_t> > ReadInterfacePtr;
/*----- classes -----*/
// Helper classes for implementing std::unordered_map of
// IndexBuffers.
struct Key
{
Key(
const std::shared_ptr<Array<index_t> >& array,
const size_t beginIdx,
const size_t endIdx
) : fArrayKey(array->key()),
fBeginIdx(beginIdx),
fEndIdx(endIdx)
{}
const ArrayBase::Key fArrayKey;
const size_t fBeginIdx;
const size_t fEndIdx;
};
struct KeyHash
{
std::size_t operator()(Key const& key) const
{
std::size_t seed = 0;
GPUCache::hash_combine(seed, ArrayBase::KeyHash()(key.fArrayKey));
GPUCache::hash_combine(seed, key.fBeginIdx);
GPUCache::hash_combine(seed, key.fEndIdx);
return seed;
}
};
struct KeyEqualTo
{
bool operator()(Key const& x,
Key const& y) const
{
return (ArrayBase::KeyEqualTo()(x.fArrayKey, y.fArrayKey) &&
x.fBeginIdx == y.fBeginIdx &&
x.fEndIdx == y.fEndIdx);
}
};
/*----- static member functions -----*/
static std::shared_ptr<IndexBuffer> create(
const std::shared_ptr<Array<index_t> >& array
)
{ return create( array, 0, array->size() ); }
// Note that the endIdx is exclusive following the STL iterator
// convention.
static std::shared_ptr<IndexBuffer> create(
const std::shared_ptr<Array<index_t> >& array,
const size_t beginIdx,
const size_t endIdx
);
// Return the number of currently allocated IndexBuffer
// within the process.
static size_t nbAllocated();
// Return the number of bytes occupied by the currently allocated
// IndexBuffer's within the process.
static size_t nbAllocatedBytes();
/*----- member functions -----*/
~IndexBuffer();
ReadInterfacePtr readableInterface() const { return fArray->getReadable(); }
size_t numIndices() const { return fEndIdx - fBeginIdx; }
size_t bytes() const { return numIndices() * sizeof(index_t); }
const std::shared_ptr<Array<index_t> >& array() const
{ return fArray; }
size_t beginIdx() const
{ return fBeginIdx; }
size_t endIdx() const
{ return fEndIdx; }
// Graft an alternate array instance into this IndexBuffer. This allows the source
// internal data to be converted to Maya-managed hardware buffers after the ShapeSample
// structure is already in place (or vice-versa). The new buffer should represent the
// same contents as the old buffer.
// Note: this breaks const conventions.
void ReplaceArrayInstance(std::shared_ptr<Array<index_t> >& newArray) const;
private:
/*----- member functions -----*/
// The constructor is declare private to force user to go through
// the create() factory member function.
struct MakeSharedEnabler;
IndexBuffer(
const std::shared_ptr<Array<index_t> >& array,
const size_t beginIdx,
const size_t endIdx
) : fArray(array),
fBeginIdx(beginIdx),
fEndIdx(endIdx)
{}
/*----- data members -----*/
const std::shared_ptr<Array<index_t> > fArray;
const size_t fBeginIdx;
const size_t fEndIdx;
};
/*==============================================================================
* CLASS VertexBuffer
*============================================================================*/
// A buffer containing vertex attribute data.
class VertexBuffer
{
public:
typedef Alembic::Util::Digest Digest;
typedef std::shared_ptr<const ReadableArray<float> > ReadableArrayPtr;
typedef std::shared_ptr<const ArrayReadInterface<float> > ReadInterfacePtr;
/*----- classes -----*/
// Helper classes for implementing std::unordered_map of
// VertexBuffers.
struct Key
{
Key(
const std::shared_ptr<Array<float> >& array,
) : fArrayKey(array->key()),
fName(desc.name().asChar()),
fSemantic(desc.semantic()),
fDataType(desc.dataType()),
fDimension(desc.dimension()),
fOffset(desc.offset()),
fStride(desc.stride())
{}
const ArrayBase::Key fArrayKey;
std::string fName;
int fDimension;
int fOffset;
int fStride;
};
struct KeyHash
{
std::size_t operator()(Key const& key) const
{
std::size_t seed = 0;
GPUCache::hash_combine(seed, ArrayBase::KeyHash()(key.fArrayKey));
GPUCache::hash_combine(seed, key.fName);
GPUCache::hash_combine(seed, key.fSemantic);
GPUCache::hash_combine(seed, key.fDataType);
GPUCache::hash_combine(seed, key.fDimension);
GPUCache::hash_combine(seed, key.fOffset);
GPUCache::hash_combine(seed, key.fStride);
return seed;
}
};
struct KeyEqualTo
{
bool operator()(Key const& x,
Key const& y) const
{
return (ArrayBase::KeyEqualTo()(x.fArrayKey, y.fArrayKey) &&
x.fName == y.fName &&
x.fSemantic == y.fSemantic &&
x.fDataType == y.fDataType &&
x.fDimension == y.fDimension &&
x.fOffset == y.fOffset &&
x.fStride == y.fStride);
}
};
/*----- static member functions -----*/
static std::shared_ptr<VertexBuffer> createPositions(
const std::shared_ptr<Array<float> >& array);
static std::shared_ptr<VertexBuffer> createNormals(
const std::shared_ptr<Array<float> >& array);
static std::shared_ptr<VertexBuffer> createUVs(
const std::shared_ptr<Array<float> >& array);
// Return the number of currently allocated IndexBuffer
// within the process.
static size_t nbAllocated();
// Return the number of bytes occupied by the currently allocated
// IndexBuffer's within the process.
static size_t nbAllocatedBytes();
/*----- member functions -----*/
~VertexBuffer();
ReadInterfacePtr readableInterface() const { return fArray->getReadable(); }
size_t numVerts() const
{ return fArray->size() / fDescriptor.dimension(); }
size_t bytes() const
{ return fArray->bytes(); }
const std::shared_ptr<Array<float> >& array() const
{ return fArray; }
const MHWRender::MVertexBufferDescriptor& descriptor() const
{ return fDescriptor; }
// Graft an alternate array instance into this VertexBuffer. This allows the source
// internal data to be converted to Maya-managed hardware buffers after the ShapeSample
// structure is already in place (or vice-versa). The new buffer should represent the
// same contents as the old buffer.
// Note: this breaks const conventions.
void ReplaceArrayInstance(std::shared_ptr<Array<float> >& newArray) const;
private:
// The constructor is declare private to force user to go through
// the create() factory member function.
struct MakeSharedEnabler;
/*----- static member functions -----*/
static std::shared_ptr<VertexBuffer> create(
const std::shared_ptr<Array<float> >& array,
);
/*----- member functions -----*/
VertexBuffer(
const std::shared_ptr<Array<float> >& array,
)
: fArray(array),
fDescriptor(desc)
{}
/*----- data members -----*/
const std::shared_ptr<Array<float> > fArray;
};
/*==============================================================================
* CLASS ShapeSample
*============================================================================*/
// A sample of the topology and vertex attributes for a given time.
class ShapeSample
{
public:
/*----- static member functions -----*/
static std::shared_ptr<ShapeSample> create(
double timeInSeconds,
size_t numWires,
size_t numVerts,
const std::shared_ptr<IndexBuffer>& wireVertIndices,
const std::shared_ptr<IndexBuffer>& triangleVertIndices,
const std::shared_ptr<VertexBuffer>& positions,
const MBoundingBox& boundingBox,
const MColor& diffuseColor,
bool visibility);
static std::shared_ptr<ShapeSample> create(
double timeInSeconds,
size_t numWires,
size_t numVerts,
const std::shared_ptr<IndexBuffer>& wireVertIndices,
const std::vector<std::shared_ptr<IndexBuffer> >& triangleVertIndices,
const std::shared_ptr<VertexBuffer>& positions,
const MBoundingBox& boundingBox,
const MColor& diffuseColor,
bool visibility);
static std::shared_ptr<ShapeSample> createEmptySample(
double timeInSeconds)
{
return ShapeSample::create(
timeInSeconds,
0,
0,
std::shared_ptr<IndexBuffer>(),
std::shared_ptr<IndexBuffer>(),
std::shared_ptr<VertexBuffer>(),
GPUCache::Config::kDefaultGrayColor,
false);
}
static std::shared_ptr<ShapeSample> createBoundingBoxPlaceHolderSample(
double timeInSeconds, const MBoundingBox& bbox, bool visibility)
{
std::shared_ptr<ShapeSample> sample = ShapeSample::create(
timeInSeconds,
0,
0,
std::shared_ptr<IndexBuffer>(),
std::shared_ptr<IndexBuffer>(),
std::shared_ptr<VertexBuffer>(),
bbox,
GPUCache::Config::kDefaultGrayColor,
visibility);
sample->setBoundingBoxPlaceHolder();
return sample;
}
/*----- member functions -----*/
~ShapeSample();
void setNormals(const std::shared_ptr<VertexBuffer>& normals);
void setUVs(const std::shared_ptr<VertexBuffer>& uvs);
double timeInSeconds() const { return fTimeInSeconds; }
bool visibility() const { return fVisibility; }
size_t numWires() const { return fNumWires; }
size_t numTriangles(size_t groupId) const
{ return fTriangleVertIndices[groupId] ? fTriangleVertIndices[groupId]->numIndices()/3 : 0; }
size_t numTriangles() const;
size_t numVerts() const { return fNumVerts; }
const std::shared_ptr<IndexBuffer>& wireVertIndices() const
{ return fWireVertIndices; }
const std::shared_ptr<IndexBuffer>& triangleVertIndices(size_t groupId) const
{ return fTriangleVertIndices[groupId]; }
const std::vector<std::shared_ptr<IndexBuffer> >& triangleVertexIndexGroups() const
{ return fTriangleVertIndices; }
size_t numIndexGroups() const
{ return fTriangleVertIndices.size(); }
const std::shared_ptr<VertexBuffer>& positions() const
{ return fPositions; }
const MBoundingBox& boundingBox() const
{ return fBoundingBox; }
const MColor& diffuseColor() const
{ return fDiffuseColor; }
const std::shared_ptr<VertexBuffer>& normals() const
{ return fNormals; }
const std::shared_ptr<VertexBuffer>& uvs() const
{ return fUVs; }
bool isBoundingBoxPlaceHolder() const
{ return fBoundingBoxPlaceHolder; }
void setBoundingBoxPlaceHolder()
{ fBoundingBoxPlaceHolder = true; }
private:
/*----- member functions -----*/
// The constructor is declare private to force user to go through
// the create() factory member function.
struct MakeSharedEnabler;
ShapeSample(
double timeInSeconds,
size_t numWires,
size_t numVerts,
const std::shared_ptr<IndexBuffer>& wireVertIndices,
const std::shared_ptr<IndexBuffer>& triangleVertIndices,
const std::shared_ptr<VertexBuffer>& positions,
const MBoundingBox& boundingBox,
const MColor& diffuseColor,
bool visibility
);
ShapeSample(
double timeInSeconds,
size_t numWires,
size_t numVerts,
const std::shared_ptr<IndexBuffer>& wireVertIndices,
const std::vector<std::shared_ptr<IndexBuffer> >& triangleVertIndices,
const std::shared_ptr<VertexBuffer>& positions,
const MBoundingBox& boundingBox,
const MColor& diffuseColor,
bool visibility
);
/*----- data members -----*/
const double fTimeInSeconds;
const size_t fNumWires;
const size_t fNumVerts;
// Mandatory attributes
const std::shared_ptr<IndexBuffer> fWireVertIndices;
const std::vector<std::shared_ptr<IndexBuffer> > fTriangleVertIndices;
const std::shared_ptr<VertexBuffer> fPositions;
const MBoundingBox fBoundingBox;
const MColor fDiffuseColor;
const bool fVisibility;
// Optional attributes
std::shared_ptr<VertexBuffer> fNormals;
std::shared_ptr<VertexBuffer> fUVs;
// Flag that this sample is a bounding box place holder for the real geometry sample
bool fBoundingBoxPlaceHolder;
};
/*==============================================================================
* CLASS XformSample
*============================================================================*/
// A sample of the transform matrix for a given time.
class XformSample
{
public:
/*----- static member functions -----*/
static std::shared_ptr<XformSample> create(
double timeInSeconds,
const MMatrix& xform,
const MBoundingBox& boundingBox,
bool visibility);
/*----- member functions -----*/
~XformSample() {}
double timeInSeconds() const { return fTimeInSeconds; }
const MMatrix& xform() const { return fXform; }
bool isReflection() const { return fIsReflection; }
const MBoundingBox& boundingBox() const { return fBoundingBox; }
bool visibility() const { return fVisibility; }
private:
/*----- member functions -----*/
// The constructor is declare private to force user to go through
// the create() factory member function.
struct MakeSharedEnabler;
XformSample(double timeInSeconds,
const MMatrix& xform,
const MBoundingBox& boundingBox,
bool visibility)
: fTimeInSeconds(timeInSeconds),
fXform(xform),
fIsReflection(xform.det3x3() < 0.0f),
fBoundingBox(boundingBox),
fVisibility(visibility)
{}
/*----- data members -----*/
const double fTimeInSeconds;
const MMatrix fXform;
const bool fIsReflection;
const MBoundingBox fBoundingBox;
const bool fVisibility;
};
} // namespace GPUCache
#endif