mayaViewer.cpp

Consolidated documentation for the source code collectively known as mayaViewer:

     OpenSubdivShader.h/cpp
     OpenSubdivShaderOverride.h/cpp
     osdMeshData.h/cpp
     hbrUtil.h/cpp

--------------------------------------------------------------------------------------

OpenSubdivShader.h

Class definition of custom MPxHwShaderNode for drawing OpenSubdiv patches.

class OpenSubdivShader : public MPxHwShaderNode 
{
 Supports Viewport 2.0 rendering only.
 geometry/glGeometry, bind/glBind and unbind/glUnbind
 are not implemented at this time. 
public:
    /* ... Standard MPxHwShader methods and definitions ... */

private:
OSD attributes
    static MObject aLevel;                  // Subdivision level
    static MObject aTessFactor;             // GPU tesselation factor
    static MObject aScheme;                 // Subdivision scheme
    static MObject aAdaptive;               // Feature-adaptive toggle
    static MObject aWireframe;              // Wireframe display toggle
    static MObject aKernel;                 // Computation kernel
    static MObject aInterpolateBoundary;    // Boundary interpolation flag
Material attributes
    static MObject aDiffuse;
    static MObject aAmbient;
    static MObject aSpecular;
    static MObject aShininess;
Texture attributes
    static MObject aDiffuseMapFile;         // Texture filename
    static MObject aUVSet;                  // UV set (defaults to current UV set)
    static MObject aInterpolateUVBoundary;  // Boundary interpolation flag for face-varying data (UVs)
Shader attribute
    static MObject aShaderSource;           // Optional shader file

private:
    /* ... private methods and member variables ... */
};

--------------------------------------------------------------------------------------

OpenSubdivShader.cpp

Custom MPxHwShaderNode for drawing OpenSubdiv patches

OSD include initializing OpenSubdiv library version

/* Must be included before all other OSD includes */
#include <version.h>        

/* GLEW needs to be included before OSD and Maya headers */
#include <GL/glew.h>

/* ... System includes ... */
/* ... Maya includes ... */

dyu - difference between DrawContext and DrawRegistry?

I could take a stab but you can explain this in your sleep right?

#include <osd/glDrawContext.h>
#include <osd/glDrawRegistry.h>
Attribute declarations
/* MObject OpenSubdivShader::... */
shaderSource attribute
 A default shader is compiled into the plug-in.
 The shaderSource attribute offers the ability to modify or 
 replace the shader without having to recompile the plug-in.
MObject OpenSubdivShader::aShaderSource;            // Optional shader file

static const char *defaultShaderSource =
#include "shader.inc"
;

EffectDrawRegistry

 An OpenSubdiv application builds its own draw registry in 
 order to define parameters needed for its shader.  In our 
 case we use the attributes from the OpenSubdivShader to set up 
 parameters and #define directives to pass through subdivision 
 options and to control draw style.  Client/application must
 specialize OsdGLDrawRegistry in order to provide an appearance
 for objects.  Built-in shaders to not contain lighting code.

Draw styles for EffectDrawRegistry

enum Effect 
{
    kQuadFill = 0,
    kQuadLine = 1,
    kTriFill = 2,
    kTriLine = 3,
    kPoint = 4,
};
typedef std::pair<OpenSubdiv::OsdPatchDescriptor, Effect> EffectDesc;

Override of OpenSubdiv::OsdGLDrawInstance

dyu - when would someone need to add anything here?

struct EffectDrawInstance : public OpenSubdiv::OsdGLDrawInstance 
{
};

Override of OpenSubdiv::OsdGLDrawRegistry

At the very least this class needs to define _CreateDrawConfig and _CreateDrawInstance in order to define shader content.

class EffectDrawRegistry :
        public OpenSubdiv::OsdGLDrawRegistry<EffectDesc, EffectDrawInstance> 
{

public:
    EffectDrawRegistry() : _isAdaptive(true),
                           _diffuseId(0),
                           _shaderSource( defaultShaderSource )
            {}
Accessors
 When setInternalValueInContext() gets triggered for certain 
 attributes it will set dirty flags causing the shaders to 
 be rebuilt.
    /* isAdaptive */
    void setIsAdaptive( bool ad ) { resetIfChanged(ad, _isAdaptive); }
    bool getIsAdaptive() const { return _isAdaptive; }

    /* diffuseId */
    void setDiffuseId( GLuint dId ) { resetIfChanged(dId, _diffuseId); }
    GLuint getDiffuseId() const { return _diffuseId; }

    /* shaderSource */
    void setShaderSource( std::string const & src ) { resetIfChanged(src, _shaderSource); }
    std::string const & getShaderSource() const { return _shaderSource; }

protected:
Build shader configuration
    virtual ConfigType * _CreateDrawConfig(DescType const & desc);
Compile and link the shader
    virtual InstanceType * _CreateDrawInstance(DescType const & desc);

private:

Clear the registry if a value has changed, triggering a rebuild

    template< typename T>
    void resetIfChanged( T newVal, T& curVal )
    {
        if ( newVal != curVal ) {
            curVal = newVal;
            Reset();
        }
    }

Parameters mirroring attributes that affect shader generation

    bool        _isAdaptive;
    GLuint      _diffuseId;
    std::string _shaderSource;
};

_CreateDrawConfig

 Called by base registry when a draw configuration is requested.
 Returns a shader source configuration which consists of a 
 set of shader source code and compile-time configuration 
 defines.  These are cached by the effect registry for
 efficient re-use when rebuilding shaders.
EffectDrawRegistry::ConfigType *
EffectDrawRegistry::_CreateDrawConfig(DescType const & desc) 
{
    Effect effect = desc.second;

Create base draw configuration

    ConfigType * config = BaseRegistry::_CreateDrawConfig(desc.first);

    if (desc.first.type != OpenSubdiv::kPerVertex) {

Per-vertex descriptors are use for uniform refinement

        if (effect == kQuadFill) effect = kTriFill;
        if (effect == kQuadLine) effect = kTriLine;
        config->geometryShader.AddDefine("SMOOTH_NORMALS");

    } else {

Configuration for adaptive refinement

        config->vertexShader.version = "#version 410\n";
        config->vertexShader.source = _shaderSource;
        config->vertexShader.AddDefine("VERTEX_SHADER");
    }
    assert(config);

Enable feature-adaptive face-varying UV generation

    if (_isAdaptive) {
        config->geometryShader.AddDefine("FVAR_ADAPTIVE");
    }

Enable diffuse texture display if there is a valid texture map

    if (_diffuseId != 0) {
        config->fragmentShader.AddDefine("USE_DIFFUSE_MAP");
    }

NUM_ELEMENTS should be set to the same value that is specified for the "numElements" argument when creating OsdVertexBuffers, e.g. OsdGLVertexBuffer

    config->vertexShader.AddDefine("NUM_ELEMENTS", "3");

Initialize geometry shader

    config->geometryShader.version = "#version 410\n";
    config->geometryShader.source = _shaderSource;
    config->geometryShader.AddDefine("GEOMETRY_SHADER");

Initialize fragment shader

    config->fragmentShader.version = "#version 410\n";
    config->fragmentShader.source = _shaderSource;
    config->fragmentShader.AddDefine("FRAGMENT_SHADER");

Set up directives according to draw style

    switch (effect) {
    case kQuadFill:
        config->geometryShader.AddDefine("PRIM_QUAD");
        config->geometryShader.AddDefine("GEOMETRY_OUT_FILL");
        config->fragmentShader.AddDefine("PRIM_QUAD");
        config->fragmentShader.AddDefine("GEOMETRY_OUT_FILL");
        break;
    case kQuadLine:
        config->geometryShader.AddDefine("PRIM_QUAD");
        config->geometryShader.AddDefine("GEOMETRY_OUT_LINE");
        config->fragmentShader.AddDefine("PRIM_QUAD");
        config->fragmentShader.AddDefine("GEOMETRY_OUT_LINE");
        break;
    case kTriFill:
        config->geometryShader.AddDefine("PRIM_TRI");
        config->geometryShader.AddDefine("GEOMETRY_OUT_FILL");
        config->fragmentShader.AddDefine("PRIM_TRI");
        config->fragmentShader.AddDefine("GEOMETRY_OUT_FILL");
        break;
    case kTriLine:
        config->geometryShader.AddDefine("PRIM_TRI");
        config->geometryShader.AddDefine("GEOMETRY_OUT_LINE");
        config->fragmentShader.AddDefine("PRIM_TRI");
        config->fragmentShader.AddDefine("GEOMETRY_OUT_LINE");
        break;
    case kPoint:
        config->geometryShader.AddDefine("PRIM_POINT");
        config->fragmentShader.AddDefine("PRIM_POINT");
        break;
    }

    return config;
}

