Reorganize EvalLimitContext and EvalLimitController

Moved transient states (current vertex buffer etc) to controller.
ComputeContext becomes constant so that it's well suited for coarse-grain
parallelism on cpu.

Client-facing API has changed slightly - limitEval example has been adjusted
This commit is contained in:
Manuel Kraemer 2014-05-10 17:55:50 -07:00
parent ec89f76038
commit 827efd14e3
6 changed files with 219 additions and 322 deletions

View File

@ -411,30 +411,30 @@ updateGeom() {
g_nsamplesFound=0;
// Bind/Unbind of the vertex buffers to the context needs to happen
// outside of the parallel loop
g_evalCtx->GetVertexData().Bind( g_idesc, g_vertexData, g_odesc, g_Q, g_dQu, g_dQv );
// The varying data ends-up interleaved in the same g_Q output buffer because
// g_Q has a stride of 6 and g_vdesc sets the offset to 3, while g_odesc sets
// the offset to 0
switch (g_drawMode) {
case kVARYING : g_evalCtx->GetVaryingData().Bind( g_idesc, g_varyingData, g_vdesc, g_Q ); break;
case kVARYING : g_evalCtrl.BindVaryingBuffers( g_idesc, g_varyingData, g_vdesc, g_Q ); break;
case kFACEVARYING : g_evalCtx->GetFaceVaryingData().Bind( g_fvidesc, g_fvodesc, g_Q );
case kFACEVARYING : g_evalCtrl.BindFacevaryingBuffers( g_fvidesc, g_fvodesc, g_Q ); break;
case kUV :
default : g_evalCtx->GetVaryingData().Unbind(); break;
default : g_evalCtrl.Unbind(); break;
}
// Bind/Unbind of the vertex buffers to the context needs to happen
// outside of the parallel loop
g_evalCtrl.BindVertexBuffers( g_idesc, g_vertexData, g_odesc, g_Q, g_dQu, g_dQv );
#define USE_OPENMP
#if defined(OPENSUBDIV_HAS_OPENMP) and defined(USE_OPENMP)
#pragma omp parallel for
#endif
for (int i=0; i<(int)g_coords.size(); ++i) {
int n = g_evalCtrl.EvalLimitSample<OsdCpuVertexBuffer,OsdCpuGLVertexBuffer>( g_coords[i], g_evalCtx, i );
int n = g_evalCtrl.EvalLimitSample( g_coords[i], g_evalCtx, i );
if (n) {
// point colors
@ -461,16 +461,8 @@ updateGeom() {
}
}
g_evalCtx->GetVertexData().Unbind();
g_evalCtrl.Unbind();
switch (g_drawMode) {
case kVARYING : g_evalCtx->GetVaryingData().Unbind(); break;
case kFACEVARYING : g_evalCtx->GetFaceVaryingData().Unbind(); break;
default : break;
}
g_Q->BindVBO();
s.Stop();

View File

@ -98,36 +98,5 @@ OsdCpuEvalLimitContext::~OsdCpuEvalLimitContext() {
delete _patchMap;
}
void
OsdCpuEvalLimitContext::VertexData::Unbind() {
inDesc.Reset();
in.Unbind();
outDesc.Reset();
out.Unbind();
outDu.Unbind();
outDv.Unbind();
}
void
OsdCpuEvalLimitContext::VaryingData::Unbind() {
inDesc.Reset();
in.Unbind();
outDesc.Reset();
out.Unbind();
}
void
OsdCpuEvalLimitContext::FaceVaryingData::Unbind() {
inDesc.Reset();
outDesc.Reset();
out.Unbind();
}
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@ -56,219 +56,6 @@ public:
virtual ~OsdCpuEvalLimitContext();
/// A container able to bind vertex buffer data as input or output streams.
class DataStream {
public:
/// Constructor
DataStream() : _data(0) { }
/// Binds the stream to the context (and moves the data to the appropriate
/// compute device)
///
/// @param data a valid OsdVertexBuffer
///
template <class BUFFER> void Bind( BUFFER * data ) {
_data = data ? data->BindCpuBuffer() : 0;
}
/// True if the stream has been bound
bool IsBound() const {
return (_data!=NULL);
}
/// Unbinds the stream
void Unbind() {
_data=0;
}
protected:
float * _data;
};
/// \brief Input (const) data stream
class InputDataStream : public DataStream {
public:
/// Const accessor
float const * GetData() const {
return _data;
}
};
/// \brief Output (const) data stream
class OutputDataStream : public DataStream {
public:
/// Non-cont accessor
float * GetData() {
return _data;
}
};
/// Vertex-interpolated streams
struct VertexData {
/// input vertex-interpolated data descriptor
OsdVertexBufferDescriptor inDesc;
/// input vertex-interpolated data stream
InputDataStream in;
/// output vertex-interpolated data descriptor
OsdVertexBufferDescriptor outDesc;
/// output vertex-interpolated data stream and parametric derivative streams
OutputDataStream out,
outDu,
outDv;
/// Binds the vertex-interpolated data streams
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param inQ input vertex data
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output vertex data
///
/// @param outdQu output derivative along "u" of the vertex data (optional)
///
/// @param outdQv output derivative along "v" of the vertex data (optional)
///
template<class VERTEX_BUFFER, class OUTPUT_BUFFER>
void Bind( OsdVertexBufferDescriptor const & iDesc, VERTEX_BUFFER *inQ,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ,
OUTPUT_BUFFER *outdQu=0,
OUTPUT_BUFFER *outdQv=0) {
inDesc = iDesc;
in.Bind( inQ );
outDesc = oDesc;
out.Bind( outQ );
outDu.Bind( outdQu );
outDv.Bind( outdQv );
}
/// True if both the mandatory input and output streams have been bound
bool IsBound() const {
return in.IsBound() and out.IsBound();
}
/// Unbind the vertex data streams
void Unbind();
};
/// Returns an Eval data descriptor of the vertex-interpolated data currently
/// bound to this EvalLimitContext.
VertexData & GetVertexData() {
return _vertexData;
}
/// Varying-interpolated streams
struct VaryingData {
/// input varying-interpolated data descriptor
OsdVertexBufferDescriptor inDesc;
/// input varying-interpolated data stream
InputDataStream in;
/// output varying-interpolated data descriptor
OsdVertexBufferDescriptor outDesc;
/// output varying-interpolated data stream
OutputDataStream out;
/// Binds the varying-interpolated data streams
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param inQ input varying data
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output varying data
///
template<class VARYING_BUFFER, class OUTPUT_BUFFER>
void Bind( OsdVertexBufferDescriptor const & iDesc, VARYING_BUFFER *inQ,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ ) {
inDesc = iDesc;
in.Bind( inQ );
outDesc = oDesc;
out.Bind( outQ );
}
/// True if both the mandatory input and output streams have been bound
bool IsBound() const {
return in.IsBound() and out.IsBound();
}
/// Unbind the vertex data streams
void Unbind();
};
/// Returns an Eval data descriptor of the varying-interpolated data currently
/// bound to this EvalLimitContext.
VaryingData & GetVaryingData() {
return _varyingData;
}
/// Face-Varying-interpolated streams
struct FaceVaryingData {
/// input face-varying-interpolated data descriptor
OsdVertexBufferDescriptor inDesc;
/// output face-varying-interpolated data descriptor
OsdVertexBufferDescriptor outDesc;
/// output face-varying-interpolated data stream and parametric derivative streams
OutputDataStream out;
/// Binds the face-varying-interpolated data streams
///
/// Note : currently we only support bilinear boundary interpolation rules
/// for face-varying data. Although Hbr supports 3 addition smooth rule sets,
/// the feature-adaptive patch interpolation code currently does not support
/// them, and neither does this EvalContext
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output face-varying data
///
template<class OUTPUT_BUFFER>
void Bind( OsdVertexBufferDescriptor const & iDesc,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ ) {
inDesc = iDesc;
outDesc = oDesc;
out.Bind( outQ );
}
/// True if the output stream has been bound
bool IsBound() const {
return out.IsBound();
}
/// Unbind the vertex data streams
void Unbind();
};
/// Returns an Eval data descriptor of the face-varying-interpolated data
/// currently bound to this EvalLimitContext.
FaceVaryingData & GetFaceVaryingData() {
return _faceVaryingData;
}
/// Returns the vector of patch arrays
const FarPatchTables::PatchArrayVector & GetPatchArrayVector() const {
return _patchArrays;
@ -331,10 +118,6 @@ private:
FarPatchMap * _patchMap; // map of the sub-patches given a face index
VertexData _vertexData; // vertex-interpolated data descriptor
VaryingData _varyingData; // varying-interpolated data descriptor
FaceVaryingData _faceVaryingData; // face-varying-interpolated data descriptor
int _maxValence,
_fvarwidth;
};

View File

@ -72,9 +72,9 @@ OsdCpuEvalLimitController::EvalLimitSample( OpenSubdiv::OsdEvalCoords const & co
unsigned int const * cvs = &context->GetControlVertices()[ parray.GetVertIndex() + handle->vertexOffset ];
OsdCpuEvalLimitContext::VertexData & vertexData = context->GetVertexData();
VertexData const & vertexData = _currentBindState.vertexData;
if (vertexData.in.IsBound()) {
if (vertexData.in) {
float * out = outQ ? outQ + outDesc.offset : 0,
* outDu = outDQU ? outDQU + outDesc.offset : 0,
@ -84,21 +84,21 @@ OsdCpuEvalLimitController::EvalLimitSample( OpenSubdiv::OsdEvalCoords const & co
case FarPatchTables::REGULAR : evalBSpline( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
outDesc,
out, outDu, outDv );
break;
case FarPatchTables::BOUNDARY : evalBoundary( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
outDesc,
out, outDu, outDv );
break;
case FarPatchTables::CORNER : evalCorner( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
outDesc,
out, outDu, outDv );
break;
@ -109,7 +109,7 @@ OsdCpuEvalLimitController::EvalLimitSample( OpenSubdiv::OsdEvalCoords const & co
&context->GetQuadOffsetTable()[ parray.GetQuadOffsetIndex() + handle->vertexOffset ],
context->GetMaxValence(),
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
outDesc,
out, outDu, outDv );
break;
@ -120,7 +120,7 @@ OsdCpuEvalLimitController::EvalLimitSample( OpenSubdiv::OsdEvalCoords const & co
&context->GetQuadOffsetTable()[ parray.GetQuadOffsetIndex() + handle->vertexOffset ],
context->GetMaxValence(),
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
outDesc,
out, outDu, outDv );
break;
@ -154,39 +154,38 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
unsigned int const * cvs = &context->GetControlVertices()[ parray.GetVertIndex() + handle->vertexOffset ];
OsdCpuEvalLimitContext::VertexData & vertexData = context->GetVertexData();
VertexData const & vertexData = _currentBindState.vertexData;
if (vertexData.IsBound()) {
if (vertexData.in) {
int offset = vertexData.outDesc.stride * index;
if (vertexData.out) {
if (vertexData.IsBound()) {
float * out = vertexData.out.GetData()+offset,
* outDu = vertexData.outDu.IsBound() ? vertexData.outDu.GetData()+offset : 0,
* outDv = vertexData.outDv.IsBound() ? vertexData.outDv.GetData()+offset : 0;
float * out = vertexData.out+offset,
* outDu = vertexData.outDu ? vertexData.outDu+offset : 0,
* outDv = vertexData.outDv ? vertexData.outDv+offset : 0;
// Based on patch type - go execute interpolation
switch( parray.GetDescriptor().GetType() ) {
case FarPatchTables::REGULAR : evalBSpline( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
vertexData.outDesc,
out, outDu, outDv );
break;
case FarPatchTables::BOUNDARY : evalBoundary( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
vertexData.outDesc,
out, outDu, outDv );
break;
case FarPatchTables::CORNER : evalCorner( v, u, cvs,
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
vertexData.outDesc,
out, outDu, outDv );
break;
@ -197,7 +196,7 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
&context->GetQuadOffsetTable()[ parray.GetQuadOffsetIndex() + handle->vertexOffset ],
context->GetMaxValence(),
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
vertexData.outDesc,
out, outDu, outDv );
break;
@ -208,7 +207,7 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
&context->GetQuadOffsetTable()[ parray.GetQuadOffsetIndex() + handle->vertexOffset ],
context->GetMaxValence(),
vertexData.inDesc,
vertexData.in.GetData(),
vertexData.in,
vertexData.outDesc,
out, outDu, outDv );
break;
@ -219,9 +218,9 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
}
}
OsdCpuEvalLimitContext::VaryingData & varyingData = context->GetVaryingData();
VaryingData const & varyingData = _currentBindState.varyingData;
if (varyingData.IsBound()) {
if (varyingData.in and varyingData.out) {
static int indices[5][4] = { {5, 6,10, 9}, // regular
{1, 2, 6, 5}, // boundary
@ -240,9 +239,9 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
evalBilinear( v, u, zeroRing,
varyingData.inDesc,
varyingData.in.GetData(),
varyingData.in,
varyingData.outDesc,
varyingData.out.GetData()+offset);
varyingData.out+offset);
}
@ -250,22 +249,24 @@ OsdCpuEvalLimitController::_EvalLimitSample( OpenSubdiv::OsdEvalCoords const & c
// for face-varying data. Although Hbr supports 3 additional smooth rule
// sets, the feature-adaptive patch interpolation code currently does not
// support them, and neither does this EvalContext.
OsdCpuEvalLimitContext::FaceVaryingData & faceVaryingData = context->GetFaceVaryingData();
if (faceVaryingData.IsBound()) {
FacevaryingData const & facevaryingData = _currentBindState.facevaryingData;
if (facevaryingData.out) {
std::vector<float> const & fvarData = context->GetFVarData();
if (not fvarData.empty()) {
int offset = faceVaryingData.outDesc.stride * index;
int offset = facevaryingData.outDesc.stride * index;
static unsigned int zeroRing[4] = {0,1,2,3};
evalBilinear( v, u, zeroRing,
faceVaryingData.inDesc,
facevaryingData.inDesc,
&fvarData[ handle->patchIdx * 4 * context->GetFVarWidth() ],
faceVaryingData.outDesc,
faceVaryingData.out.GetData()+offset);
facevaryingData.outDesc,
facevaryingData.out+offset);
}
}

View File

@ -39,6 +39,23 @@ namespace OPENSUBDIV_VERSION {
/// A CPU-driven controller that can be called to evaluate samples on the limit
/// surface for a given EvalContext.
///
/// Warning : this eval controller is re-entrant but it breaks the Osd API pattern
/// by requiring client code to bind and unbind the data buffers to the
/// Controller before calling evaluation methods.
///
/// Ex :
/// \code
/// evalCtroller->BindVertexBuffers( ... );
/// evalCtroller->BindVaryingBuffers( ... );
/// evalCtroller->BindFacevaryingBuffers( ... );
///
/// parallel_for( int index=0; i<nsamples; ++index ) {
/// evalCtroller->EvalLimitSample( coord, evalCtxt, index );
/// }
///
/// evalCtroller->Unbind();
/// \endcode
///
class OsdCpuEvalLimitController {
public:
@ -54,13 +71,83 @@ public:
float u,v; // local u,v
};
/// \brief Binds control vertex data buffer
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param inQ input vertex data
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output vertex data
///
/// @param outdQu output derivative along "u" of the vertex data (optional)
///
/// @param outdQv output derivative along "v" of the vertex data (optional)
///
template<class INPUT_BUFFER, class OUTPUT_BUFFER>
void BindVertexBuffers( OsdVertexBufferDescriptor const & iDesc, INPUT_BUFFER *inQ,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ,
OUTPUT_BUFFER *outdQu=0,
OUTPUT_BUFFER *outdQv=0 ) {
_currentBindState.vertexData.inDesc = iDesc;
_currentBindState.vertexData.in = inQ ? inQ->BindCpuBuffer() : 0;
_currentBindState.vertexData.outDesc = oDesc;
_currentBindState.vertexData.out = outQ ? outQ->BindCpuBuffer() : 0;
_currentBindState.vertexData.outDu = outdQu ? outdQu->BindCpuBuffer() : 0;
_currentBindState.vertexData.outDv = outdQv ? outdQv->BindCpuBuffer() : 0;
}
/// \brief Binds the varying-interpolated data streams
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param inQ input varying data
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output varying data
///
template<class INPUT_BUFFER, class OUTPUT_BUFFER>
void BindVaryingBuffers( OsdVertexBufferDescriptor const & iDesc, INPUT_BUFFER *inQ,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ ) {
_currentBindState.varyingData.inDesc = iDesc;
_currentBindState.varyingData.in = inQ ? inQ->BindCpuBuffer() : 0;
_currentBindState.varyingData.outDesc = oDesc;
_currentBindState.varyingData.out = outQ ? outQ->BindCpuBuffer() : 0;
}
/// \brief Binds the face-varying-interpolated data streams
///
/// Note : currently we only support bilinear boundary interpolation rules
/// for face-varying data. Although Hbr supports 3 addition smooth rule sets,
/// the feature-adaptive patch interpolation code currently does not support
/// them, and neither does this EvalContext
///
/// @param iDesc data descriptor shared by all input data buffers
///
/// @param oDesc data descriptor shared by all output data buffers
///
/// @param outQ output face-varying data
///
template<class OUTPUT_BUFFER>
void BindFacevaryingBuffers( OsdVertexBufferDescriptor const & iDesc,
OsdVertexBufferDescriptor const & oDesc, OUTPUT_BUFFER *outQ ) {
_currentBindState.facevaryingData.inDesc = iDesc;
_currentBindState.facevaryingData.outDesc = oDesc;
_currentBindState.facevaryingData.out = outQ ? outQ->BindCpuBuffer() : 0;
}
/// \brief Vertex interpolation of a single sample at the limit
///
/// Evaluates "vertex" interpolation of a single sample on the surface limit.
///
/// This function is re-entrant and does not require the context to bind
/// output vertex buffers.
/// This function is re-entrant but does not require binding the
/// output vertex buffers. Pointers to memory where the data is
/// output are explicitly passed to the function.
///
/// @param coords location on the limit surface to be evaluated
///
@ -68,6 +155,12 @@ public:
///
/// @param outDesc data descriptor (offset, length, stride)
///
/// @param outQ output vertex data
///
/// @param outdQu output derivative along "u" of the vertex data (optional)
///
/// @param outdQv output derivative along "v" of the vertex data (optional)
///
/// @return 1 if the sample was found
///
int EvalLimitSample( OpenSubdiv::OsdEvalCoords const & coord,
@ -77,27 +170,10 @@ public:
float * outDQU,
float * outDQV ) const;
/// \brief Vertex interpolation of samples at the limit
///
/// Evaluates "vertex" interpolation of a sample on the surface limit.
///
/// Warning : this function is re-entrant but it breaks the Osd API pattern
/// by requiring the client code to bind and unbind the vertex buffers to
/// the EvalLimitContext.
///
/// Ex :
/// \code
/// evalCtxt->BindVertexBuffers( ... );
///
/// parallel_for( int index=0; i<nsamples; ++index ) {
/// evalCtrlr->EvalLimitSample( coord, evalCtxt, index );
/// }
///
/// evalCtxt->UnbindVertexBuffers();
/// \endcode
///
/// @param coords location on the limit surface to be evaluated
///
/// @param context the EvalLimitContext that the controller will evaluate
@ -108,7 +184,6 @@ public:
/// @return the number of samples found (0 if the location was tagged as a hole
/// or the coordinate was invalid)
///
template<class VERTEX_BUFFER, class OUTPUT_BUFFER>
int EvalLimitSample( OpenSubdiv::OsdEvalCoords const & coords,
OsdCpuEvalLimitContext * context,
unsigned int index ) const {
@ -120,12 +195,92 @@ public:
return n;
}
void Unbind() {
_currentBindState.Reset();
}
protected:
// Vertex interpolated streams
struct VertexData {
VertexData() : in(0), out(0), outDu(0), outDv(0) { }
void Reset() {
in = out = outDu = outDv = NULL;
inDesc.Reset();
outDesc.Reset();
}
OsdVertexBufferDescriptor inDesc,
outDesc;
float * in,
* out,
* outDu,
* outDv;
};
// Varying interpolated streams
struct VaryingData {
VaryingData() : in(0), out(0) { }
void Reset() {
in = out = NULL;
inDesc.Reset();
outDesc.Reset();
}
OsdVertexBufferDescriptor inDesc,
outDesc;
float * in,
* out;
};
// Facevarying interpolated streams
struct FacevaryingData {
FacevaryingData() : out(0) { }
void Reset() {
out = NULL;
inDesc.Reset();
outDesc.Reset();
}
OsdVertexBufferDescriptor inDesc,
outDesc;
float * out;
};
private:
int _EvalLimitSample( OpenSubdiv::OsdEvalCoords const & coords,
OsdCpuEvalLimitContext * context,
unsigned int index ) const;
// Bind state is a transitional state during refinement.
// It doesn't take an ownership of vertex buffers.
struct BindState {
BindState() { }
void Reset() {
vertexData.Reset();
varyingData.Reset();
facevaryingData.Reset();
}
VertexData vertexData; // vertex interpolated data descriptor
VaryingData varyingData; // varying interpolated data descriptor
FacevaryingData facevaryingData; // face-varying interpolated data descriptor
};
BindState _currentBindState;
};
} // end namespace OPENSUBDIV_VERSION

View File

@ -156,14 +156,6 @@ OsdUtilAdaptiveEvaluator::Initialize(
_evalLimitContext = OsdCpuEvalLimitContext::Create(
fmesh->GetPatchTables(), /*requierFVarData*/ false);
// Setup evaluation context. Values are offset, length, stride */
OsdVertexBufferDescriptor in_desc(0, 3, 3), out_desc(0, 0, 0);
OsdCpuEvalLimitContext::VertexData & vertexData =
_evalLimitContext->GetVertexData();
vertexData.Bind<OsdCpuVertexBuffer,OsdCpuVertexBuffer>(in_desc, _vertexBuffer, out_desc, NULL);
return true;
}
@ -216,6 +208,11 @@ OsdUtilAdaptiveEvaluator::EvaluateLimit(
OsdCpuEvalLimitController cpuEvalLimitController;
static OsdVertexBufferDescriptor desc(0,3,3);
// Setup evaluation controller. Values are offset, length, stride */
OsdVertexBufferDescriptor in_desc(0, 3, 3), out_desc(0, 0, 0);
cpuEvalLimitController.BindVertexBuffers<OsdCpuVertexBuffer,OsdCpuVertexBuffer>(in_desc, _vertexBuffer, out_desc, NULL);
cpuEvalLimitController.EvalLimitSample(coords, _evalLimitContext, desc, P, dPdu, dPdv);
}