Added evaluator, first .obj file reading and subdividing.

This commit is contained in:
gelder 2013-11-05 21:50:11 -08:00
parent 7dbf3d4198
commit ecd726742f
7 changed files with 646 additions and 85 deletions

View File

@ -68,11 +68,12 @@
#include <osd/mesh.h>
#include <osd/vertex.h>
#include <osdutil/mesh.h>
#include <osdutil/refiner.h>
#include <osdutil/uniformEvaluator.h>
#include <osdutil/topology.h>
#include "../common/stopwatch.h"
#include "../../regression/common/shape_utils.h"
#include <cfloat>
#include <vector>
@ -86,70 +87,86 @@
using namespace OpenSubdiv;
//------------------------------------------------------------------------------
typedef HbrMesh<OsdVertex> OsdHbrMesh;
typedef HbrVertex<OsdVertex> OsdHbrVertex;
typedef HbrFace<OsdVertex> OsdHbrFace;
typedef HbrHalfedge<OsdVertex> OsdHbrHalfedge;
static shape * readShape( char const * fname ) {
typedef FarMesh<OsdVertex> OsdFarMesh;
typedef FarMeshFactory<OsdVertex> OsdFarMeshFactory;
typedef FarSubdivisionTables<OsdVertex> OsdFarMeshSubdivision;
FILE * handle = fopen( fname, "rt" );
if (not handle) {
printf("Could not open \"%s\" - aborting.\n", fname);
exit(0);
}
fseek( handle, 0, SEEK_END );
size_t size = ftell(handle);
fseek( handle, 0, SEEK_SET );
char * shapeStr = new char[size+1];
//------------------------------------------------------------------------------
static void
createOsdMesh(int level)
if ( fread( shapeStr, size, 1, handle)!=1 ) {
printf("Error reading \"%s\" - aborting.\n", fname);
exit(0);
}
fclose(handle);
shapeStr[size]='\0';
return shape::parseShape( shapeStr, 1 );
}
static bool
shapeToTopology( const shape *input, PxOsdUtilSubdivTopology *output,
std::string *errorMessage)
{
float points[] = { 0.000000f, -1.414214f, 1.000000f,
1.414214f, 0.000000f, 1.000000f,
-1.414214f, 0.000000f, 1.000000f,
0.000000f, 1.414214f, 1.000000f,
-1.414214f, 0.000000f, -1.000000f,
0.000000f, 1.414214f, -1.000000f,
0.000000f, -1.414214f, -1.000000f,
1.414214f, 0.000000f, -1.000000f };
int nverts[] = { 4, 4, 4, 4, 4, 4};
int indices[] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4};
int numVertices = input->getNverts();
output->numVertices = numVertices;
output->maxLevels = 3; //arbitrary initial value
output->nverts = input->nvertsPerFace;
output->indices = input->faceverts;
// Scheme scheme = kCatmark;
// XXX:gelder
// Need to pull over uvs and tags for better test coverage
PxOsdUtilSubdivTopology t;
t.name = "TestSubdiv";
for (int i=0; i< (int)(sizeof(nverts)/sizeof(int)); ++i) {
t.nverts.push_back(nverts[i]);
}
for (int i=0; i< (int)(sizeof(indices)/sizeof(int)); ++i) {
t.indices.push_back(indices[i]);
}
t.numVertices = (int)sizeof(points)/(3*sizeof(float));
t.maxLevels = 8;
return output->IsValid(errorMessage);
}
std::string errorMessage;
PxOsdUtilRefiner refiner;
// Create refiner, passing "false" to adaptive so we'll get
// uniform refinement
if (not refiner.Initialize(t, false, &errorMessage)) {
std::cout << "Refiner creation failed with " << errorMessage << std::endl;
return;
//------------------------------------------------------------------------------
static bool
createOsdMesh(char *inputFile, char *outputFile, std::string *errorMessage)
{
shape *inputShape = readShape(inputFile);
PxOsdUtilSubdivTopology topology;
if (not shapeToTopology(inputShape, &topology, errorMessage))
return false;
PxOsdUtilUniformEvaluator uniformEvaluator;
// Create uniformEvaluator
if (not uniformEvaluator.Initialize(topology, errorMessage)) {
return false;
}
// Push the vertex data
uniformEvaluator.SetCoarsePositions(inputShape->verts, errorMessage);
// Refine with eight threads
if (not uniformEvaluator.Refine(8, errorMessage))
return false;
std::vector<int> refinedQuads;
if (not refiner.GetRefinedQuads(&refinedQuads, &errorMessage)) {
if (not uniformEvaluator.GetRefinedQuads(&refinedQuads, errorMessage)) {
std::cout << "GetRefinedQuads failed with " << errorMessage << std::endl;
}
float *refinedPositions = NULL;
int numFloats = 0;
if (not uniformEvaluator.GetRefinedPositions(&refinedPositions, &numFloats, errorMessage)) {
std::cout << "GetRefinedPositions failed with " << errorMessage << std::endl;
}
std::cout << "Quads = " << refinedQuads.size()/4 << std::endl;
for (int i=0; i<(int)refinedQuads.size(); i+=4) {
std::cout << "(" << refinedQuads[i] <<
@ -159,42 +176,22 @@ createOsdMesh(int level)
")\n";
}
/*
// Push the vertex data:
std::vector<float> pointsVec;
pointsVec.resize(sizeof(points));
for (int i=0; i<(int)sizeof(points); ++i) {
pointsVec[i] = points[i];
std::cout << "Hot damn, it worked.\n";
std::cout << "Positions = " << numFloats/3 << std::endl;
for (int i=0; i<numFloats; i+=3) {
std::cout << "(" << refinedPositions[i] <<
", " << refinedPositions[i+1] <<
"," << refinedPositions[i+2] << ")\n";
}
shape->SetCoarsePositions(pointsVec);
std::vector<float> refinedPositions;
// if (not uniformEvaluator.WriteRefinedObj("foo.obj", errorMessage)) {
// std::cout << errorMessage << std::endl;
// }
if (not (shape->Refine(2) and
shape->GetPositions(&refinedPositions, &errorMessage) and
shape->GetQuads(&refinedQuads, &errorMessage))) {
std::cout << errorMessage << std::endl;
} else {
std::cout << "Hot damn, it worked.\n";
std::cout << "Positions = " << refinedPositions.size()/3 << std::endl;
for (int i=0; i<(int)refinedPositions.size(); i+=3) {
std::cout << "(" << refinedPositions[i] <<
", " << refinedPositions[i+1] <<
"," << refinedPositions[i+2] << ")\n";
}
if (not shape->WriteRefinedObj("foo.obj", &errorMessage)) {
std::cout << errorMessage << std::endl;
}
}
*/
return true;
}
//------------------------------------------------------------------------------
@ -207,11 +204,23 @@ callbackError(OpenSubdiv::OsdErrorType err, const char *message)
//------------------------------------------------------------------------------
int main(int, char**) {
int main(int argc, char** argv) {
if (argc < 3) {
std::cout << "Usage: projectTest input.obj output\n";
return false;
}
std::cout << "input is " << argv[1] << " and output is " << argv[2] <<std::endl;
OsdSetErrorCallback(callbackError);
createOsdMesh(1);
std::string errorMessage;
if (not createOsdMesh(argv[1], argv[2], &errorMessage)) {
std::cout << "Failed with error: " << errorMessage << std::endl;
}
}

View File

@ -36,7 +36,8 @@ set(PUBLIC_HEADER_FILES
drawController.h
mesh.h
refiner.h
topology.h
topology.h
uniformEvaluator.h
)
add_library(osdutil
@ -45,7 +46,9 @@ add_library(osdutil
refiner.h
refiner.cpp
topology.h
topology.cpp
topology.cpp
uniformEvaluator.h
uniformEvaluator.cpp
${INC_FILES}
)

View File

@ -107,7 +107,7 @@ PxOsdUtilRefiner::Initialize(
// create the quad tables to include all levels by specifying
// firstLevel as 1
FarMeshFactory<OsdVertex> uniformMeshFactory(
_mesh->GetHbrMesh(), t.maxLevels, false, /*firstLevel=*/1);
_mesh->GetHbrMesh(), t.maxLevels, false);
_farMesh = uniformMeshFactory.Create();

View File

@ -1,3 +1,26 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "topology.h"
#include <sstream>

View File

@ -1,3 +1,26 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#ifndef PX_OSD_UTIL_TOPOLOGY_H
#define PX_OSD_UTIL_TOPOLOGY_H

View File

@ -0,0 +1,319 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "uniformEvaluator.h"
#define HBR_ADAPTIVE
#include "../hbr/mesh.h"
#include "../osd/vertex.h"
#include <omp.h>
#include "../osd/ompComputeController.h"
#include "../osd/cpuComputeController.h"
#include <fstream>
#include <iostream>
using namespace OpenSubdiv;
using namespace std;
PxOsdUtilUniformEvaluator::PxOsdUtilUniformEvaluator():
_refiner(NULL),
_ownsRefiner(false),
_computeContext(NULL),
_vertexBuffer(NULL),
_vvBuffer(NULL)
{
}
PxOsdUtilUniformEvaluator::~PxOsdUtilUniformEvaluator()
{
if (_ownsRefiner and _refiner) {
delete _refiner;
}
if (_computeContext)
delete _computeContext;
if (_vertexBuffer)
delete _vertexBuffer;
if (_vvBuffer)
delete _vvBuffer;
}
bool
PxOsdUtilUniformEvaluator::Initialize(
const PxOsdUtilSubdivTopology &t,
string *errorMessage)
{
std::cout << "Initializing evaluator with topology\n";
// create and initialize a refiner, passing "false" for adaptive
// to indicate we wish for uniform refinement
PxOsdUtilRefiner *refiner = new PxOsdUtilRefiner();
_ownsRefiner = true;
std::cout << "Created refiner\n";
if (not refiner->Initialize(t, false, errorMessage)) {
return false;
}
std::cout << "Initialized refiner\n";
return Initialize(refiner, errorMessage);
}
bool
PxOsdUtilUniformEvaluator::Initialize(
PxOsdUtilRefiner *refiner,
string *errorMessage)
{
std::cout << "Initializing evaluator with refiner\n";
if (refiner->GetAdaptive()) {
if (errorMessage)
*errorMessage = "Uniform evaluator requires uniform refiner";
std::cout << *errorMessage << "\n";
return false;
}
// Note we assume someone else keeps this pointer alive
_refiner = refiner;
_ownsRefiner = false;
const FarMesh<OsdVertex> *fmesh = _refiner->GetFarMesh();
const HbrMesh<OsdVertex> *hmesh = _refiner->GetHbrMesh();
const vector<string> &vvNames = _refiner->GetTopology().vvNames;
if (not (fmesh and hmesh)) {
if (errorMessage)
*errorMessage = "No valid uniform far/hbr mesh";
std::cout << *errorMessage << "\n";
return false;
}
// No need to create a far mesh if no subdivision is required.
if (_refiner->GetTopology().maxLevels == 0) {
// Three elements per unrefined point
_vertexBuffer = OsdCpuVertexBuffer::Create(
3, hmesh->GetNumVertices());
// zeros
memset( _vertexBuffer->BindCpuBuffer(), 0,
3 * hmesh->GetNumVertices() * sizeof(float));
if (vvNames.size()) {
// One element in the vertex buffer for each
// named vertex varying attribute in the unrefined mesh
_vvBuffer = OsdCpuVertexBuffer::Create(
vvNames.size(), hmesh->GetNumVertices());
// zeros
memset( _vvBuffer->BindCpuBuffer(), 0,
vvNames.size() * hmesh->GetNumVertices() * sizeof(float));
}
return true;
}
_computeContext = OsdCpuComputeContext::Create(fmesh);
// Three elements per refined point
_vertexBuffer = OsdCpuVertexBuffer::Create(
3, fmesh->GetNumVertices());
// zeros
memset( _vertexBuffer->BindCpuBuffer(), 0,
3 * fmesh->GetNumVertices() * sizeof(float));
if (vvNames.size()) {
// One element in the vertex buffer for each
// named vertex varying attribute in the refined mesh
_vvBuffer = OsdCpuVertexBuffer::Create(
vvNames.size(), fmesh->GetNumVertices());
// zeros
memset( _vvBuffer->BindCpuBuffer(), 0,
vvNames.size() * fmesh->GetNumVertices() * sizeof(float));
}
return true;
}
void
PxOsdUtilUniformEvaluator::SetCoarsePositions(
const vector<float>& coords, string *errorMessage )
{
const float* pFloats = &coords.front();
int numFloats = (int) coords.size();
//XXX: should be >= num coarse vertices
if (numFloats/3 >= _refiner->GetFarMesh()->GetNumVertices()) {
if (errorMessage)
*errorMessage = "Indexing error in tesselator";
std::cout << *errorMessage << "\n";
} else {
_vertexBuffer->UpdateData(pFloats, 0, numFloats / 3);
}
}
void
PxOsdUtilUniformEvaluator::SetCoarseVVData(
const vector<float>& data, string *errorMessage
)
{
if (!_vvBuffer) {
if (!data.empty() and errorMessage)
*errorMessage =
"Mesh was not constructed with VV variables.";
return;
}
int numElements = _vvBuffer->GetNumElements();
int numVertices = (int) data.size() / numElements;
const float* pFloats = &data.front();
_vvBuffer->UpdateData(pFloats, 0, numVertices);
}
bool
PxOsdUtilUniformEvaluator::Refine(
int numThreads, string *errorMessage)
{
const FarMesh<OsdVertex> *fmesh = _refiner->GetFarMesh();
if (numThreads > 1) {
OsdOmpComputeController ompComputeController(numThreads);
ompComputeController.Refine(_computeContext,
fmesh->GetKernelBatches(),
_vertexBuffer, _vvBuffer);
} else {
OsdCpuComputeController cpuComputeController;
cpuComputeController.Refine(_computeContext,
fmesh->GetKernelBatches(),
_vertexBuffer, _vvBuffer);
}
return true;
}
bool
PxOsdUtilUniformEvaluator::GetRefinedPositions(
float **positions, int *numFloats,
string *errorMessage) const
{
if (not (positions and numFloats)) {
if (errorMessage) {
*errorMessage =
"GetRefinedPositions: positions and/or numFloats was NULL";
}
return false;
}
if (!_refiner->IsRefined()) {
if (errorMessage) {
*errorMessage = "GetRefinedPositions: Mesh has not been refined.";
}
return false;
}
int numRefinedVerts = _refiner->GetNumRefinedVertices();
int firstVertexOffset = _refiner->GetFirstVertexOffset();
if (numRefinedVerts == 0) {
if (errorMessage) {
*errorMessage = "GetRefinedPositions: not refined.";
}
return false;
}
// The vertexBuffer has all subdivision levels, here we are skipping
// past the vertices on lower subdivision levels and returning
// a pointer to the start of the most refined level
*positions = _vertexBuffer->BindCpuBuffer() + (3*firstVertexOffset);
*numFloats = numRefinedVerts*3;
return true;
}
bool
PxOsdUtilUniformEvaluator::GetRefinedVVData(
float **data, int *numFloats, int *numElementsRetVal,
std::string *errorMessage ) const
{
if (not (data and numFloats)) {
if (errorMessage) {
*errorMessage =
"GetRefinedVVData: data and/or numFloats was NULL";
}
return false;
}
if (!_refiner->IsRefined()) {
if (errorMessage) {
*errorMessage = "GetRefinedVVData: Mesh has not been refined.";
}
return false;
}
int numRefinedVerts = _refiner->GetNumRefinedVertices();
int firstVertexOffset = _refiner->GetFirstVertexOffset();
if (numRefinedVerts == 0) {
if (errorMessage) {
*errorMessage = "GetRefinedVVData: not refined.";
}
return false;
}
int numElements = GetTopology().vvNames.size();
if (numElementsRetVal)
*numElementsRetVal = numElements;
// The vertexBuffer has all subdivision levels, here we are skipping
// past the vertices on lower subdivision levels and returning
// a pointer to the start of the most refined level
*data =
_vvBuffer->BindCpuBuffer() + (numElements * firstVertexOffset);
*numFloats = numElements * numRefinedVerts;
return true;
}

View File

@ -0,0 +1,184 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#ifndef PXOSDUTIL_UNIFORM_EVALUATOR_H
#define PXOSDUTIL_UNIFORM_EVALUATOR_H
#include "refiner.h"
#include <string>
#include <vector>
#define HBR_ADAPTIVE
#include "../osd/cpuVertexBuffer.h"
#include "../osd/cpuComputeContext.h"
#include "../far/mesh.h"
// This class takes a mesh that has undergone uniform refinement to
// a fixed subdivision level, and creates required run time OpenSubdiv
// data structures used to sample values on subdivision surfaces.
//
// An important note here is that refined positions and vertex varying
// attributes are sampled at the n-th subdivision level, not at the
// exact limit surface. Use PxOsdUtilAdaptiveEvaluator for true limits.
//
class PxOsdUtilUniformEvaluator {
public:
PxOsdUtilUniformEvaluator();
~PxOsdUtilUniformEvaluator();
// Initialize returns false on error. If errorMessage is non-NULL it'll
// be populated upon error.
//
// If successful vertex buffers and compute contexts will have been
// created and are ready to SetCoarse* methods, call Refine, and then
// sample refined values with the Get* methods
//
// Note that calling this method assumes that the evaluator isn't
// responsible for the refiner's lifetime, someone else needs to
// hold onto the refiner pointer. This allows for lightweight sharing
// of refiners among evaluators.
//
bool Initialize(
PxOsdUtilRefiner* refiner,
std::string *errorMessage = NULL);
bool Initialize(
const PxOsdUtilSubdivTopology &topology,
std::string *errorMessage = NULL);
// Set new coarse-mesh CV positions, need to call Refine
// before calling Get* methods
void SetCoarsePositions(
const std::vector<float>& coords,
std::string *errorMessage = NULL
);
// Set new coarse-mesh vertex varying values, need to call Refine
// before calling Get* methods
void SetCoarseVVData(
const std::vector<float>& data,
std::string *errorMessage = NULL
);
// Refine the coarse mesh, needed before calling GetPositions, GetQuads etc.
// If numThreads is 1, use single cpu. If numThreads > 1 use Omp and set
// number of omp threads.
//
bool Refine(int numThreads,
std::string *errorMessage = NULL);
// Grab position results of calling Refine, return by reference a pointer
// into _vertexBuffer memory with the refined points positions,
// packed as 3 floats per point. This doesn't involve a copy, the
// pointer will be valid as long as _vertexBuffer exists.
//
bool GetRefinedPositions(float **positions, int *numFloats,
std::string *errorMessage = NULL) const;
// Grab vertex varying results of calling Refine, return by reference
// a pointer into _vvBuffer memory with the refined data,
// packed as (numElements) floats per point. This doesn't involve
// a copy, the pointer will be valid as long as _vertexBuffer exists.
//
bool GetRefinedVVData(float **data, int *numFloats,
int *numElements = NULL,
std::string *errorMessage = NULL) const;
// Fetch the face varying attribute values on refined quads, call
// through to the refiner but keep in evaluator API for
// one-stop-service for user API
void GetRefinedFVData(int subdivisionLevel,
const std::vector<std::string>& names,
std::vector<float>* fvdata) {
_refiner->GetRefinedFVData(subdivisionLevel, names, fvdata);
}
// Fetch the topology of the post-refined mesh. The "quads" vector
// will be filled with 4 ints per quad which index into a vector
// of positions.
//
// Calls through to the refiner.
//
bool GetRefinedQuads(std::vector<int>* quads,
std::string *errorMessage = NULL) const {
return _refiner->GetRefinedQuads(quads, errorMessage);
}
// Fetch the U/V coordinates of the refined quads in the U/V space
// of their parent coarse face
bool GetRefinedPtexUvs(std::vector<float>* subfaceUvs,
std::vector<int>* ptexIndices,
std::string *errorMessage = NULL) const {
return _refiner->GetRefinedPtexUvs(subfaceUvs, ptexIndices, errorMessage);
}
// Write the refined quad mesh to given filename, return false on error
// bool WriteRefinedObj( const std::string &filename,
// std::string *errorMessage = NULL) const;
// Forward these calls through to the refiner, which may forward
// to the mesh. Make these top level API calls on the evaluator
// so clients can talk to a single API
//
const std::string &GetName() const { return _refiner->GetName();}
const OpenSubdiv::HbrMesh<OpenSubdiv::OsdVertex> *GetHbrMesh() {
return _refiner->GetHbrMesh();
}
const PxOsdUtilSubdivTopology &GetTopology() const {
return _refiner->GetTopology();
}
const OpenSubdiv::FarMesh<OpenSubdiv::OsdVertex>* GetFarMesh() {
return _refiner->GetFarMesh();
}
private:
// A pointer to the shared refiner used. Note that this class may
// own the refiner pointer (if _ownsRefiner is true), or it may
// assume that someone else is responsible for managing that pointer
// if _ownsRefiner is false.
PxOsdUtilRefiner *_refiner;
bool _ownsRefiner;
// responsible for performing uniform catmull/clark subdivision
// on the incoming polygonal topology. This only stores topology and
// face varying data and can be shared among threads. Doesn't store
// per-vertex information being refined in different threads, those
// are in vertex buffers that can't be shared.
OpenSubdiv::OsdCpuComputeContext *_computeContext;
OpenSubdiv::OsdCpuVertexBuffer *_vertexBuffer;
OpenSubdiv::OsdCpuVertexBuffer *_vvBuffer;
};
#endif /* PXOSDUTIL_UNIFORM_EVALUATOR_H */