Global GL buffer IDs and binding points

GLuint g_transformUB = 0,
       g_transformBinding = 0,
       g_tessellationUB = 0,
       g_tessellationBinding = 0,
       g_lightingUB = 0,
       g_lightingBinding = 0;

compileShader

 Common code for compiling each of the shader types
 (vertex, geometry, fragment, etc)
static GLuint
compileShader(GLenum shaderType,
              OpenSubdiv::OsdDrawShaderSource const & common,
              OpenSubdiv::OsdDrawShaderSource const & source) 
{
    const char *sources[4];
    std::stringstream definitions;
    for (size_t i = 0; i < common.defines.size(); ++i) {
        definitions << "#define "
                    << common.defines[i].first << " "
                    << common.defines[i].second << "\n";
    }
    for (size_t i = 0; i < source.defines.size(); ++i) {
        definitions << "#define "
                    << source.defines[i].first << " "
                    << source.defines[i].second << "\n";
    }
    std::string defString = definitions.str();

    sources[0] = defString.c_str();
    sources[1] = source.version.c_str();
    sources[2] = common.source.c_str();
    sources[3] = source.source.c_str();

    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 4, sources, NULL);
    glCompileShader(shader);

    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        GLchar emsg[1024];
        glGetShaderInfoLog(shader, sizeof(emsg), 0, emsg);
        fprintf(stderr, "Error compiling GLSL shader: %s\n", emsg);
        exit(0);
    }

    return shader;
}

_CreateDrawInstance

 Called by base registry when a draw instance is requested.
 Returns a compiled and linked shader program corresponding to 
 a previously created DrawConfig. The effect registry also 
 caches these for efficient re-use.
EffectDrawRegistry::InstanceType *
EffectDrawRegistry::_CreateDrawInstance(DescType const & desc) 
{
    ConfigType * config = GetDrawConfig(desc);
    assert(config);

    GLuint vertexShader =
        config->vertexShader.source.empty() ? 0 :
        compileShader(GL_VERTEX_SHADER,
                      config->commonShader, config->vertexShader);

    GLuint tessControlShader =
        config->tessControlShader.source.empty() ? 0 :
        compileShader(GL_TESS_CONTROL_SHADER,
                      config->commonShader, config->tessControlShader);

    GLuint tessEvalShader =
        config->tessEvalShader.source.empty() ? 0 :
        compileShader(GL_TESS_EVALUATION_SHADER,
                      config->commonShader, config->tessEvalShader);

    GLuint geometryShader =
        config->geometryShader.source.empty() ? 0 :
        compileShader(GL_GEOMETRY_SHADER,
                      config->commonShader, config->geometryShader);

    GLuint fragmentShader =
        config->fragmentShader.source.empty() ? 0 :
        compileShader(GL_FRAGMENT_SHADER,
                      config->commonShader, config->fragmentShader);

    InstanceType * instance = new InstanceType();

Create GL program object and attach all of our shaders

    GLuint program = glCreateProgram();
    if (vertexShader)      glAttachShader(program, vertexShader);
    if (tessControlShader) glAttachShader(program, tessControlShader);
    if (tessEvalShader)    glAttachShader(program, tessEvalShader);
    if (geometryShader)    glAttachShader(program, geometryShader);
    if (fragmentShader)    glAttachShader(program, fragmentShader);

Create shader executable

    glLinkProgram(program);

    CHECK_GL_ERROR("CreateDrawInstance link program\n");

Flag shaders for deletion when program is finished

    glDeleteShader(vertexShader);
    glDeleteShader(tessControlShader);
    glDeleteShader(tessEvalShader);
    glDeleteShader(geometryShader);
    glDeleteShader(fragmentShader);

Check results of glLinkProgram

    GLint status;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        GLchar emsg[1024];
        glGetProgramInfoLog(program, sizeof(emsg), 0, emsg);
        fprintf(stderr, "Error linking GLSL program : %s\n", emsg);
        exit(0);
    }

Assign binding points to uniform blocks

    /* struct Transform */
    g_transformBinding = 0;
    glUniformBlockBinding(program,
        glGetUniformBlockIndex(program, "Transform"),
        g_transformBinding);

    /* struct Tessellation */
    g_tessellationBinding = 1;
    glUniformBlockBinding(program,
        glGetUniformBlockIndex(program, "Tessellation"),
        g_tessellationBinding);

    /* struct Lighting */
    g_lightingBinding = 2;
    glUniformBlockBinding(program,
        glGetUniformBlockIndex(program, "Lighting"),
        g_lightingBinding);

Specify texture buffer ID uniforms in shader

    GLint loc;
    if ((loc = glGetUniformLocation(program, "g_VertexBuffer")) != -1) {
        glProgramUniform1i(program, loc, 0);  // GL_TEXTURE0
    }
    if ((loc = glGetUniformLocation(program, "g_ValenceBuffer")) != -1) {
        glProgramUniform1i(program, loc, 1);  // GL_TEXTURE1
    }
    if ((loc = glGetUniformLocation(program, "g_QuadOffsetBuffer")) != -1) {
        glProgramUniform1i(program, loc, 2);  // GL_TEXTURE2
    }
    if ((loc = glGetUniformLocation(program, "g_patchLevelBuffer")) != -1) {
        glProgramUniform1i(program, loc, 3);  // GL_TEXTURE3
    }
    if ((loc = glGetUniformLocation(program, "g_uvFVarBuffer")) != -1) {
        glProgramUniform1i(program, loc, 4);  // GL_TEXTURE4
    }

    instance->program = program;

    CHECK_GL_ERROR("CreateDrawInstance leave\n");

    return instance;
}

Shader construction and initialization

OpenSubdivShader::OpenSubdivShader() {...}
OpenSubdivShader::~OpenSubdivShader() {...}
void * OpenSubdivShader::creator() {...}
MStatus OpenSubdivShader::initialize() {...}
void OpenSubdivShader::postConstructor() {...}

MStatus
OpenSubdivShader::compute(const MPlug &, MDataBlock &) 
{
    return MS::kSuccess;
}

updateAttributes

 Called by openSubdivShaderOverride in updateDG.
 Pulls values for all non-internal attributes.
void
OpenSubdivShader::updateAttributes() 
{
    MObject object = thisMObject();
    MFnDependencyNode depFn(object);

Retrieve non-internal attributes

    _diffuse = getColor(object, aDiffuse);
    _ambient = getColor(object, aAmbient);
    _specular = getColor(object, aSpecular);

    getAttribute(object, aWireframe, &_wireframe);
    getAttribute(object, aShininess, &_shininess);

Pull on any plugs that might be connected

    getAttribute(object, aDiffuseMapFile, &_diffuseMapFile);
}

Main draw method

 Called by OpenSubdivShaderOverride for each renderItem.
 Binds the vertex buffer and calls glDrawElements for 
 each patch.
