C++ API Reference
gpuCache/CacheWriter.cpp
//-
//**************************************************************************/
// 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.
//**************************************************************************/
//+
#include "CacheWriter.h"
#include "gpuCacheUtil.h"
#include "gpuCacheStrings.h"
#include <maya/MFnDagNode.h>
#include <maya/MAnimControl.h>
#include <maya/MFnMeshData.h>
#include <maya/MIntArray.h>
#include <maya/MFloatArray.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MFnMesh.h>
#include <maya/MUintArray.h>
#include <maya/MHWGeometry.h>
#include <maya/MGeometryExtractor.h>
#include <maya/MFnSingleIndexedComponent.h>
#include <maya/MDagPathArray.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MFnTransform.h>
#include <string.h> // for memcmp
#include <string>
#include <cassert>
using namespace MHWRender;
using namespace GPUCache;
//==============================================================================
// CLASS CacheWriter
//==============================================================================
std::map<std::string,void*> CacheWriter::fsRegistry;
std::shared_ptr<CacheWriter> CacheWriter::create(const MString& impl,
const MFileObject& file, char compressLevel, const MString& dataFormat)
{
std::string key = impl.asChar();
std::map<std::string,void*>::iterator iter = fsRegistry.find(key);
if (iter != fsRegistry.end()) {
return ((CreateFunction*)(*iter).second)(file, compressLevel, dataFormat);
}
assert("not implemented");
return std::shared_ptr<CacheWriter>();
}
void CacheWriter::registerWriter(const MString& impl, CreateFunction func)
{
std::string key = impl.asChar();
fsRegistry[key] = (void*)func;
}
//==============================================================================
// CLASS CacheXformSampler
//==============================================================================
struct CacheXformSampler::MakeSharedEnabler: public CacheXformSampler {
MakeSharedEnabler(const MObject& xformObject): CacheXformSampler(xformObject) {}
};
std::shared_ptr<CacheXformSampler>
CacheXformSampler::create(const MObject& xformObject)
{
return std::make_shared<MakeSharedEnabler>(xformObject);
}
CacheXformSampler::CacheXformSampler(const MObject& xformObject)
: fXform(xformObject),
fIsFirstSample(true),
// The first sample is always considered animated as we have to
// capture its values.
fXformAnimated(true),
fVisibilitySample(false),
fVisibilityAnimated(true)
{}
CacheXformSampler::~CacheXformSampler()
{}
void CacheXformSampler::addSample()
{
MMatrix prevXformSample = fXformSample;
bool prevVisibilitySample = fVisibilitySample;
fXformSample = fXform.transformationMatrix();
fVisibilitySample = ShapeVisibilityChecker(fXform.object()).isVisible();
if (fIsFirstSample) {
// The first sample is always considered animated as we have
// to capture its values.
fIsFirstSample = false;
}
else {
fXformAnimated = (prevXformSample != fXformSample);
fVisibilityAnimated = (prevVisibilitySample != fVisibilitySample);
}
}
std::shared_ptr<const XformSample>
CacheXformSampler::getSample(double timeInSeconds)
{
std::shared_ptr<XformSample> sample =
XformSample::create(
timeInSeconds,fXformSample, MBoundingBox(), fVisibilitySample);
return sample;
}
//==============================================================================
// CLASS CacheMeshSampler::AttributeSet
//==============================================================================
CacheMeshSampler::AttributeSet::AttributeSet(
MObject meshObject,
const bool visibility,
const bool needUVs
)
{
typedef IndexBuffer::index_t index_t;
MayaMeshExtractor<index_t> extractor(meshObject);
extractor.setWantUVs(needUVs);
extractor.compute();
std::shared_ptr<Array<index_t> > wireIndices = extractor.wireIndices();
std::shared_ptr<Array<index_t> > triangleIndices = extractor.triangleIndices();
std::shared_ptr<Array<float> > positions = extractor.positions();
std::shared_ptr<Array<float> > normals = extractor.normals();
std::shared_ptr<Array<float> > uvs;
if (needUVs) {
uvs = extractor.uvs();
}
fNumWires = wireIndices->size() / 2;
fNumTriangles = triangleIndices->size() / 3;
fNumVerts = positions->size() / 3;
fWireVertIndices = IndexBuffer::create(wireIndices);
fTriangleVertIndices.push_back(IndexBuffer::create(triangleIndices));
fPositions = VertexBuffer::createPositions(positions);
fNormals = VertexBuffer::createNormals(normals);
if (needUVs) {
fUVs = VertexBuffer::createUVs(uvs);
}
{
float minX = +std::numeric_limits<float>::max();
float minY = +std::numeric_limits<float>::max();
float minZ = +std::numeric_limits<float>::max();
float maxX = -std::numeric_limits<float>::max();
float maxY = -std::numeric_limits<float>::max();
float maxZ = -std::numeric_limits<float>::max();
VertexBuffer::ReadInterfacePtr readable = fPositions->readableInterface();
const float* srcPositions = readable->get();
for (size_t i=0; i<fNumVerts; ++i) {
const float x = srcPositions[3*i + 0];
const float y = srcPositions[3*i + 1];
const float z = srcPositions[3*i + 2];
minX = std::min(x, minX);
minY = std::min(y, minY);
minZ = std::min(z, minZ);
maxX = std::max(x, maxX);
maxY = std::max(y, maxY);
maxZ = std::max(z, maxZ);
}
fBoundingBox = MBoundingBox(MPoint(minX, minY, minZ),
MPoint(maxX, maxY, maxZ));
}
fVisibility = visibility;
}
CacheMeshSampler::AttributeSet::AttributeSet(
MFnMesh& mesh,
const bool needUVs,
const bool useBaseTessellation
)
:fNumWires(0),
fNumTriangles(0),
fNumVerts(0),
fVisibility(false)
{
// Refresh the internal shape, otherwise topo changes make mesh.numPolygons() crash.
// Note that buildShaderAssignmentGroups() also call mesh.numPolygons().
mesh.syncObject();
MDagPath dagPath;
mesh.getPath(dagPath);
// build a geometry request and add requirements to it.
// Build descriptors to request the positions, normals and UVs
MVertexBufferDescriptor posDesc("", MGeometry::kPosition, MGeometry::kFloat, 3);
MVertexBufferDescriptor normalDesc("", MGeometry::kNormal, MGeometry::kFloat, 3);
MVertexBufferDescriptor uvDesc(mesh.currentUVSetName(), MGeometry::kTexture, MGeometry::kFloat, 2);
// add the descriptors to the geometry requirements
geomRequirements.addVertexRequirement(posDesc);
geomRequirements.addVertexRequirement(normalDesc);
if (needUVs)
geomRequirements.addVertexRequirement(uvDesc);
MString noName; // we do not need custom named index buffers here.
// create a component to include all elements.
// create edge component
MObject edgeCompObj = edgeComp.create(MFn::kMeshEdgeComponent);
edgeComp.setCompleteData(mesh.numEdges());
// Add the edge line index buffer to the requirements
MIndexBufferDescriptor edgeDesc(MIndexBufferDescriptor::kEdgeLine, noName, MGeometry::kLines, 2, edgeCompObj);
geomRequirements.addIndexingRequirement(edgeDesc);
// add a triangle buffer to the requirements
MIndexBufferDescriptor triangleDesc(MIndexBufferDescriptor::kTriangle, noName, MGeometry::kTriangles, 3, compObj);
geomRequirements.addIndexingRequirement(triangleDesc);
typedef IndexBuffer::index_t index_t;
// We ignore the Smooth Preview option on the mesh shape node when
// using base tessellation.
int extractorOptions = MHWRender::kPolyGeom_Normal;
if (useBaseTessellation) {
extractorOptions |= MHWRender::kPolyGeom_BaseMesh;
}
// create an extractor to get the geometry
MStatus status;
MHWRender::MGeometryExtractor extractor(geomRequirements,dagPath, extractorOptions, &status);
if (MS::kFailure==status)
return;
// get the number of vertices from the extractor
unsigned int numVertices = extractor.vertexCount();
// get the number of primitives (triangles, lines, etc.)
unsigned int numWires = extractor.primitiveCount(edgeDesc);
// create the arrays that the generator will fill
GPUCache::shared_array<float> vertices(new float[numVertices*posDesc.stride()]);
GPUCache::shared_array<float> normals(new float[numVertices*normalDesc.stride()]);
GPUCache::shared_array<float> uvs(new float[numVertices*uvDesc.stride()]);
unsigned int minBufferSize = extractor.minimumBufferSize(numWires, edgeDesc.primitive());
GPUCache::shared_array<index_t> wireframeIdx(new index_t[minBufferSize]);
// populate the index buffer for the edges
if (MS::kFailure==extractor.populateIndexBuffer(wireframeIdx.get(), numWires, edgeDesc))
return;
// populate the vertex buffers you are interested in. (pos, normal, and uv)
if (MS::kFailure==extractor.populateVertexBuffer(vertices.get(), numVertices, posDesc))
return;
if (MS::kFailure==extractor.populateVertexBuffer(normals.get(), numVertices, normalDesc))
return;
if (needUVs && MS::kFailure==extractor.populateVertexBuffer(uvs.get(), numVertices, uvDesc))
return;
// populate the index buffers for all the triangle components
{
std::vector<std::shared_ptr<Array<index_t> > > trgIdxGrps;
// get the index buffer count from the extractor
unsigned int numTriangles = extractor.primitiveCount(triangleDesc);
minBufferSize = extractor.minimumBufferSize(numTriangles, triangleDesc.primitive());
GPUCache::shared_array<index_t> triangleIdx(new index_t[minBufferSize]);
if (numTriangles != 0)
{
if (MS::kFailure==extractor.populateIndexBuffer(triangleIdx.get(), numTriangles, triangleDesc))
return;
fNumTriangles += (size_t)numTriangles;
}
trgIdxGrps.push_back(SharedArray<index_t>::create(
triangleIdx, 3 * numTriangles));
for(size_t i=0, offset=0; i<trgIdxGrps.size(); ++i) {
fTriangleVertIndices.push_back(IndexBuffer::create(
trgIdxGrps[0], offset, offset + trgIdxGrps[i]->size()));
offset += trgIdxGrps[i]->size();
}
}
fNumWires = (size_t)numWires;
fNumVerts = (size_t)numVertices;
fWireVertIndices = IndexBuffer::create(
SharedArray<index_t>::create( wireframeIdx, 2 * fNumWires));
fPositions = VertexBuffer::createPositions(
SharedArray<float>::create( vertices, 3 * fNumVerts));
fNormals = VertexBuffer::createNormals(
SharedArray<float>::create( normals, 3 * fNumVerts));
if (needUVs) {
fUVs = VertexBuffer::createUVs(
SharedArray<float>::create(uvs, 2 * fNumVerts));
}
fBoundingBox = mesh.boundingBox();
// Check visibility
fVisibility = ShapeVisibilityChecker(mesh.object()).isVisible();
}
bool CacheMeshSampler::AttributeSet::updateAnimatedChannels(
bool& animated, const AttributeSet& newer
)
{
const bool numWiresAnimated = fNumWires != newer.fNumWires;
const bool numTrianglesAnimated = fNumTriangles != newer.fNumTriangles;
const bool numVertsAnimated = fNumVerts != newer.fNumVerts;
const bool wiresAnimated = fWireVertIndices != newer.fWireVertIndices;
// We reuse the triangulation from the previous sample
// if the topology of the wire mesh is not changing. This is
// done to avoid performance issue due to position dependent
// triangulation of animated meshes.
const bool trianglesAnimated =
(!numWiresAnimated && !numTrianglesAnimated && !numVertsAnimated &&
!wiresAnimated) ?
false :
(fTriangleVertIndices != newer.fTriangleVertIndices);
const bool positionsAnimated = fPositions != newer.fPositions;
const bool normalsAnimated = fNormals != newer.fNormals;
const bool uvsAnimated = fUVs != newer.fUVs;
const bool boundingBoxAnimated =
(!fBoundingBox.min().isEquivalent(newer.fBoundingBox.min()) ||
!fBoundingBox.max().isEquivalent(newer.fBoundingBox.max()));
const bool visibilityAnimated = fVisibility != newer.fVisibility;
fNumWires = newer.fNumWires;
fNumTriangles = newer.fNumTriangles;
fNumVerts = newer.fNumVerts;
fWireVertIndices = wiresAnimated ? newer.fWireVertIndices : fWireVertIndices;
fTriangleVertIndices = trianglesAnimated ? newer.fTriangleVertIndices : fTriangleVertIndices;
fPositions = positionsAnimated ? newer.fPositions : fPositions;
fNormals = normalsAnimated ? newer.fNormals : fNormals;
fUVs = uvsAnimated ? newer.fUVs : fUVs;
fBoundingBox = newer.fBoundingBox;
fVisibility = newer.fVisibility;
animated =
numWiresAnimated || numTrianglesAnimated || numVertsAnimated ||
wiresAnimated || trianglesAnimated ||
positionsAnimated || normalsAnimated || uvsAnimated ||
boundingBoxAnimated || visibilityAnimated;
return true;
}
//==============================================================================
// CLASS CacheMeshSampler
//==============================================================================
struct CacheMeshSampler::MakeSharedEnabler: public CacheMeshSampler{
MakeSharedEnabler(bool needUVs):CacheMeshSampler(needUVs){}
};
std::shared_ptr<CacheMeshSampler> CacheMeshSampler::create(
const bool needUVs)
{
return std::make_shared<MakeSharedEnabler>(needUVs);
}
CacheMeshSampler::CacheMeshSampler(const bool needUVs)
: fNeedUVs(needUVs), fUseBaseTessellation(false), fIsAnimated(true)
{}
CacheMeshSampler::~CacheMeshSampler()
{}
bool CacheMeshSampler::addSample(MObject meshObject, bool visibility)
{
return fAttributeSet.updateAnimatedChannels(
fIsAnimated, AttributeSet(meshObject, visibility, fNeedUVs));
}
bool CacheMeshSampler::addSampleFromMesh(MFnMesh& mesh)
{
return fAttributeSet.updateAnimatedChannels(
fIsAnimated, AttributeSet(mesh, fNeedUVs, fUseBaseTessellation));
}
std::shared_ptr<const ShapeSample>
CacheMeshSampler::getSample(double timeInSeconds, const MColor& diffuseColor)
{
if (!fAttributeSet.fVisibility) {
// return an empty sample if the shape is invisible
std::shared_ptr<ShapeSample> sample =
ShapeSample::createEmptySample(timeInSeconds);
return sample;
}
std::shared_ptr<ShapeSample> sample =
ShapeSample::create(
timeInSeconds,
fAttributeSet.fNumWires,
fAttributeSet.fNumVerts,
fAttributeSet.fWireVertIndices,
fAttributeSet.fTriangleVertIndices,
fAttributeSet.fPositions,
fAttributeSet.fBoundingBox,
diffuseColor,
fAttributeSet.fVisibility
);
sample->setNormals(fAttributeSet.fNormals);
sample->setUVs(fAttributeSet.fUVs);
return sample;
}