// Created on: 2012-04-09 // Created by: Sergey ANIKIN // Copyright (c) 2012-2014 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern Standard_Boolean VDisplayAISObject (const TCollection_AsciiString& theName, const Handle(AIS_InteractiveObject)& theAISObj, Standard_Boolean theReplaceIfExists = Standard_True); extern ViewerTest_DoubleMapOfInteractiveAndName& GetMapOfAIS(); namespace { //======================================================================= //function : VUserDraw //purpose : Checks availability and operation of UserDraw feature //======================================================================= class VUserDrawObj : public AIS_InteractiveObject { public: // CASCADE RTTI DEFINE_STANDARD_RTTI_INLINE(VUserDrawObj,AIS_InteractiveObject); VUserDrawObj() { myCoords[0] = -10.; myCoords[1] = -20.; myCoords[2] = -30.; myCoords[3] = 10.; myCoords[4] = 20.; myCoords[5] = 30.; } public: class Element : public OpenGl_Element { private: Handle(VUserDrawObj) myIObj; public: Element (const Handle(VUserDrawObj)& theIObj) : myIObj (theIObj) {} virtual ~Element() {} virtual void Render (const Handle(OpenGl_Workspace)& theWorkspace) const { if (!myIObj.IsNull()) myIObj->Render(theWorkspace); } virtual void Release (OpenGl_Context*) { // } public: DEFINE_STANDARD_ALLOC }; private: // Virtual methods implementation void Compute (const Handle(PrsMgr_PresentationManager3d)& thePresentationManager, const Handle(Prs3d_Presentation)& thePresentation, const Standard_Integer theMode) Standard_OVERRIDE; void ComputeSelection (const Handle(SelectMgr_Selection)& theSelection, const Standard_Integer theMode) Standard_OVERRIDE; // Called by VUserDrawElement void Render(const Handle(OpenGl_Workspace)& theWorkspace) const; private: GLfloat myCoords[6]; friend class Element; }; void VUserDrawObj::Compute(const Handle(PrsMgr_PresentationManager3d)& thePrsMgr, const Handle(Prs3d_Presentation)& thePrs, const Standard_Integer /*theMode*/) { thePrs->Clear(); Graphic3d_Vec4 aBndMin (myCoords[0], myCoords[1], myCoords[2], 1.0f); Graphic3d_Vec4 aBndMax (myCoords[3], myCoords[4], myCoords[5], 1.0f); Handle(OpenGl_Group) aGroup = Handle(OpenGl_Group)::DownCast (thePrs->NewGroup()); aGroup->SetMinMaxValues (aBndMin.x(), aBndMin.y(), aBndMin.z(), aBndMax.x(), aBndMax.y(), aBndMax.z()); aGroup->SetGroupPrimitivesAspect (myDrawer->LineAspect()->Aspect()); VUserDrawObj::Element* anElem = new VUserDrawObj::Element (this); aGroup->AddElement(anElem); // invalidate bounding box of the scene thePrsMgr->StructureManager()->Update(); } void VUserDrawObj::ComputeSelection (const Handle(SelectMgr_Selection)& theSelection, const Standard_Integer /*theMode*/) { Handle(SelectMgr_EntityOwner) anEntityOwner = new SelectMgr_EntityOwner(this); Handle(TColgp_HArray1OfPnt) aPnts = new TColgp_HArray1OfPnt(1, 5); aPnts->SetValue(1, gp_Pnt(myCoords[0], myCoords[1], myCoords[2])); aPnts->SetValue(2, gp_Pnt(myCoords[3], myCoords[4], myCoords[2])); aPnts->SetValue(3, gp_Pnt(myCoords[3], myCoords[4], myCoords[5])); aPnts->SetValue(4, gp_Pnt(myCoords[0], myCoords[1], myCoords[5])); aPnts->SetValue(5, gp_Pnt(myCoords[0], myCoords[1], myCoords[2])); Handle(Select3D_SensitiveCurve) aSensitive = new Select3D_SensitiveCurve(anEntityOwner, aPnts); theSelection->Add(aSensitive); } void VUserDrawObj::Render(const Handle(OpenGl_Workspace)& theWorkspace) const { // this sample does not use GLSL programs - make sure it is disabled Handle(OpenGl_Context) aCtx = theWorkspace->GetGlContext(); aCtx->BindProgram (Handle(OpenGl_ShaderProgram)()); aCtx->ShaderManager()->PushState (Handle(OpenGl_ShaderProgram)()); // To test linking against OpenGl_Workspace and all aspect classes const OpenGl_AspectMarker* aMA = theWorkspace->AspectMarker(); aMA->Aspect()->Type(); const OpenGl_AspectText* aTA = theWorkspace->AspectText(); aTA->Aspect()->Font(); OpenGl_Vec4 aColor = theWorkspace->LineColor(); #if !defined(GL_ES_VERSION_2_0) // Finally draw something to make sure UserDraw really works glPushAttrib(GL_ENABLE_BIT); glDisable(GL_LIGHTING); glColor4fv(aColor.GetData()); glBegin(GL_LINE_LOOP); glVertex3f(myCoords[0], myCoords[1], myCoords[2]); glVertex3f(myCoords[3], myCoords[4], myCoords[2]); glVertex3f(myCoords[3], myCoords[4], myCoords[5]); glVertex3f(myCoords[0], myCoords[1], myCoords[5]); glEnd(); glPopAttrib(); #endif } } // end of anonymous namespace static Standard_Integer VUserDraw (Draw_Interpretor& di, Standard_Integer argc, const char ** argv) { Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext(); if (aContext.IsNull()) { di << argv[0] << "Call 'vinit' before!\n"; return 1; } Handle(OpenGl_GraphicDriver) aDriver = Handle(OpenGl_GraphicDriver)::DownCast (aContext->CurrentViewer()->Driver()); if (aDriver.IsNull()) { std::cerr << "Graphic driver not available.\n"; return 1; } if (argc > 2) { di << argv[0] << "Wrong number of arguments, only the object name expected\n"; return 1; } TCollection_AsciiString aName (argv[1]); VDisplayAISObject(aName, Handle(AIS_InteractiveObject)()); Handle(VUserDrawObj) anIObj = new VUserDrawObj(); VDisplayAISObject(aName, anIObj); return 0; } //============================================================================== //function : VFeedback //purpose : //============================================================================== static int VFeedback (Draw_Interpretor& theDI, Standard_Integer /*theArgNb*/, const char** /*theArgVec*/) { #if !defined(GL_ES_VERSION_2_0) // get the active view Handle(V3d_View) aView = ViewerTest::CurrentView(); if (aView.IsNull()) { std::cerr << "No active view. Please call vinit.\n"; return 1; } unsigned int aBufferSize = 1024 * 1024; for (;;) { size_t aBytes = (size_t )aBufferSize * sizeof(GLfloat); if (aBytes / sizeof(GLfloat) != (size_t )aBufferSize) { // finito la commedia std::cerr << "Can not allocate buffer - requested size (" << (double(aBufferSize / (1024 * 1024)) * double(sizeof(GLfloat))) << " MiB) is out of address space\n"; return 1; } GLfloat* aBuffer = (GLfloat* )Standard::Allocate (aBytes); if (aBuffer == NULL) { // finito la commedia std::cerr << "Can not allocate buffer with size (" << (double(aBufferSize / (1024 * 1024)) * double(sizeof(GLfloat))) << " MiB)\n"; return 1; } glFeedbackBuffer ((GLsizei )aBufferSize, GL_2D, aBuffer); glRenderMode (GL_FEEDBACK); aView->Redraw(); GLint aResult = glRenderMode (GL_RENDER); if (aResult < 0) { aBufferSize *= 2; void* aPtr = aBuffer; Standard::Free (aPtr); aBuffer = NULL; continue; } std::cout << "FeedBack result= " << aResult << "\n"; GLint aPntNb = 0; GLint aTriNb = 0; GLint aQuadsNb = 0; GLint aPolyNb = 0; GLint aNodesNb = 0; GLint aLinesNb = 0; GLint aBitmapsNb = 0; GLint aPassThrNb = 0; GLint aUnknownNb = 0; const GLint NODE_VALUES = 2; // GL_2D for (GLint anIter = 0; anIter < aResult;) { const GLfloat aPos = aBuffer[anIter]; switch ((GLint )aPos) { case GL_POINT_TOKEN: { ++aPntNb; ++aNodesNb; anIter += 1 + NODE_VALUES; break; } case GL_LINE_RESET_TOKEN: case GL_LINE_TOKEN: { ++aLinesNb; aNodesNb += 2; anIter += 1 + 2 * NODE_VALUES; break; } case GL_POLYGON_TOKEN: { const GLint aCount = (GLint )aBuffer[++anIter]; aNodesNb += aCount; anIter += aCount * NODE_VALUES + 1; if (aCount == 3) { ++aTriNb; } else if (aCount == 4) { ++aQuadsNb; } else { ++aPolyNb; } break; } case GL_BITMAP_TOKEN: case GL_DRAW_PIXEL_TOKEN: case GL_COPY_PIXEL_TOKEN: { ++aBitmapsNb; anIter += 1 + NODE_VALUES; break; } case GL_PASS_THROUGH_TOKEN: { ++aPassThrNb; anIter += 2; // header + value break; } default: { ++anIter; ++aUnknownNb; break; } } } void* aPtr = aBuffer; Standard::Free (aPtr); // return statistics theDI << "Total nodes: " << aNodesNb << "\n" << "Points: " << aPntNb << "\n" << "Line segments: " << aLinesNb << "\n" << "Triangles: " << aTriNb << "\n" << "Quads: " << aQuadsNb << "\n" << "Polygons: " << aPolyNb << "\n" << "Bitmap tokens: " << aBitmapsNb << "\n" << "Pass through: " << aPassThrNb << "\n" << "UNKNOWN: " << aUnknownNb << "\n"; double aLen2D = double(aNodesNb * 2 + aPntNb + aLinesNb * 2 + (aTriNb + aQuadsNb + aPolyNb) * 2 + aBitmapsNb + aPassThrNb); double aLen3D = double(aNodesNb * 3 + aPntNb + aLinesNb * 2 + (aTriNb + aQuadsNb + aPolyNb) * 2 + aBitmapsNb + aPassThrNb); double aLen3D_rgba = double(aNodesNb * 7 + aPntNb + aLinesNb * 2 + (aTriNb + aQuadsNb + aPolyNb) * 2 + aBitmapsNb + aPassThrNb); theDI << "Buffer size GL_2D: " << aLen2D * double(sizeof(GLfloat)) / double(1024 * 1024) << " MiB\n" << "Buffer size GL_3D: " << aLen3D * double(sizeof(GLfloat)) / double(1024 * 1024) << " MiB\n" << "Buffer size GL_3D_COLOR: " << aLen3D_rgba * double(sizeof(GLfloat)) / double(1024 * 1024) << " MiB\n"; return 0; } #else (void )theDI; std::cout << "Command is unsupported on current platform.\n"; return 1; #endif } //============================================================================== //function : VImmediateFront //purpose : //============================================================================== static int VImmediateFront (Draw_Interpretor& /*theDI*/, Standard_Integer theArgNb, const char** theArgVec) { // get the context Handle(AIS_InteractiveContext) aContextAIS = ViewerTest::GetAISContext(); if (aContextAIS.IsNull()) { std::cerr << "No active view. Please call vinit.\n"; return 1; } Handle(Graphic3d_GraphicDriver) aDriver = aContextAIS->CurrentViewer()->Driver(); if (aDriver.IsNull()) { std::cerr << "Graphic driver not available.\n"; return 1; } if (theArgNb < 2) { std::cerr << "Wrong number of arguments.\n"; return 1; } ViewerTest::CurrentView()->View()->SetImmediateModeDrawToFront (atoi(theArgVec[1]) != 0); return 0; } //! Search the info from the key. inline TCollection_AsciiString searchInfo (const TColStd_IndexedDataMapOfStringString& theDict, const TCollection_AsciiString& theKey) { for (TColStd_IndexedDataMapOfStringString::Iterator anIter (theDict); anIter.More(); anIter.Next()) { if (TCollection_AsciiString::IsSameString (anIter.Key(), theKey, Standard_False)) { return anIter.Value(); } } return TCollection_AsciiString(); } //============================================================================== //function : VGlInfo //purpose : //============================================================================== static int VGlInfo (Draw_Interpretor& theDI, Standard_Integer theArgNb, const char** theArgVec) { // get the active view Handle(V3d_View) aView = ViewerTest::CurrentView(); if (aView.IsNull()) { std::cerr << "No active view. Please call vinit.\n"; return 1; } Standard_Integer anArgIter = 1; Graphic3d_DiagnosticInfo anInfoLevel = Graphic3d_DiagnosticInfo_Basic; if (theArgNb == 2) { TCollection_AsciiString aName (theArgVec[1]); aName.LowerCase(); if (aName == "-short") { ++anArgIter; anInfoLevel = Graphic3d_DiagnosticInfo_Short; } else if (aName == "-basic") { ++anArgIter; anInfoLevel = Graphic3d_DiagnosticInfo_Basic; } else if (aName == "-complete" || aName == "-full") { ++anArgIter; anInfoLevel = Graphic3d_DiagnosticInfo_Complete; } } TColStd_IndexedDataMapOfStringString aDict; if (anArgIter >= theArgNb) { aView->DiagnosticInformation (aDict, anInfoLevel); TCollection_AsciiString aText; for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aDict); aValueIter.More(); aValueIter.Next()) { if (!aText.IsEmpty()) { aText += "\n"; } aText += TCollection_AsciiString(" ") + aValueIter.Key() + ": " + aValueIter.Value(); } theDI << "OpenGL info:\n" << aText; return 0; } const Standard_Boolean isList = theArgNb >= 3; aView->DiagnosticInformation (aDict, Graphic3d_DiagnosticInfo_Complete); for (; anArgIter < theArgNb; ++anArgIter) { TCollection_AsciiString aName (theArgVec[anArgIter]); aName.UpperCase(); TCollection_AsciiString aValue; if (aName.Search ("VENDOR") != -1) { aValue = searchInfo (aDict, "GLvendor"); } else if (aName.Search ("RENDERER") != -1) { aValue = searchInfo (aDict, "GLdevice"); } else if (aName.Search ("SHADING_LANGUAGE_VERSION") != -1 || aName.Search ("GLSL") != -1) { aValue = searchInfo (aDict, "GLSLversion"); } else if (aName.Search ("VERSION") != -1) { aValue = searchInfo (aDict, "GLversion"); } else if (aName.Search ("EXTENSIONS") != -1) { aValue = searchInfo (aDict, "GLextensions"); } else { std::cerr << "Unknown key '" << aName.ToCString() << "'\n"; return 1; } if (isList) { theDI << "{" << aValue << "} "; } else { theDI << aValue; } } return 0; } //! Parse shader type argument. static bool parseShaderTypeArg (Graphic3d_TypeOfShaderObject& theType, const TCollection_AsciiString& theArg) { if (theArg == "-vertex" || theArg == "-vert") { theType = Graphic3d_TOS_VERTEX; } else if (theArg == "-tessevaluation" || theArg == "-tesseval" || theArg == "-evaluation" || theArg == "-eval") { theType = Graphic3d_TOS_TESS_EVALUATION; } else if (theArg == "-tesscontrol" || theArg == "-tessctrl" || theArg == "-control" || theArg == "-ctrl") { theType = Graphic3d_TOS_TESS_CONTROL; } else if (theArg == "-geometry" || theArg == "-geom") { theType = Graphic3d_TOS_GEOMETRY; } else if (theArg == "-fragment" || theArg == "-frag") { theType = Graphic3d_TOS_FRAGMENT; } else if (theArg == "-compute" || theArg == "-comp") { theType = Graphic3d_TOS_COMPUTE; } else { return false; } return true; } //============================================================================== //function : VShaderProg //purpose : Sets the pair of vertex and fragment shaders for the object //============================================================================== static Standard_Integer VShaderProg (Draw_Interpretor& /*theDI*/, Standard_Integer theArgNb, const char** theArgVec) { Handle(AIS_InteractiveContext) aCtx = ViewerTest::GetAISContext(); if (aCtx.IsNull()) { std::cout << "Error: no active view.\n"; return 1; } else if (theArgNb < 2) { std::cout << "Syntax error: lack of arguments\n"; return 1; } bool isExplicitShaderType = false; Handle(Graphic3d_ShaderProgram) aProgram = new Graphic3d_ShaderProgram(); NCollection_Sequence aPrsList; Graphic3d_GroupAspect aGroupAspect = Graphic3d_ASPECT_FILL_AREA; bool isSetGroupAspect = false; for (Standard_Integer anArgIter = 1; anArgIter < theArgNb; ++anArgIter) { TCollection_AsciiString anArg (theArgVec[anArgIter]); anArg.LowerCase(); Graphic3d_TypeOfShaderObject aShaderTypeArg = Graphic3d_TypeOfShaderObject(-1); if (!aProgram.IsNull() && aProgram->ShaderObjects().IsEmpty() && (anArg == "-off" || anArg == "off")) { aProgram.Nullify(); } else if (!aProgram.IsNull() && aProgram->ShaderObjects().IsEmpty() && (anArg == "-phong" || anArg == "phong")) { const TCollection_AsciiString& aShadersRoot = Graphic3d_ShaderProgram::ShadersFolder(); if (aShadersRoot.IsEmpty()) { std::cout << "Error: both environment variables CSF_ShadersDirectory and CASROOT are undefined!\n" "At least one should be defined to load Phong program.\n"; return 1; } const TCollection_AsciiString aSrcVert = aShadersRoot + "/PhongShading.vs"; const TCollection_AsciiString aSrcFrag = aShadersRoot + "/PhongShading.fs"; if (!aSrcVert.IsEmpty() && !OSD_File (aSrcVert).Exists()) { std::cout << "Error: PhongShading.vs is not found\n"; return 1; } if (!aSrcFrag.IsEmpty() && !OSD_File (aSrcFrag).Exists()) { std::cout << "Error: PhongShading.fs is not found\n"; return 1; } aProgram->AttachShader (Graphic3d_ShaderObject::CreateFromFile (Graphic3d_TOS_VERTEX, aSrcVert)); aProgram->AttachShader (Graphic3d_ShaderObject::CreateFromFile (Graphic3d_TOS_FRAGMENT, aSrcFrag)); } else if (aPrsList.IsEmpty() && anArg == "*") { // } else if (!isSetGroupAspect && anArgIter + 1 < theArgNb && (anArg == "-primtype" || anArg == "-primitivetype" || anArg == "-groupaspect" || anArg == "-aspecttype" || anArg == "-aspect")) { isSetGroupAspect = true; TCollection_AsciiString aPrimTypeStr (theArgVec[++anArgIter]); aPrimTypeStr.LowerCase(); if (aPrimTypeStr == "line") { aGroupAspect = Graphic3d_ASPECT_LINE; } else if (aPrimTypeStr == "tris" || aPrimTypeStr == "triangles" || aPrimTypeStr == "fill" || aPrimTypeStr == "fillarea" || aPrimTypeStr == "shading" || aPrimTypeStr == "shade") { aGroupAspect = Graphic3d_ASPECT_FILL_AREA; } else if (aPrimTypeStr == "text") { aGroupAspect = Graphic3d_ASPECT_TEXT; } else if (aPrimTypeStr == "marker" || aPrimTypeStr == "point" || aPrimTypeStr == "pnt") { aGroupAspect = Graphic3d_ASPECT_MARKER; } else { std::cerr << "Syntax error at '" << aPrimTypeStr << "'\n"; return 1; } } else if (anArgIter + 1 < theArgNb && !aProgram.IsNull() && aProgram->Header().IsEmpty() && (anArg == "-version" || anArg == "-glslversion" || anArg == "-header" || anArg == "-glslheader")) { TCollection_AsciiString aHeader (theArgVec[++anArgIter]); if (aHeader.IsIntegerValue()) { aHeader = TCollection_AsciiString ("#version ") + aHeader; } aProgram->SetHeader (aHeader); } else if (!anArg.StartsWith ("-") && GetMapOfAIS().IsBound2 (theArgVec[anArgIter])) { Handle(AIS_InteractiveObject) anIO = Handle(AIS_InteractiveObject)::DownCast (GetMapOfAIS().Find2 (theArgVec[anArgIter])); if (anIO.IsNull()) { std::cerr << "Syntax error: " << theArgVec[anArgIter] << " is not an AIS object\n"; return 1; } aPrsList.Append (anIO); } else if (!aProgram.IsNull() && ((anArgIter + 1 < theArgNb && parseShaderTypeArg (aShaderTypeArg, anArg)) || (!isExplicitShaderType && aProgram->ShaderObjects().Size() < 2))) { TCollection_AsciiString aShaderPath (theArgVec[anArgIter]); if (aShaderTypeArg != Graphic3d_TypeOfShaderObject(-1)) { aShaderPath = (theArgVec[++anArgIter]); isExplicitShaderType = true; } const bool isSrcFile = OSD_File (aShaderPath).Exists(); Handle(Graphic3d_ShaderObject) aShader = isSrcFile ? Graphic3d_ShaderObject::CreateFromFile (Graphic3d_TOS_VERTEX, aShaderPath) : Graphic3d_ShaderObject::CreateFromSource(Graphic3d_TOS_VERTEX, aShaderPath); const TCollection_AsciiString& aShaderSrc = aShader->Source(); const bool hasVertPos = aShaderSrc.Search ("gl_Position") != -1; const bool hasFragColor = aShaderSrc.Search ("occFragColor") != -1 || aShaderSrc.Search ("gl_FragColor") != -1 || aShaderSrc.Search ("gl_FragData") != -1; Graphic3d_TypeOfShaderObject aShaderType = aShaderTypeArg; if (aShaderType == Graphic3d_TypeOfShaderObject(-1)) { if (hasVertPos && !hasFragColor) { aShaderType = Graphic3d_TOS_VERTEX; } if (hasFragColor && !hasVertPos) { aShaderType = Graphic3d_TOS_FRAGMENT; } } if (aShaderType == Graphic3d_TypeOfShaderObject(-1)) { std::cerr << "Error: non-existing or invalid shader source\n"; return 1; } aProgram->AttachShader (Graphic3d_ShaderObject::CreateFromSource (aShaderType, aShaderSrc)); } else { std::cerr << "Syntax error at '" << anArg << "'\n"; return 1; } } ViewerTest_DoubleMapIteratorOfDoubleMapOfInteractiveAndName aGlobalPrsIter (GetMapOfAIS()); NCollection_Sequence::Iterator aPrsIter (aPrsList); const bool isGlobalList = aPrsList.IsEmpty(); for (;;) { Handle(AIS_InteractiveObject) anIO; if (isGlobalList) { if (!aGlobalPrsIter.More()) { break; } anIO = Handle(AIS_InteractiveObject)::DownCast (aGlobalPrsIter.Key1()); aGlobalPrsIter.Next(); if (anIO.IsNull()) { continue; } } else { if (!aPrsIter.More()) { break; } anIO = aPrsIter.Value(); aPrsIter.Next(); } if (anIO->Attributes()->SetShaderProgram (aProgram, aGroupAspect, true)) { aCtx->Redisplay (anIO, Standard_False); } else { anIO->SynchronizeAspects(); } } aCtx->UpdateCurrentViewer(); return 0; } //======================================================================= //function : OpenGlCommands //purpose : //======================================================================= void ViewerTest::OpenGlCommands(Draw_Interpretor& theCommands) { const char* aGroup ="Commands for low-level TKOpenGl features"; theCommands.Add("vuserdraw", "vuserdraw : name - simulates drawing with help of UserDraw", __FILE__, VUserDraw, aGroup); theCommands.Add("vfeedback", "vfeedback : perform test GL feedback rendering", __FILE__, VFeedback, aGroup); theCommands.Add("vimmediatefront", "vimmediatefront : render immediate mode to front buffer or to back buffer", __FILE__, VImmediateFront, aGroup); theCommands.Add("vglinfo", "vglinfo [-short|-basic|-complete]" "\n\t\t: [GL_VENDOR] [GL_RENDERER] [GL_VERSION]" "\n\t\t: [GL_SHADING_LANGUAGE_VERSION] [GL_EXTENSIONS]" "\n\t\t: print OpenGL info", __FILE__, VGlInfo, aGroup); theCommands.Add("vshader", "vshader name -vert VertexShader -frag FragmentShader [-geom GeometryShader]" "\n\t\t: [-off] [-phong] [-aspect {shading|line|point|text}=shading]" "\n\t\t: [-header VersionHeader]" "\n\t\t: [-tessControl TessControlShader -tesseval TessEvaluationShader]" "\n\t\t: Assign custom GLSL program to presentation aspects.", __FILE__, VShaderProg, aGroup); theCommands.Add("vshaderprog", "Alias for vshader", __FILE__, VShaderProg, aGroup); }