void
OpenSubdivShader::draw(const MHWRender::MDrawContext &mDrawContext,
                             OsdMeshData *data)
{
    glPushAttrib(GL_POLYGON_BIT|GL_ENABLE_BIT);

    if (_wireframe) {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    } else {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    CHECK_GL_ERROR("draw begin\n");

If shader source attribute has changed, update effectRegistry

    updateRegistry();

Bind position vertex buffer

    GLuint bPosition = data->bindPositionVBO();
    OpenSubdiv::OsdGLDrawContext *osdDrawContext = data->getDrawContext();

    glBindBuffer(GL_ARRAY_BUFFER, bPosition);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, 0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, osdDrawContext->patchIndexBuffer);

Get list of patches from OSD

    OpenSubdiv::OsdPatchArrayVector const & patches =
        osdDrawContext->patchArrays;

Draw patches

    for (size_t i = 0; i < patches.size(); ++i) {
        OpenSubdiv::OsdPatchArray const & patch = patches[i];

        GLint surfaceProgram = bindProgram(mDrawContext, osdDrawContext, patch);

        if (patch.desc.type != OpenSubdiv::kPerVertex) {
            glPatchParameteri(GL_PATCH_VERTICES, patch.patchSize);

            glDrawElements(GL_PATCHES,
                           patch.numIndices, GL_UNSIGNED_INT,
                           reinterpret_cast<void *>(patch.firstIndex *
                                                    sizeof(unsigned int)));
        } else {
            glDrawElements(_scheme == OsdMeshData::kLoop ? GL_TRIANGLES : GL_LINES_ADJACENCY,
                           patch.numIndices, GL_UNSIGNED_INT,
                           reinterpret_cast<void *>(patch.firstIndex *
                                                    sizeof(unsigned int)));
        }
        CHECK_GL_ERROR("post draw\n");
    }

Clear state

    glUseProgram(0);
    glDisableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glPopAttrib();

    CHECK_GL_ERROR("draw end\n");
}

bindTexture

 Utility routine which binds a single texture map
GLuint 
OpenSubdivShader::bindTexture( const MString& filename, int textureUnit )
{
    GLuint textureId = 0;

    MHWRender::MTexture *mTexture = _theTextureManager->acquireTexture(filename);
    if (mTexture) {
        textureId = *(GLuint*)mTexture->resourceHandle();
        glActiveTexture(GL_TEXTURE0+textureUnit);
        glBindTexture(GL_TEXTURE_2D, textureId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    } else {
        fprintf(stderr,"Can't read texture file: \"%s\"\n", filename.asChar());
    }
    return textureId;
}

updateRegistry

 Evaluates dirty flags and updates the effect registry if any
 attributes have changed that require the shader to be rebuilt
void
OpenSubdivShader::updateRegistry()
{
    /* If adaptive flag has changed, update the effectRegistry accordingly */
    if (_adaptiveDirty) {
        _effectRegistry.setIsAdaptive(_adaptive);
        _adaptiveDirty = false;
    }

    MHWRender::MRenderer *theRenderer = MHWRender::MRenderer::theRenderer();
    _theTextureManager = theRenderer->getTextureManager();

    /* If diffuse texture has changed, update the effectRegistry accordingly */
    if (_diffuseMapDirty) {
        GLuint diffMapId = bindTexture( _diffuseMapFile, DIFF_TEXTURE_UNIT );
        _effectRegistry.setDiffuseId(diffMapId);
        _diffuseMapDirty = false;
    }

    /* If shader source has changed, update the effectRegistry accordingly */
    if (_shaderSourceDirty) {
        if ( _shaderSource.empty() ) {
            if ( _effectRegistry.getShaderSource() != defaultShaderSource ) {
                _effectRegistry.setShaderSource(defaultShaderSource);
            }
        } else {
            if ( _effectRegistry.getShaderSource() != _shaderSource ) {
                _effectRegistry.setShaderSource(_shaderSource);
            }
        }
        _shaderSourceDirty = false;
    }
}

bindProgram

 Do all the work to build and install shader including
 set up buffer blocks for uniform variables, set up
 default lighting parameters, pass material uniforms
 and bind texture buffers used by texture maps and by
 OpenSubdiv's built-in shading code.
GLuint
OpenSubdivShader::bindProgram(const MHWRender::MDrawContext &     mDrawContext,
                                    OpenSubdiv::OsdGLDrawContext *osdDrawContext,
                              const OpenSubdiv::OsdPatchArray &   patch)
{

    CHECK_GL_ERROR("bindProgram begin\n");

Primitives are triangles for Loop subdivision, quads otherwise

    Effect effect = (_scheme == OsdMeshData::kLoop) ? kTriFill : kQuadFill;
    EffectDesc effectDesc( patch.desc, effect );

Build shader

    EffectDrawRegistry::InstanceType * instance = _effectRegistry.GetDrawInstance(effectDesc);

Install shader

    GLuint program = instance->program;
    glUseProgram(program);

Update and bind transform state

    struct Transform {
        float ModelViewMatrix[16];
        float ProjectionMatrix[16];
        float ModelViewProjectionMatrix[16];
    } transformData;
    setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewMtx),
              transformData.ModelViewMatrix);
    setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kProjectionMtx),
              transformData.ProjectionMatrix);
    setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewProjMtx),
              transformData.ModelViewProjectionMatrix);

    if (!g_transformUB) {
        glGenBuffers(1, &g_transformUB);
        glBindBuffer(GL_UNIFORM_BUFFER, g_transformUB);
        glBufferData(GL_UNIFORM_BUFFER,
                sizeof(transformData), NULL, GL_STATIC_DRAW);
    };
    glBindBuffer(GL_UNIFORM_BUFFER, g_transformUB);
    glBufferSubData(GL_UNIFORM_BUFFER,
                0, sizeof(transformData), &transformData);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    glBindBufferBase(GL_UNIFORM_BUFFER, g_transformBinding, g_transformUB);

#define MAX_LEVEL 16

Update and bind tessellation state

    struct Tessellation {
        /* XXX: std140 layout requires vec4 aligns for every entry */
        float TessLevelInner[MAX_LEVEL][4];
        float TessLevelOuter[MAX_LEVEL][4];
        int GregoryQuadOffsetBase;
        int LevelBase;
    } tessellationData;

    int tessFactor = 2 << _tessFactor;
    for (int i = 0; i < MAX_LEVEL; ++i) {
        tessellationData.TessLevelInner[i][0] = float(std::max(tessFactor >> i, 1));
        tessellationData.TessLevelOuter[i][0] = float(std::max(tessFactor >> i, 1));
    }

    tessellationData.GregoryQuadOffsetBase = patch.gregoryQuadOffsetBase;
    tessellationData.LevelBase = patch.levelBase;

    if (!g_tessellationUB) {
        glGenBuffers(1, &g_tessellationUB);
        glBindBuffer(GL_UNIFORM_BUFFER, g_tessellationUB);
        glBufferData(GL_UNIFORM_BUFFER,
                sizeof(tessellationData), NULL, GL_STATIC_DRAW);
    };
    glBindBuffer(GL_UNIFORM_BUFFER, g_tessellationUB);
    glBufferSubData(GL_UNIFORM_BUFFER,
                0, sizeof(tessellationData), &tessellationData);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    glBindBufferBase(GL_UNIFORM_BUFFER,
                     g_tessellationBinding,
                     g_tessellationUB);

Update and bind lighting state

    int numLights = mDrawContext.numberOfActiveLights();
    struct Lighting {
        struct Light {
            float position[4];
            float diffuse[4];
            float ambient[4];
            float specular[4];
        } lightSource[2];
    } lightingData;
    memset(&lightingData, 0, sizeof(lightingData));

    for (int i = 0; i < numLights && i < 2; ++i) {
        MFloatPointArray positions;
        MFloatVector direction;
        float intensity;
        MColor color;
        bool hasDirection, hasPosition;
        mDrawContext.getLightInformation(i, positions, direction, intensity,
                                    color, hasDirection, hasPosition);

        MMatrix modelView = mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewMtx);
        direction = MVector(direction) * modelView;

        Lighting::Light &light = lightingData.lightSource[i];
        if (hasDirection) {
            light.position[0] = -direction[0];
            light.position[1] = -direction[1];
            light.position[2] = -direction[2];

            for (int j = 0; j < 4; ++j) {
                light.diffuse[j] = color[j] * intensity;
                light.ambient[j] = color[j] * intensity;
                light.specular[j] = color[j] * intensity;
            }
        }
    }

    if (!g_lightingUB) {
        glGenBuffers(1, &g_lightingUB);
        glBindBuffer(GL_UNIFORM_BUFFER, g_lightingUB);
        glBufferData(GL_UNIFORM_BUFFER,
                sizeof(lightingData), NULL, GL_STATIC_DRAW);
    };
    glBindBuffer(GL_UNIFORM_BUFFER, g_lightingUB);
    glBufferSubData(GL_UNIFORM_BUFFER,
                0, sizeof(lightingData), &lightingData);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    glBindBufferBase(GL_UNIFORM_BUFFER, g_lightingBinding, g_lightingUB);

