// // 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. // // 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 /* GLEW needs to be included before OSD and Maya headers */ #include /* ... 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 #include // ##### 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 EffectDesc; // // #### Override of OpenSubdiv::OsdGLDrawRegistry // // At the very least this class needs to define _CreateDrawSourceConfig // and _CreateDrawConfig in order to define shader content. // class EffectDrawRegistry : public OpenSubdiv::OsdGLDrawRegistry { 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 SourceConfigType * _CreateDrawSourceConfig(DescType const & desc); // ##### Compile and link the shader virtual ConfigType * _CreateDrawConfig(DescType const & desc, SourceConfigType const * sconfig); 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; }; // // #### _CreateDrawSourceConfig // // 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::SourceConfigType * EffectDrawRegistry::_CreateDrawSourceConfig(DescType const & desc) { Effect effect = desc.second; // Create base draw configuration SourceConfigType * sconfig = BaseRegistry::_CreateDrawSourceConfig(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; sconfig->geometryShader.AddDefine("SMOOTH_NORMALS"); } else { // Configuration for adaptive refinement sconfig->vertexShader.version = "#version 410\n"; sconfig->vertexShader.source = _shaderSource; sconfig->vertexShader.AddDefine("VERTEX_SHADER"); } assert(sconfig); // Enable feature-adaptive face-varying UV generation if (_isAdaptive) { sconfig->geometryShader.AddDefine("FVAR_ADAPTIVE"); } // Enable diffuse texture display if there is a valid texture map if (_diffuseId != 0) { sconfig->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 sconfig->vertexShader.AddDefine("NUM_ELEMENTS", "3"); // Initialize geometry shader sconfig->geometryShader.version = "#version 410\n"; sconfig->geometryShader.source = _shaderSource; sconfig->geometryShader.AddDefine("GEOMETRY_SHADER"); // Initialize fragment shader sconfig->fragmentShader.version = "#version 410\n"; sconfig->fragmentShader.source = _shaderSource; sconfig->fragmentShader.AddDefine("FRAGMENT_SHADER"); // Set up directives according to draw style switch (effect) { case kQuadFill: sconfig->geometryShader.AddDefine("PRIM_QUAD"); sconfig->geometryShader.AddDefine("GEOMETRY_OUT_FILL"); sconfig->fragmentShader.AddDefine("PRIM_QUAD"); sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_FILL"); break; case kQuadLine: sconfig->geometryShader.AddDefine("PRIM_QUAD"); sconfig->geometryShader.AddDefine("GEOMETRY_OUT_LINE"); sconfig->fragmentShader.AddDefine("PRIM_QUAD"); sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_LINE"); break; case kTriFill: sconfig->geometryShader.AddDefine("PRIM_TRI"); sconfig->geometryShader.AddDefine("GEOMETRY_OUT_FILL"); sconfig->fragmentShader.AddDefine("PRIM_TRI"); sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_FILL"); break; case kTriLine: sconfig->geometryShader.AddDefine("PRIM_TRI"); sconfig->geometryShader.AddDefine("GEOMETRY_OUT_LINE"); sconfig->fragmentShader.AddDefine("PRIM_TRI"); sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_LINE"); break; case kPoint: sconfig->geometryShader.AddDefine("PRIM_POINT"); sconfig->fragmentShader.AddDefine("PRIM_POINT"); break; } return sconfig; } // 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; // // #### _CreateDrawConfig // // Called by base registry when a draw config is requested. // Returns a compiled and linked shader program corresponding to // a previously created DrawSourceConfig. The effect registry also // caches these for efficient re-use. // EffectDrawRegistry::ConfigType * EffectDrawRegistry::_CreateDrawConfig( DescType const & desc) SourceConfigType const * sconfig) { ConfigType * config = BaseRegistry::_CreateDrawConfig(desc.first, sconfig); assert(config); // Assign binding points to uniform blocks /* struct Transform */ g_transformBinding = 0; glUniformBlockBinding(config->program, glGetUniformBlockIndex(config->program, "Transform"), g_transformBinding); /* struct Tessellation */ g_tessellationBinding = 1; glUniformBlockBinding(config->program, glGetUniformBlockIndex(config->program, "Tessellation"), g_tessellationBinding); /* struct Lighting */ g_lightingBinding = 2; glUniformBlockBinding(config->program, glGetUniformBlockIndex(config->program, "Lighting"), g_lightingBinding); // Specify texture buffer ID uniforms in shader GLint loc; if ((loc = glGetUniformLocation(config->program, "g_VertexBuffer")) != -1) { glProgramUniform1i(config->program, loc, 0); // GL_TEXTURE0 } if ((loc = glGetUniformLocation(config->program, "g_ValenceBuffer")) != -1) { glProgramUniform1i(config->program, loc, 1); // GL_TEXTURE1 } if ((loc = glGetUniformLocation(config->program, "g_QuadOffsetBuffer")) != -1) { glProgramUniform1i(config->program, loc, 2); // GL_TEXTURE2 } if ((loc = glGetUniformLocation(config->program, "g_patchLevelBuffer")) != -1) { glProgramUniform1i(config->program, loc, 3); // GL_TEXTURE3 } if ((loc = glGetUniformLocation(config->program, "g_uvFVarBuffer")) != -1) { glProgramUniform1i(config->program, loc, 4); // GL_TEXTURE4 } CHECK_GL_ERROR("CreateDrawConfig leave\n"); return config; } // -------------------------------------------------------------------------------------- // ### 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(patch.firstIndex * sizeof(unsigned int))); } else { glDrawElements(_scheme == OsdMeshData::kLoop ? GL_TRIANGLES : GL_LINES_ADJACENCY, patch.numIndices, GL_UNSIGNED_INT, reinterpret_cast(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::ConfigType * config = _effectRegistry.GetDrawConfig(effectDesc); // Install shader GLuint program = config->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 PrimitiveIdBase; } 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.GetQuadOffsetIndex(); tessellationData.PrimitiveIdBase = patch.GetPatchIndex(); 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 /* ... System includes ... */ /* ... Maya includes ... */ // Set up compute controllers for each available compute kernel. // // `dyu - what exactly is a compute controller?` // #include OpenSubdiv::OsdCpuComputeController *g_cpuComputeController = 0; #ifdef OPENSUBDIV_HAS_OPENMP #include OpenSubdiv::OsdOmpComputeController *g_ompComputeController = 0; #endif #ifdef OPENSUBDIV_HAS_OPENCL #include cl_context g_clContext; cl_command_queue g_clQueue; #include "../common/clInit.h" OpenSubdiv::OsdCLComputeController *g_clComputeController = 0; #endif #ifdef OPENSUBDIV_HAS_CUDA #include 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( 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(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(vertexBuffer.acquire(nVertices, true)); #else float *buffer = static_cast(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 #include #include #include #ifdef OPENSUBDIV_HAS_OPENCL #include #include #endif #ifdef OPENSUBDIV_HAS_CUDA #include #include #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 #include extern OpenSubdiv::OsdCpuComputeController *g_cpuComputeController; #ifdef OPENSUBDIV_HAS_OPENMP #include #include extern OpenSubdiv::OsdOmpComputeController *g_ompComputeController; #endif #ifdef OPENSUBDIV_HAS_OPENCL #include #include extern cl_context g_clContext; extern cl_command_queue g_clQueue; extern OpenSubdiv::OsdCLComputeController *g_clComputeController; #endif #ifdef OPENSUBDIV_HAS_CUDA #include #include extern OpenSubdiv::OsdCudaComputeController *g_cudaComputeController; #endif #include // // #### 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& 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 numIndices(&vertexCount[0], &vertexCount[vertexCount.length()]); std::vector faceIndices(&vertexList[0], &vertexList[vertexList.length()]); std::vector vtxCreaseIndices(&vtxIds[0], &vtxIds[vtxIds.length()]); std::vector vtxCreases(&vtxCreaseData[0], &vtxCreaseData[vtxCreaseData.length()]); std::vector edgeCreases(&edgeCreaseData[0], &edgeCreaseData[edgeCreaseData.length()]); // Edge crease index is stored as pairs of vertex ids int nEdgeIds = edgeIds.length(); std::vector 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(), std::vector(), 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 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(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 _fvarIndices; // Start indices of the facevarying data std::vector _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 const & faceVertCounts, std::vector const & faceIndices, std::vector const & vtxCreaseIndices, std::vector const & vtxCreases, std::vector const & edgeCrease1Indices, // face index, local edge index std::vector const & edgeCreases1, std::vector const & edgeCrease2Indices, // 2 vertex indices (Maya friendly) std::vector const & edgeCreases2, OsdHbrMesh::InterpolateBoundaryMethod interpBoundary, HbrMeshUtil::SchemeType scheme, bool usingPtex, // defaults to false FVarDataDesc const * fvarDesc, // defaults to NULL std::vector const * fvarData // defaults to NULL ) { static OpenSubdiv::HbrBilinearSubdivision _bilinear; static OpenSubdiv::HbrLoopSubdivision _loop; static OpenSubdiv::HbrCatmarkSubdivision _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 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; fviGetVertex( 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(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(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(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?`