Update other uniforms

    float color[4] = { 0, 0, 0, 1 };
    _diffuse.get(color);
    glProgramUniform4fv(program,
                        glGetUniformLocation(program, "diffuseColor"),
                        1, color);
    _ambient.get(color);
    glProgramUniform4fv(program,
                        glGetUniformLocation(program, "ambientColor"),
                        1, color);
    _specular.get(color);
    glProgramUniform4fv(program,
                        glGetUniformLocation(program, "specularColor"),
                        1, color);
    glProgramUniform1f(program,
                       glGetUniformLocation(program, "shininess"),
                       _shininess);

Bind diffuse map

    if (_effectRegistry.getDiffuseId()!=0) {
        GLint difmap = glGetUniformLocation(program, "diffuseMap");
        glProgramUniform1i(program, difmap, DIFF_TEXTURE_UNIT);
    }

Bind all texture buffers

 OpenSubdiv's geometric shading code depends on additional 
 GL texture buffers. These are managed by the DrawContext 
 and must be bound for use by the program in addition to 
 any buffers used by the client/application shading code.
    if (osdDrawContext->vertexTextureBuffer) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_BUFFER,
                      osdDrawContext->vertexTextureBuffer);
    }
    if (osdDrawContext->vertexValenceTextureBuffer) {
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_BUFFER,
                      osdDrawContext->vertexValenceTextureBuffer);
    }
    if (osdDrawContext->quadOffsetTextureBuffer) {
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_BUFFER,
                      osdDrawContext->quadOffsetTextureBuffer);
    }
    if (osdDrawContext->patchLevelTextureBuffer) {
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_BUFFER,
                      osdDrawContext->patchLevelTextureBuffer);
    }
    if (osdDrawContext->fvarDataTextureBuffer) {
        glActiveTexture( GL_TEXTURE4 );
        glBindTexture(  GL_TEXTURE_BUFFER, 
                      osdDrawContext->fvarDataTextureBuffer );
    }

    glActiveTexture(GL_TEXTURE0);

    CHECK_GL_ERROR("bindProgram leave\n");

    return program;
}

--------------------------------------------------------------------------------------

OpenSubdivShaderOverride.h

 Viewport 2.0 override for OpenSubdivShader
class OpenSubdivShaderOverride : public MHWRender::MPxShaderOverride 
{
public:
    /* ... Standard MPxShaderOverride methods and definitions ... */
    
private:
Pointer to associated shader
    OpenSubdivShader *_shader;

};

--------------------------------------------------------------------------------------

OpenSubdivShaderOverride.cpp

 Viewport 2.0 override for OpenSubdivShader, implementing
 custom shading for OpenSubdiv patches.
/* Include GLEW before Maya and OSD includes */
#include <GL/glew.h>

/* ... System includes ... */
/* ... Maya includes ... */

Set up compute controllers for each available compute kernel.

dyu - what exactly is a compute controller?

#include <osd/cpuComputeController.h>
OpenSubdiv::OsdCpuComputeController *g_cpuComputeController = 0;

#ifdef OPENSUBDIV_HAS_OPENMP
    #include <osd/ompComputeController.h>
    OpenSubdiv::OsdOmpComputeController *g_ompComputeController = 0;
#endif

#ifdef OPENSUBDIV_HAS_OPENCL
    #include <osd/clComputeController.h>
    cl_context g_clContext;
    cl_command_queue g_clQueue;
    #include "../common/clInit.h"
    OpenSubdiv::OsdCLComputeController *g_clComputeController = 0;
#endif

#ifdef OPENSUBDIV_HAS_CUDA
    #include <osd/cudaComputeController.h>
    extern void cudaInit();
    OpenSubdiv::OsdCudaComputeController *g_cudaComputeController = 0;
#endif

OpenSubdivShaderOverride::OpenSubdivShaderOverride(const MObject &obj) {...}
OpenSubdivShaderOverride::~OpenSubdivShaderOverride() {...}
MHWRender::MPxShaderOverride* OpenSubdivShaderOverride::creator(const MObject &obj) {...}

attrChangedCB

 Informs us whenever an attribute on the shape node changes.
 Overkill since we really only want to know if the topology 
 changes (e.g. an edge crease is added or changed) but Maya 
 doesn't give us access to a callback that fine-grained. 
 MMessage::PolyTopologyChangedCallback sounds promising 
 but only calls back on a single change for any edit 
 (i.e. not while dragging).
/*static*/
void 
OpenSubdivShaderOverride::attrChangedCB(MNodeMessage::AttributeMessage msg, MPlug & plug,
                                        MPlug & otherPlug, void* clientData)
{
    /* We only care if the plug is outMesh and the action is "evaluate" */
    if ( msg & MNodeMessage::kAttributeEval ) {
        OsdMeshData *meshData = (OsdMeshData*)clientData;
    MFnDependencyNode depNodeFn(meshData->getDagPath().node());
        if ( plug == depNodeFn.attribute("outMesh")) {
            meshData->setMeshTopoDirty();
        }
    }
}

addTopologyChangedCallbacks

 Add a callback to inform us when topology might be changing 
 so we can update the HBR mesh accordingly.
void
OpenSubdivShaderOverride::addTopologyChangedCallbacks( const MDagPath& dagPath, OsdMeshData *data )
{
    MStatus status = MS::kSuccess;

Extract shape node and add callback to let us know when an attribute changes

    MDagPath meshDagPath = dagPath;
    meshDagPath.extendToShape();
    MObject shapeNode = meshDagPath.node();
    MCallbackId id = MNodeMessage::addAttributeChangedCallback(shapeNode, 
                                    attrChangedCB, data, &status );

Keep track so we can delete callbacks in destructor

    if ( status ) {
        _callbackIds.append( id );
    } else {
        cerr << "MNodeMessage.addCallback failed" << endl;
    }
}

initialize

 Set up vertex buffer descriptors and geometry requirements
MString
OpenSubdivShaderOverride::initialize(const MInitContext &initContext,
                                           MInitFeedback &initFeedback)
{
    MString empty;

Roundabout way of getting positions pulled into our OsdBufferGenerator where we can manage the VBO memory size.

Needs to be re-visited, re-factored, optimized, etc.

dyu - can we add any more explanation about why this is done this way?

    {
        MHWRender::MVertexBufferDescriptor positionDesc(
            empty,
            MHWRender::MGeometry::kPosition,
            MHWRender::MGeometry::kFloat,
            3);
        addGeometryRequirement(positionDesc);
    }

    {
        MHWRender::MVertexBufferDescriptor positionDesc(
            "osdPosition",
            MHWRender::MGeometry::kTangent,
            MHWRender::MGeometry::kFloat,
            3);
        positionDesc.setSemanticName("osdPosition");
        addGeometryRequirement(positionDesc);
    }

Build data object for managing OSD mesh, pass in as custom data

    if (initFeedback.customData == NULL) {
        OsdMeshData *data = new OsdMeshData(initContext.dagPath);
        initFeedback.customData = data;
    }

Add a Maya callback so we can rebuild HBR mesh if topology changes

    addTopologyChangedCallbacks( initContext.dagPath, (OsdMeshData*)initFeedback.customData );

    return MString("OpenSubdivShaderOverride");
}

updateDG

 Save pointer to shader so we have access down the line.
 Call shader to update any attributes it needs to.
void
OpenSubdivShaderOverride::updateDG(MObject object)
{
    if (object == MObject::kNullObj)
        return;

Save pointer to shader for access from draw()

    _shader = static_cast<OpenSubdivShader*>(
        MPxHwShaderNode::getHwShaderNodePtr(object));

Get updated attributes from shader

    if (_shader) {
        _shader->updateAttributes();
    }
}

void
OpenSubdivShaderOverride::updateDevice()
{
}

void
OpenSubdivShaderOverride::endUpdate()
{
}

Override draw method.

Setup draw state and call osdMeshData methods to setup and refine geometry. Call to shader to do actual drawing.

bool
OpenSubdivShaderOverride::draw(
    MHWRender::MDrawContext &context,
    const MHWRender::MRenderItemList &renderItemList) const
{
    {
        MHWRender::MStateManager *stateMgr = context.getStateManager();
        static const MDepthStencilState * depthState = NULL;
        if (!depthState) {
            MDepthStencilStateDesc desc;
            depthState = stateMgr->acquireDepthStencilState(desc);
        }
        static const MBlendState *blendState = NULL;
        if (!blendState) {
            MBlendStateDesc desc;

            int ntargets = desc.independentBlendEnable ?
                MHWRender::MBlendState::kMaxTargets : 1;

            for (int i = 0; i < ntargets; ++i) {
                desc.targetBlends[i].blendEnable = false;
            }
            blendState = stateMgr->acquireBlendState(desc);
        }

        stateMgr->setDepthStencilState(depthState);
        stateMgr->setBlendState(blendState);
    }

    for (int i = 0; i < renderItemList.length(); i++) 
    {
        const MHWRender::MRenderItem *renderItem = renderItemList.itemAt(i);
        OsdMeshData *data =
            static_cast<OsdMeshData*>(renderItem->customData());
        if (data == NULL) {
            return false;
        }

If attributes or topology have changed which affect the HBR mesh it will be regenerated here.

        data->rebuildHbrMeshIfNeeded(_shader);

        const MHWRender::MVertexBuffer *position = NULL;
        {
            const MHWRender::MGeometry *geometry = renderItem->geometry();
            for (int i = 0; i < geometry->vertexBufferCount(); i++) {
                const MHWRender::MVertexBuffer *vb = geometry->vertexBuffer(i);
                const MHWRender::MVertexBufferDescriptor &vdesc = vb->descriptor();

                if (vdesc.name() == "osdPosition")
                    position = vb;
            }
        }

If HBR mesh was regenerated, rebuild FAR mesh factory and recreate OSD draw context

        data->prepare();

Refine geometry

        data->updateGeometry(position);

Draw patches

        _shader->draw(context, data);
    }

    return true;
}

OsdBufferGenerator

Vertex buffer generator for OpenSubdiv geometry

dyu - might need some explanation from takahito here

class OsdBufferGenerator : public MHWRender::MPxVertexBufferGenerator
{
public:
    OsdBufferGenerator() {}
    virtual ~OsdBufferGenerator() {}

    virtual bool getSourceIndexing(
        const MDagPath &dagPath,
        MHWRender::MComponentDataIndexing &sourceIndexing) const
    {
        MFnMesh mesh(dagPath.node());

        MIntArray vertexCount, vertexList;
        mesh.getVertices(vertexCount, vertexList);

        MUintArray &vertices = sourceIndexing.indices();
        for (unsigned int i = 0; i < vertexList.length(); ++i)
            vertices.append((unsigned int)vertexList[i]);

        sourceIndexing.setComponentType(MComponentDataIndexing::kFaceVertex);

        return true;
    }

    virtual bool getSourceStreams(const MDagPath &dagPath,
                                  MStringArray &) const
    {
        return false;
    }

#if MAYA_API_VERSION >= 201350
    virtual void createVertexStream(
        const MDagPath &dagPath, 
              MVertexBuffer &vertexBuffer,
        const MComponentDataIndexing &targetIndexing,
        const MComponentDataIndexing &,
        const MVertexBufferArray &) const
    {
#else
    virtual void createVertexStream(
        const MDagPath &dagPath, MVertexBuffer &vertexBuffer,
        const MComponentDataIndexing &targetIndexing) const
    {
#endif
        const MVertexBufferDescriptor &desc = vertexBuffer.descriptor();

        MFnMesh meshFn(dagPath);
        int nVertices = meshFn.numVertices();
        MFloatPointArray points;
        meshFn.getPoints(points);

#if MAYA_API_VERSION >= 201350
        float *buffer = static_cast<float*>(vertexBuffer.acquire(nVertices, true));
#else
        float *buffer = static_cast<float*>(vertexBuffer.acquire(nVertices));
#endif
        float *dst = buffer;
        for (int i = 0; i < nVertices; ++i) {
            *dst++ = points[i].x;
            *dst++ = points[i].y;
            *dst++ = points[i].z;
        }
        vertexBuffer.commit(buffer);
    }

    static MPxVertexBufferGenerator *positionBufferCreator()
    {
        return new OsdBufferGenerator();
    }
private:
};

--------------------------------------------------------------------------------------

osdMeshData.h

Class definition for OpenSubdiv MUserData used as custom data in OpenSubdivShaderOverride

dyu - hi again, need intelligent descriptions here too! although maybe not if these are described elsewhere getting a bit blurry now

#include <far/meshFactory.h>
#include <osd/glDrawContext.h>

#include <osd/cpuGLVertexBuffer.h>
#include <osd/cpuComputeContext.h>

#ifdef OPENSUBDIV_HAS_OPENCL
    #include <osd/clGLVertexBuffer.h>
    #include <osd/clComputeContext.h>
#endif

#ifdef OPENSUBDIV_HAS_CUDA
    #include <osd/cudaGLVertexBuffer.h>
    #include <osd/cudaComputeContext.h>
#endif

/* ... Maya includes ... */


class OsdMeshData : public MUserData 
{
...
};

--------------------------------------------------------------------------------------

osdMeshData.cpp

/* ... Maya includes ... */

Set up compute controllers for each available compute kernel.

dyu - what exactly is a compute controller? same decription from above...

#include <osd/cpuDispatcher.h>
#include <osd/cpuComputeController.h>
extern OpenSubdiv::OsdCpuComputeController *g_cpuComputeController;

#ifdef OPENSUBDIV_HAS_OPENMP
#include <osd/ompDispatcher.h>
#include <osd/ompComputeController.h>
extern OpenSubdiv::OsdOmpComputeController *g_ompComputeController;
#endif

#ifdef OPENSUBDIV_HAS_OPENCL
#include <osd/clDispatcher.h>
#include <osd/clComputeController.h>
extern cl_context g_clContext;
extern cl_command_queue g_clQueue;
extern OpenSubdiv::OsdCLComputeController *g_clComputeController;
#endif

#ifdef OPENSUBDIV_HAS_CUDA
#include <osd/cudaDispatcher.h>
#include <osd/cudaComputeController.h>
extern OpenSubdiv::OsdCudaComputeController *g_cudaComputeController;
#endif

#include <osd/glDrawContext.h>

Constructor

 Initialize all context and buffers to NULL
OsdMeshData::OsdMeshData(const MDagPath& meshDagPath) {...}
    : MUserData(false), 
{
    _cpuComputeContext = NULL;
    _cpuPositionBuffer = NULL;

#ifdef OPENSUBDIV_HAS_OPENCL
    _clComputeContext = NULL;
    _clPositionBuffer = NULL;
#endif

#ifdef OPENSUBDIV_HAS_CUDA
    _cudaComputeContext = NULL;
    _cudaPositionBuffer = NULL;
#endif
}

Destructor

 Delete meshes, clear contexts and buffers
OsdMeshData::~OsdMeshData() 
{
    delete _hbrmesh;
    delete _farmesh;
    delete _drawContext;
    delete _fvarDesc;

    clearComputeContextAndVertexBuffer();
}

void
OsdMeshData::clearComputeContextAndVertexBuffer() 
{
    delete _cpuComputeContext;
    _cpuComputeContext = NULL;
    delete _cpuPositionBuffer;
    _cpuPositionBuffer = NULL;

#ifdef OPENSUBDIV_HAS_CUDA
    delete _cudaComputeContext;
    _cudaComputeContext = NULL;
    delete _cudaPositionBuffer;
    _cudaPositionBuffer = NULL;
#endif

#ifdef OPENSUBDIV_HAS_OPENCL
    delete _clComputeContext;
    _clComputeContext = NULL;
    delete _clPositionBuffer;
    _clPositionBuffer = NULL;
#endif
}

buildUVList

Face-varying data expects a list of per-face per-vertex floats. This method reads the UVs from the mesh and concatenates them into such a list.

MStatus
OsdMeshData::buildUVList( MFnMesh& meshFn, std::vector<float>& uvList )
{
    MStatus status = MS::kSuccess;

    MItMeshPolygon polyIt( _meshDagPath );

    MFloatArray uArray;
    MFloatArray vArray;

If user hasn't given us a UV set, use the current one

    MString *uvSetPtr=NULL;
    if ( _uvSet.numChars() > 0 ) {
        if (uvSetNameIsValid(meshFn, _uvSet)) {
            uvSetPtr = &_uvSet;
        }
        else {
            MGlobal::displayWarning(MString("OpenSubdivShader:  uvSet \""+_uvSet+"\" does not exist."));
        }
    } else {
        uvSetPtr = NULL;
    }

Pull UVs from Maya mesh

    status = meshFn.getUVs( uArray, vArray, uvSetPtr );
    MCHECK_RETURN(status, "OpenSubdivShader: Error reading UVs");

    if ( uArray.length() == 0 || vArray.length() == 0 )
    {
        MGlobal::displayWarning("OpenSubdivShader: Mesh has no UVs");
        return MS::kFailure;
    }

Initalize list of UV values

    uvList.clear();
    uvList.resize( meshFn.numFaceVertices()*2 );
    int uvListIdx = 0;

For each face-vertex copy UVs into list, adjusting for renderman orientation

    for ( polyIt.reset(); !polyIt.isDone(); polyIt.next() ) 
    { 
        int          faceIdx      = polyIt.index(); 
        unsigned int numPolyVerts = polyIt.polygonVertexCount();

        for ( unsigned int faceVertIdx = 0; 
                           faceVertIdx < numPolyVerts; 
                           faceVertIdx++ )
        {
            int uvIdx;
            polyIt.getUVIndex( faceVertIdx, uvIdx, uvSetPtr );

convert maya UV orientation to renderman orientation

            uvList[ uvListIdx++ ] = uArray[ uvIdx ];
            uvList[ uvListIdx++ ] = 1.0f - vArray[ uvIdx ];
        }
    }

    return status;
}

rebuildHbrMeshIfNeeded

 If the topology of the mesh changes, or any attributes that affect
 how the mesh is computed the original HBR needs to be rebuilt
 which will trigger a rebuild of the FAR mesh and subsequent buffers.
void
OsdMeshData::rebuildHbrMeshIfNeeded(OpenSubdivShader *shader)
{
    MStatus status = MS::kSuccess;

    if (!_meshTopoDirty && !shader->getHbrMeshDirty())
        return;

    MFnMesh meshFn(_meshDagPath);

Cache attribute values

    _level      = shader->getLevel();
    _kernel     = shader->getKernel();
    _adaptive   = shader->isAdaptive();
    _uvSet      = shader->getUVSet();

    int level = (_level < 1) ? 1 : _level;

Get Maya vertex topology and crease data

    MIntArray vertexCount;
    MIntArray vertexList;
    meshFn.getVertices(vertexCount, vertexList);

    MUintArray edgeIds;
    MDoubleArray edgeCreaseData;
    meshFn.getCreaseEdges(edgeIds, edgeCreaseData);

    MUintArray vtxIds;
    MDoubleArray vtxCreaseData;
    meshFn.getCreaseVertices(vtxIds, vtxCreaseData);

    if (vertexCount.length() == 0) return;

Copy Maya vectors into std::vectors

    std::vector<int> numIndices(&vertexCount[0], &vertexCount[vertexCount.length()]);
    std::vector<int> faceIndices(&vertexList[0], &vertexList[vertexList.length()]);
    std::vector<int> vtxCreaseIndices(&vtxIds[0], &vtxIds[vtxIds.length()]);
    std::vector<double> vtxCreases(&vtxCreaseData[0], &vtxCreaseData[vtxCreaseData.length()]);
    std::vector<double> edgeCreases(&edgeCreaseData[0], &edgeCreaseData[edgeCreaseData.length()]);

Edge crease index is stored as pairs of vertex ids

    int nEdgeIds = edgeIds.length();
    std::vector<int> edgeCreaseIndices;
    edgeCreaseIndices.resize(nEdgeIds*2);
    for (int i = 0; i < nEdgeIds; ++i) {
        int2 vertices;
        status = meshFn.getEdgeVertices(edgeIds[i], vertices);
        if (status.error()) {
            MERROR(status, "OpenSubdivShader: Can't get edge vertices");
            continue;
        }
        edgeCreaseIndices[i*2] = vertices[0];
        edgeCreaseIndices[i*2+1] = vertices[1];
    }

Convert attribute enums to HBR enums (this is why the enums need to match)

    HbrMeshUtil::SchemeType hbrScheme = (HbrMeshUtil::SchemeType) shader->getScheme();
    OsdHbrMesh::InterpolateBoundaryMethod hbrInterpBoundary = 
            (OsdHbrMesh::InterpolateBoundaryMethod) shader->getInterpolateBoundary();
    OsdHbrMesh::InterpolateBoundaryMethod hbrInterpUVBoundary = 
            (OsdHbrMesh::InterpolateBoundaryMethod) shader->getInterpolateUVBoundary();

Clear any existing face-varying descriptor

    if (_fvarDesc) {
        delete _fvarDesc;
        _fvarDesc = NULL;
    }

Read UV data from maya and build per-face per-vert list of UVs for HBR face-varying data

    std::vector< float > uvList;
    status = buildUVList( meshFn, uvList );
    if (! status.error()) {

Create face-varying data descriptor. The memory required for indices and widths needs to stay alive as the HBR library only takes in the pointers and assumes the client will maintain the memory so keep _fvarDesc around as long as _hbrmesh is around.

        int fvarIndices[] = { 0, 1 };
        int fvarWidths[] = { 1, 1 };
        _fvarDesc = new FVarDataDesc( 2, fvarIndices, fvarWidths, 2, hbrInterpUVBoundary );
    }

    if (_fvarDesc && hbrScheme != HbrMeshUtil::kCatmark) {
        MGlobal::displayWarning("Face-varying not yet supported for Loop/Bilinear, using Catmull-Clark");
        hbrScheme = HbrMeshUtil::kCatmark;
    }

Convert Maya mesh to internal HBR representation

    _hbrmesh = ConvertToHBR(meshFn.numVertices(), numIndices, faceIndices,
                            vtxCreaseIndices, vtxCreases,
                            std::vector<int>(), std::vector<float>(),
                            edgeCreaseIndices, edgeCreases,
                            hbrInterpBoundary, 
                            hbrScheme,
                            false,                      // no ptex
                            _fvarDesc, 
                            _fvarDesc?&uvList:NULL);    // yes fvar (if have UVs)

    /* note: GL function can't be used in prepareForDraw API. */

Set dirty flag for FAR mesh factory to regenerate

    _needsInitializeMesh = true;

Mesh topology data is up to date

    _meshTopoDirty = false;
    shader->setHbrMeshDirty(false);
}

initializeMesh

dyu - some juicy details about mesh factory and what a compute context is

void
OsdMeshData::initializeMesh() 
{
    if (!_hbrmesh)
        return;

Create FAR mesh

    OpenSubdiv::FarMeshFactory<OpenSubdiv::OsdVertex>
        meshFactory(_hbrmesh, _level, _adaptive);

    _farmesh = meshFactory.Create(false,  /* ptex coords */
                                  true);  /* fvar data */

    delete _hbrmesh;
    _hbrmesh = NULL;

    int numTotalVertices = _farmesh->GetNumVertices();

Create context and vertex buffer

    clearComputeContextAndVertexBuffer();

    if (_kernel == kCPU) {
        _cpuComputeContext = OpenSubdiv::OsdCpuComputeContext::Create(_farmesh);
        _cpuPositionBuffer = OpenSubdiv::OsdCpuGLVertexBuffer::Create(3, numTotalVertices);
#ifdef OPENSUBDIV_HAS_OPENMP
    } else if (_kernel == kOPENMP) {
        _cpuComputeContext = OpenSubdiv::OsdCpuComputeContext::Create(_farmesh);
        _cpuPositionBuffer = OpenSubdiv::OsdCpuGLVertexBuffer::Create(3, numTotalVertices);
#endif
#ifdef OPENSUBDIV_HAS_CUDA
    } else if (_kernel == kCUDA) {
        _cudaComputeContext = OpenSubdiv::OsdCudaComputeContext::Create(_farmesh);
        _cudaPositionBuffer = OpenSubdiv::OsdCudaGLVertexBuffer::Create(3, numTotalVertices);
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
    } else if (_kernel == kCL) {
        _clComputeContext = OpenSubdiv::OsdCLComputeContext::Create(_farmesh, g_clContext);
        _clPositionBuffer = OpenSubdiv::OsdCLGLVertexBuffer::Create(3, numTotalVertices,
                                                                    g_clContext);
#endif
    }

    _needsInitializeMesh = false;

Get geometry from maya mesh

    MFnMesh meshFn(_meshDagPath);
    meshFn.getPoints(_pointArray);

    _needsUpdate = true;
}

createKernelDrawContext

dyu - more juicy details

void
OsdMeshData::createKernelDrawContext() 
{
    delete _drawContext;

    if (_kernel == kCPU) {
        _drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
                                                            _cpuPositionBuffer,
                                                            false,  // ptex coords?
                                                            true);  // fvar data?
#ifdef OPENSUBDIV_HAS_OPENMP
    } else if (_kernel == kOPENMP) {
        _drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
                                                            _cpuPositionBuffer,
                                                            false,  true);
#endif
#ifdef OPENSUBDIV_HAS_CUDA
    } else if (_kernel == kCUDA) {
        _drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
                                                            _cudaPositionBuffer,
                                                            false,  true);
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
    } else if (_kernel == kCL) {
        _drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
                                                            _clPositionBuffer,
                                                            false,  true);
#endif
    } else {
        assert(false);
    }
}

prepare

If HBR mesh has been rebuilt the mesh factory and draw context also need to be rebuilt.

dyu - it seems like this could be merged with rebuildHBR

void
OsdMeshData::prepare() 
{
    if (_needsInitializeMesh) {
        initializeMesh();
        createKernelDrawContext();
    }
}

updateGeometry

Refine the geometry.

dyu - probably need more detail... do they need to know about the bind/subdata copy?

void
OsdMeshData::updateGeometry(const MHWRender::MVertexBuffer *points)
{
    int nCoarsePoints = _pointArray.length();

    GLuint mayaPositionVBO = *static_cast<GLuint*>(points->resourceHandle());
    int size = nCoarsePoints * 3 * sizeof(float);

    if (_kernel == kCPU || _kernel == kOPENMP) {
        float *d_pos = _cpuPositionBuffer->BindCpuBuffer();
        glBindBuffer(GL_ARRAY_BUFFER, mayaPositionVBO);
        glGetBufferSubData(GL_ARRAY_BUFFER, 0, size, d_pos);
        g_cpuComputeController->Refine(_cpuComputeContext, _cpuPositionBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

#ifdef OPENSUBDIV_HAS_CUDA
    } else if (_kernel == kCUDA) {
        glBindBuffer(GL_COPY_READ_BUFFER, mayaPositionVBO);
        glBindBuffer(GL_COPY_WRITE_BUFFER, _cudaPositionBuffer->BindVBO());
        glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
                            0, 0, size);
        g_cudaComputeController->Refine(_cudaComputeContext, _cudaPositionBuffer);

        glBindBuffer(GL_COPY_READ_BUFFER, 0);
        glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
    } else if (_kernel == kCL) {
        glBindBuffer(GL_COPY_READ_BUFFER, mayaPositionVBO);
        glBindBuffer(GL_COPY_WRITE_BUFFER, _clPositionBuffer->BindVBO());
        glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
                            0, 0, size);
        g_clComputeController->Refine(_clComputeContext, _clPositionBuffer);

        glBindBuffer(GL_COPY_READ_BUFFER, 0);
        glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
#endif
    }

    _needsUpdate = false;
}

bindPositionVBO

Wrapper to bind position the correct vertex buffer according to the current compute kernel

GLuint
OsdMeshData::bindPositionVBO() 
{
    if (_kernel == kCPU) {
        return _cpuPositionBuffer->BindVBO();
#ifdef OPENSUBDIV_HAS_OPENMP
    } else if (_kernel == kOPENMP) {
        return _cpuPositionBuffer->BindVBO();
#endif
#ifdef OPENSUBDIV_HAS_CUDA
    } else if (_kernel == kCUDA) {
        return _cudaPositionBuffer->BindVBO();
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
    } else if (_kernel == kCL) {
        return _clPositionBuffer->BindVBO();
#endif
    }
    return 0;
}

--------------------------------------------------------------------------------------

hbrUtil.h

Face-varying data descriptor

 Wrapper for basic information needed to request
 face-varying data allocation from HBR
class FVarDataDesc
{
public:

Must be instantiated with descriptor information

    FVarDataDesc(      int   count, 
                  const int *indices,   // start index for each face-varying variable
                  const int *widths,    // width for each face-varying variable
                        int  width,
                        OsdHbrMesh::InterpolateBoundaryMethod 
                             boundary=OsdHbrMesh::k_InterpolateBoundaryNone
                        ) 
    {
        _fvarCount      = count; 
        _totalfvarWidth = width;
        _fvarIndices.assign( indices, indices+count );
        _fvarWidths.assign( widths, widths+count );
        _interpBoundary = boundary;
    }

    ~FVarDataDesc() {}
Accessors
          int  getCount() const          { return _fvarCount; }
    const int *getIndices() const        { return &_fvarIndices.front(); }
    const int *getWidths() const         { return &_fvarWidths.front(); }
          int  getTotalWidth() const     { return _totalfvarWidth; }
    OsdHbrMesh::InterpolateBoundaryMethod 
               getInterpBoundary() const { return _interpBoundary; }

private:
Face-varying data parameters
    int  _fvarCount;                // Number of facevarying datums
    std::vector<int> _fvarIndices;  // Start indices of the facevarying data 
    std::vector<int> _fvarWidths;   // Individual widths of facevarying data
    int  _totalfvarWidth;           // Total widths of the facevarying data
Boundary interpolation
    OsdHbrMesh::InterpolateBoundaryMethod _interpBoundary;
};


extern "C" OsdHbrMesh * ConvertToHBR( ... );

--------------------------------------------------------------------------------------

hbrUtil.cpp

ConvertToHBR

 Take Maya mesh topology input and build an HBR mesh.  Optional
 input parameters direct the method to create Ptex texture
 coordinates or to copy given face-varying data to the 
 HBR faces as they are created.
OsdHbrMesh * 
ConvertToHBR( int nVertices,
              std::vector<int>   const & faceVertCounts,
              std::vector<int>   const & faceIndices,
              std::vector<int>   const & vtxCreaseIndices,
              std::vector<double> const & vtxCreases,
              std::vector<int>   const & edgeCrease1Indices,  // face index, local edge index
              std::vector<float> const & edgeCreases1,
              std::vector<int>   const & edgeCrease2Indices,  // 2 vertex indices (Maya friendly)
              std::vector<double> const & edgeCreases2,
              OsdHbrMesh::InterpolateBoundaryMethod interpBoundary,
              HbrMeshUtil::SchemeType scheme,
              bool usingPtex,                       // defaults to false
              FVarDataDesc const * fvarDesc,        // defaults to NULL
              std::vector<float> const * fvarData   // defaults to NULL
            )
{
    static OpenSubdiv::HbrBilinearSubdivision<OpenSubdiv::OsdVertex> _bilinear;
    static OpenSubdiv::HbrLoopSubdivision<OpenSubdiv::OsdVertex> _loop;
    static OpenSubdiv::HbrCatmarkSubdivision<OpenSubdiv::OsdVertex> _catmark;

Build HBR mesh with/without face varying data, according to input data. If a face-varying descriptor is passed in its memory needs to stay alive as long as this hbrMesh is alive (for indices and widths arrays).

    OsdHbrMesh *hbrMesh;
    if ( fvarDesc )
    {
        if (scheme == HbrMeshUtil::kCatmark)
            hbrMesh = new OsdHbrMesh(&_catmark,  fvarDesc->getCount(), 
                                                 fvarDesc->getIndices(), 
                                                 fvarDesc->getWidths(), 
                                                 fvarDesc->getTotalWidth());
        else if (scheme == HbrMeshUtil::kLoop)
            hbrMesh = new OsdHbrMesh(&_loop,     fvarDesc->getCount(), 
                                                 fvarDesc->getIndices(), 
                                                 fvarDesc->getWidths(), 
                                                 fvarDesc->getTotalWidth());
        else 
            hbrMesh = new OsdHbrMesh(&_bilinear, fvarDesc->getCount(),
                                                 fvarDesc->getIndices(), 
                                                 fvarDesc->getWidths(), 
                                                 fvarDesc->getTotalWidth());
    }
    else
    {
        if (scheme == HbrMeshUtil::kCatmark)
            hbrMesh = new OsdHbrMesh(&_catmark);
        else if (scheme == HbrMeshUtil::kLoop)
            hbrMesh = new OsdHbrMesh(&_loop);
        else
            hbrMesh = new OsdHbrMesh(&_bilinear);
    }

Create empty verts: actual vertices initialized in UpdatePoints();

    OpenSubdiv::OsdVertex v;
    for (int i = 0; i < nVertices; ++i) {
        hbrMesh->NewVertex(i, v);
    }

    std::vector<int> vIndex;
    int nFaces = (int)faceVertCounts.size();
    int fvcOffset = 0;          // face-vertex count offset
    int ptxIdx = 0;

Collect vertex indices for each face and create HBR face

    for (int fi = 0; fi < nFaces; ++fi) 
    {
        int nFaceVerts = faceVertCounts[fi];
        vIndex.resize(nFaceVerts);

        bool valid = true;
        for (int fvi = 0; fvi < nFaceVerts; ++fvi) 
        {
            vIndex[fvi] = faceIndices[fvi + fvcOffset];
            int vNextIndex = faceIndices[(fvi+1) % nFaceVerts + fvcOffset];

Check for non-manifold face

            OsdHbrVertex * origin = hbrMesh->GetVertex(vIndex[fvi]);
            OsdHbrVertex * destination = hbrMesh->GetVertex(vNextIndex);
            if (!origin || !destination) {
                OSD_ERROR("ERROR : An edge was specified that connected a nonexistent vertex");
                valid = false;
            }

            if (origin == destination) {
                OSD_ERROR("ERROR : An edge was specified that connected a vertex to itself");
                valid = false;
            }

            OsdHbrHalfedge * opposite = destination->GetEdge(origin);
            if (opposite && opposite->GetOpposite()) {
                OSD_ERROR("ERROR : A non-manifold edge incident to more than 2 faces was found");
                valid = false;
            }

            if (origin->GetEdge(destination)) {
                OSD_ERROR("ERROR : An edge connecting two vertices was specified more than once. "
                          "It's likely that an incident face was flipped");
                valid = false;
            }
        }

        if ( valid ) 
        {
            if (scheme == HbrMeshUtil::kLoop) {

For Loop subdivision, triangulate from vertex indices

                int triangle[3];
                triangle[0] = vIndex[0];
                for (int fvi = 2; fvi < nFaceVerts; ++fvi) {
                    triangle[1] = vIndex[fvi-1];
                    triangle[2] = vIndex[fvi];
                    hbrMesh->NewFace(3, triangle, 0);
                }
                /* ptex not fully implemented for loop, yet */
                /* fvar not fully implemented for loop, yet */

            } else {

For Catmull-Clark subdivision, create a quad face from vertices

                /* bilinear subdivision not fully implemented */
                OsdHbrFace *face = hbrMesh->NewFace(nFaceVerts, &(vIndex[0]), 0);

                if (usingPtex) {

Ptex textures will be used, set up ptex coordinates

                    face->SetPtexIndex(ptxIdx);
                    ptxIdx += (nFaceVerts == 4) ? 1 : nFaceVerts;
                }

                if (fvarData) {

Face-varying data has been passed in, get pointer to data

                    int fvarWidth = hbrMesh->GetTotalFVarWidth();
                    const float *faceData = &(*fvarData)[ fvcOffset*fvarWidth ];

For each face vertex copy fvar data into hbr mesh

                    for(int fvi=0; fvi<nFaceVerts; ++fvi)
                    {
                        OsdHbrVertex *v = hbrMesh->GetVertex( vIndex[fvi] );
                        OsdHbrFVarData& fvarData = v->GetFVarData(face);
                        if ( ! fvarData.IsInitialized() )
                        {
                            fvarData.SetAllData( fvarWidth, faceData );
                        }
                        else if (!fvarData.CompareAll(fvarWidth, faceData))
                        {

If data exists for this face vertex, but is different (e.g. we're on a UV seam) create another fvar datum

                            OsdHbrFVarData& fvarData = v->NewFVarData(face);
                            fvarData.SetAllData( fvarWidth, faceData );
                        }

Advance pointer to next set of face-varying data

                        faceData += fvarWidth;
                    }
                }
            }
        } else {
            OSD_ERROR("Face %d will be ignored\n", fi);
        }

        fvcOffset += nFaceVerts;
    }

Assign boundary interpolation methods

    hbrMesh->SetInterpolateBoundaryMethod(interpBoundary);
    if ( fvarDesc ) 
        hbrMesh->SetFVarInterpolateBoundaryMethod(fvarDesc->getInterpBoundary());

Set edge crease in two different indexing way

    size_t nEdgeCreases = edgeCreases1.size();
    for (size_t i = 0; i < nEdgeCreases; ++i) {
        if (edgeCreases1[i] <= 0.0)
            continue;

        OsdHbrHalfedge * e = hbrMesh->
            GetFace(edgeCrease1Indices[i*2])->
            GetEdge(edgeCrease1Indices[i*2+1]);

        if (!e) {
            OSD_ERROR("Can't find edge (face %d edge %d)\n",
                      edgeCrease1Indices[i*2], edgeCrease1Indices[i*2+1]);
            continue;
        }
        e->SetSharpness(static_cast<float>(edgeCreases1[i]));
    }
    nEdgeCreases = edgeCreases2.size();
    for (size_t i = 0; i < nEdgeCreases; ++i) {
        if (edgeCreases2[i] <= 0.0)
            continue;

        OsdHbrVertex * v0 = hbrMesh->GetVertex(edgeCrease2Indices[i*2]);
        OsdHbrVertex * v1 = hbrMesh->GetVertex(edgeCrease2Indices[i*2+1]);
        OsdHbrHalfedge * e = NULL;

        if (v0 && v1)
            if (!(e = v0->GetEdge(v1)))
                e = v1->GetEdge(v0);
        if (!e) {
            OSD_ERROR("ERROR can't find edge");
            continue;
        }
        e->SetSharpness(static_cast<float>(edgeCreases2[i]));
    }

Set corner

    {
        size_t nVertexCreases = vtxCreases.size();
        for (size_t i = 0; i < nVertexCreases; ++i) {
            if (vtxCreases[i] <= 0.0)
                continue;
            OsdHbrVertex * v = hbrMesh->GetVertex(vtxCreaseIndices[i]);
            if (!v) {
                OSD_ERROR("Can't find vertex %d\n", vtxCreaseIndices[i]);
                continue;
            }
            v->SetSharpness(static_cast<float>(vtxCreases[i]));
        }
    }

Call finish to complete build of HBR mesh

    hbrMesh->Finish();

    return hbrMesh;
}

dyu: do we need to have one of these dudes for shader.glsl?