使用Visual Studio SDK制作GLSL词法着色插件

使用Visual Studio SDK制作GLSL词法着色插件


我们在Visual Studio上开发OpenGL ES项目时,避免不了写Shader。这时在vs里直接编辑shader就会显得很方便。但是vs默认是不支持GLSL的语法着色的,我们只好自己动手创造。最简单的实现自定义语法着色的方法就是创建一个VSIX插件包,我们只需要安装Visual Studio SDK,使用内置的模版就可以构建一个插件项目。

1. 安装Visual Studio SDK 


http://www.microsoft.com/en-us/download/details.aspx?id=40758下载最新的Visual Studio 2013 SDK。

双击安装,一路next即可。

安装完毕后我们可以在新建项目->模版->C#中看到“扩展性”这一条目,这些就是开发插件用的模版了。

2. 创建插件项目


新建项目,在扩展性标签中,选择Editor Classifier模版,命名为ShaderEditor,点击确定。

Visual Studio为我们生成了如下几个文件。

使用Visual Studio SDK制作GLSL词法着色插件

ShaderEditorFormat.cs文件的默认代码如下: 

 1     [Export(typeof(EditorFormatDefinition))]  2     [ClassificationType(ClassificationTypeNames = "ShaderEditor")]  3     [Name("ShaderEditor")]  4     [UserVisible(true)] //this should be visible to the end user

 5     [Order(Before = Priority.Default)] //set the priority to be after the default classifiers

 6     internal sealed class ShaderEditorFormat : ClassificationFormatDefinition {  7         /// <summary>

 8         /// Defines the visual format for the "ShaderEditor" classification type  9         /// </summary>

10         public ShaderEditorFormat() { 11             this.DisplayName = "ShaderEditor"; //human readable version of the name

12             this.BackgroundColor = Colors.BlueViolet; 13             this.TextDecorations = System.Windows.TextDecorations.Underline; 14  } 15     }

 

这段代码定义了一个名为"ShaderEditor"的着色类型,编译工程并运行,我们可以在Visual Studio实验实例的工具->选项->字体和颜色中找到一个名为"ShaderEditor"的条目。同时我们会发现所有文本文件的颜色都变成了Colors.BlueViolet并带上了下划线。修改this.DisplayName = "ShaderEditor"的内容,可以改变在字体和颜色中显示的名字。下面的格式设置可以任意修改成喜欢的样式,但要注意在这里的格式只是插件首次安装时的默认设置,这些条目和其它着色选项一样,都可以被用户任意更改。

3. 创建GLSL的着色类型
 
  
我们已经了解了如何将着色类型添加到Visual Studio,现在修改ShaderEditorFormat.cs,添加我们的着色类型。
 1     [Export(typeof(EditorFormatDefinition))]  2     [ClassificationType(ClassificationTypeNames = "GLSLText")]  3     [Name("GLSLText")]  4     [UserVisible(true)]  5     [Order(Before = Priority.Default)]  6     internal sealed class GLSLTextFormatDefinition : ClassificationFormatDefinition {  7         public GLSLTextFormatDefinition() {  8             this.DisplayName = "GLSL文本";  9             this.ForegroundColor = Colors.Brown; 10  } 11  } 12 

13     [Export(typeof(EditorFormatDefinition))] 14     [ClassificationType(ClassificationTypeNames = "GLSLIdentifier")] 15     [Name("GLSLIdentifier")] 16     [UserVisible(true)] 17     [Order(Before = Priority.Default)] 18     internal sealed class GLSLIdentifierFormatDefinition : ClassificationFormatDefinition { 19         public GLSLIdentifierFormatDefinition() { 20             this.DisplayName = "GLSL标识符"; 21             this.ForegroundColor = Colors.Brown; 22  } 23  } 24 

25     [Export(typeof(EditorFormatDefinition))] 26     [ClassificationType(ClassificationTypeNames = "GLSLComment")] 27     [Name("GLSLComment")] 28     [UserVisible(true)] 29     [Order(Before = Priority.Default)] 30     internal sealed class GLSLCommentFormatDefinition : ClassificationFormatDefinition { 31         public GLSLCommentFormatDefinition() { 32             this.DisplayName = "GLSL注释"; 33             this.ForegroundColor = Colors.DarkGray; 34  } 35  } 36 

37     [Export(typeof(EditorFormatDefinition))] 38     [ClassificationType(ClassificationTypeNames = "GLSLKeyword")] 39     [Name("GLSLKeyword")] 40     [UserVisible(true)] 41     [Order(Before = Priority.Default)] 42     internal sealed class GLSLKeywordFormatDefinition : ClassificationFormatDefinition { 43         public GLSLKeywordFormatDefinition() { 44             this.DisplayName = "GLSL关键字"; 45             this.ForegroundColor = Colors.Blue; 46  } 47  } 48 

49     [Export(typeof(EditorFormatDefinition))] 50     [ClassificationType(ClassificationTypeNames = "GLSLClass")] 51     [Name("GLSLClass")] 52     [UserVisible(true)] 53     [Order(Before = Priority.Default)] 54     internal sealed class GLSLClassFormatDefinition : ClassificationFormatDefinition { 55         public GLSLClassFormatDefinition() { 56             this.DisplayName = "GLSL类型"; 57             this.ForegroundColor = Colors.Green; 58  } 59  } 60 

61     [Export(typeof(EditorFormatDefinition))] 62     [ClassificationType(ClassificationTypeNames = "GLSLQualifier")] 63     [Name("GLSLQualifier")] 64     [UserVisible(true)] 65     [Order(Before = Priority.Default)] 66     internal sealed class GLSLQualifierFormatDefinition : ClassificationFormatDefinition { 67         public GLSLQualifierFormatDefinition() { 68             this.DisplayName = "GLSL限定符"; 69             this.ForegroundColor = Colors.Pink; 70  } 71  } 72 

73     [Export(typeof(EditorFormatDefinition))] 74     [ClassificationType(ClassificationTypeNames = "GLSLVariable")] 75     [Name("GLSLVariable")] 76     [UserVisible(true)] 77     [Order(Before = Priority.Default)] 78     internal sealed class GLSLVariableFormatDefinition : ClassificationFormatDefinition { 79         public GLSLVariableFormatDefinition() { 80             this.DisplayName = "GLSL系统变量"; 81             this.ForegroundColor = Colors.DarkOrange; 82  } 83  } 84 

85     [Export(typeof(EditorFormatDefinition))] 86     [ClassificationType(ClassificationTypeNames = "GLSLFunction")] 87     [Name("GLSLFunction")] 88     [UserVisible(true)] 89     [Order(Before = Priority.Default)] 90     internal sealed class GLSLFunctionFormatDefinition : ClassificationFormatDefinition { 91         public GLSLFunctionFormatDefinition() { 92             this.DisplayName = "GLSL系统函数"; 93             this.ForegroundColor = Colors.DarkTurquoise; 94  } 95     }

 

4. 导出着色类型


Editor Classifier使用了MEF框架,关于MEF的具体细节,请参考MSDN的相关文档。

我们需要注意的是,在MEF中,光定义了着色类型还不够,我们需要导出一个ClassificationTypeDefinition,才能在系统中生效。

打开ShaderEditorType.cs,我们看到系统生成的代码如下:

1     internal static class ShaderEditorClassificationDefinition { 2         [Export(typeof(ClassificationTypeDefinition))] 3         [Name("ShaderEditor")] 4         internal static ClassificationTypeDefinition ShaderEditorType = null; 5     }

这里的Name与之前默认生成的ShaderEditor相同,同理,我们将这里的代码修改成方才定义的类型

 1     internal static class ShaderEditorClassificationDefinition {  2         [Export(typeof(ClassificationTypeDefinition))]  3         [Name("GLSLText")]  4         internal static ClassificationTypeDefinition GLSLTextType = null;  5 

 6         [Export(typeof(ClassificationTypeDefinition))]  7         [Name("GLSLIdentifier")]  8         internal static ClassificationTypeDefinition GLSLIdentifierType = null;  9 

10         [Export(typeof(ClassificationTypeDefinition))] 11         [Name("GLSLComment")] 12         internal static ClassificationTypeDefinition GLSLCommentType = null; 13 

14         [Export(typeof(ClassificationTypeDefinition))] 15         [Name("GLSLKeyword")] 16         internal static ClassificationTypeDefinition GLSLKeywordType = null; 17 

18         [Export(typeof(ClassificationTypeDefinition))] 19         [Name("GLSLClass")] 20         internal static ClassificationTypeDefinition GLSLClassType = null; 21 

22         [Export(typeof(ClassificationTypeDefinition))] 23         [Name("GLSLQualifier")] 24         internal static ClassificationTypeDefinition GLSLQualifierType = null; 25 

26         [Export(typeof(ClassificationTypeDefinition))] 27         [Name("GLSLVariable")] 28         internal static ClassificationTypeDefinition GLSLVariableType = null; 29 

30         [Export(typeof(ClassificationTypeDefinition))] 31         [Name("GLSLFunction")] 32         internal static ClassificationTypeDefinition GLSLFunctionType = null; 33     }

 

5. 关联文件类型


打开ShaderEditor.cs

 1     [Export(typeof(IClassifierProvider))]  2     [ContentType("text")]  3     internal class ShaderEditorProvider : IClassifierProvider {  4  [Import]  5         internal IClassificationTypeRegistryService ClassificationRegistry = null; // Set via MEF

 6 

 7         public IClassifier GetClassifier(ITextBuffer buffer) {  8             return buffer.Properties.GetOrCreateSingletonProperty<ShaderEditor>(delegate { return new ShaderEditor(ClassificationRegistry); });  9  } 10     }

代码ContentType("text")创建了一个Provider并将它们对所有text类型的文件生效。

GLSL主要的文件扩展名为.vsh和.fsh,为了只对这两个扩展名生效,我们需要自定义一个ContentType,并创建两个扩展名关联。将上述代码修改为:

 1     [Export(typeof(ITaggerProvider))]  2     [ContentType("glsl")]  3     [TagType(typeof(ClassificationTag))]  4     internal sealed class GLSLClassifierProvider : ITaggerProvider {  5 

 6  [Export]  7         [Name("glsl")]  8         [BaseDefinition("code")]  9         internal static ContentTypeDefinition GLSLContentType = null; 10 

11  [Export] 12         [FileExtension(".vsh")] 13         [ContentType("glsl")] 14         internal static FileExtensionToContentTypeDefinition GLSLVshType = null; 15 

16  [Export] 17         [FileExtension(".fsh")] 18         [ContentType("glsl")] 19         internal static FileExtensionToContentTypeDefinition GLSLFshType = null; 20 

21  [Import] 22         internal IClassificationTypeRegistryService classificationTypeRegistry = null; 23 

24  [Import] 25         internal IBufferTagAggregatorFactoryService aggregatorFactory = null; 26 

27         public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag { 28             return new GLSLClassifier(buffer, classificationTypeRegistry) as ITagger<T>; 29  } 30     }

这样我们就创建了只针对vsh和fsh文件生效的Editor。

 

6. 使用gplex进行词法分析


我们需要使用词法分析扫描器来实现具体的着色功能,gplex可以为我们生成C#语言的扫描器,下载地址:

http://gplex.codeplex.com/

解压后在binaries文件夹下找到gplex.exe,把它拷贝到项目的根目录下。

在项目根目录下新建一个GLSL文件夹,新建GLSLLexer.lex文件。并把它们添加到proj中。

在proj上右键->属性,在生成事件选项卡中,在预先生成事件命令行中输入

cd $(ProjectDir)GLSL\
$(ProjectDir)\gplex GLSLLexer

打开GLSLLexer.lex,写入以下代码:

 1 %option unicode, codepage:raw  2 

 3 %{  4         // User code is all now in ScanHelper.cs

 5 %}  6 

 7 %namespace Shane  8 %option verbose, summary, noparser, nofiles, unicode  9 

10 %{ 11     public int nextToken() { return yylex(); } 12     public int getPos() { return yypos; } 13     public int getLength() { return yyleng; } 14 %} 15 

16 //============================================================= 17 //=============================================================

18 

19 number ([0-9])+

20 chars [A-Za-z] 21 cstring [A-Za-z_] 22 blank " "

23 delim [ \t\n] 24 word {chars}+

25 singleLineComment "//"[^\n]*

26 multiLineComment "/*"[^*]*\*(\*|([^*/]([^*])*\*))*\/

27 

28 comment {multiLineComment}|{singleLineComment} 29 class bool|int|float|bvec|ivec|vec|vec2|vec3|vec4|mat2|mat3|mat4|sampler1D|sampler2D|sampler3D|samplerCube|sampler1DShadow|sampler2DShadow 30 keyword return|if|else|while|do|for|foreach|break|continue|switch|case|default|goto|class|struct|enum|extern|interface|namespace|public|static|this|volatile|using|in|out|true|false

31 qualifier const|attribute|uniform|varying 32 systemVariable gl_BackColor|gl_BackLightModelProduct|gl_BackLightProduct|gl_BackMaterial|gl_BackSecondaryColor|gl_ClipPlane|gl_ClipVertex|gl_Color|gl_DepthRange|gl_DepthRangeParameters|gl_EyePlaneQ|gl_EyePlaneR|gl_EyePlaneS|gl_EyePlaneT|gl_Fog|gl_FogCoord|gl_FogFragCoord|gl_FogParameters|gl_FragColor|gl_FragCoord|gl_FragData|gl_FragDepth|gl_FrontColor|gl_FrontFacing|gl_FrontLightModelProduct|gl_FrontLightProduct|gl_FrontMaterial|gl_FrontSecondaryColor|gl_LightModel|gl_LightModelParameters|gl_LightModelProducts|gl_LightProducts|gl_LightSource|gl_LightSourceParameters|gl_MaterialParameters|gl_MaxClipPlanes|gl_MaxCombinedTextureImageUnits|gl_MaxDrawBuffers|gl_MaxFragmentUniformComponents|gl_MaxLights|gl_MaxTextureCoords|gl_MaxTextureImageUnits|gl_MaxTextureUnits|gl_MaxVaryingFloats|gl_MaxVertexAttribs|gl_MaxVertexTextureImageUnits|gl_MaxVertexUniformComponents|gl_ModelViewMatrix|gl_ModelViewMatrixInverse|gl_ModelViewMatrixInverseTranspose|gl_ModelViewMatrixTranspose|gl_ModelViewProjectionMatrix|gl_ModelViewProjectionMatrixInverse|gl_ModelViewProjectionMatrixInverseTranspose|gl_ModelViewProjectionMatrixTranspose|gl_MultiTexCoord0|gl_MultiTexCoord1|gl_MultiTexCoord10|gl_MultiTexCoord11|gl_MultiTexCoord2|gl_MultiTexCoord3|gl_MultiTexCoord4|gl_MultiTexCoord5|gl_MultiTexCoord6|gl_MultiTexCoord7|gl_MultiTexCoord8|gl_MultiTexCoord9|gl_Normal|gl_NormalMatrix|gl_NormalScale|gl_ObjectPlaneQ|gl_ObjectPlaneR|gl_ObjectPlaneS|gl_ObjectPlaneT|gl_Point|gl_PointParameters|gl_PointSize|gl_Position|gl_ProjectionMatrix|gl_ProjectionMatrixInverse|gl_ProjectionMatrixInverseTranspose|gl_ProjectionMatrixTranspose|gl_SecondaryColor|gl_TexCoord|gl_TextureEnvColor|gl_TextureMatrix|gl_TextureMatrixInverse|gl_TextureMatrixInverseTranspose|gl_TextureMatrixTranspose|gl_Vertex 33 systemFunction radians|degress|sin|cos|tan|asin|acos|atan|pow|exp|log|exp2|log2|sqrt|inversesqrt|abs|sign|floor|ceil|fract|mod|min|max|clamp|mix|step|smoothstep|length|distance|dot|cross|normalize|faceforward|reflect|matrixCompMult|lessThan|lessThanEqual|greaterThan|greaterThanEqual|equal|notEqual|any|all|not|texture2D|texture2DProj|texture2DLod|texture2DProjLod|textureCube|textureCubeLod 34 identifier {cstring}+{number}*[{cstring}@]*{number}*

35 

36 %%

37 

38 {keyword}            return (int)GLSLTokenType.Keyword; 39 {class}                return (int)GLSLTokenType.Class; 40 {qualifier}            return (int)GLSLTokenType.Qualifier; 41 {systemVariable}    return (int)GLSLTokenType.SystemVariable; 42 {systemFunction}    return (int)GLSLTokenType.SystemFunction; 43 {identifier}        return (int)GLSLTokenType.Identifier; 44 {comment}            return (int)GLSLTokenType.Comment; 45 

46 %%

保存并关闭,这时生成一下项目,我们会看到在GLSL目录下生成了GLSLLexer.cs文件,同样把这个文件添加到proj中。

 

7. 处理扫描结果 


接下来我们要在ShaderEditor.cs中处理我们的扫描结果,并最终对匹配的代码行进行着色。

首先删除默认创建的ShaderEditor类。

添加一个GLSLToken枚举,这个枚举就是GLSLLexer.cs返回的枚举类型,它用来通知我们当前的语句块是哪个类型。

代码如下:

 1     public enum GLSLTokenType {  2         Text = 1,  3  Keyword,  4  Comment,  5  Identifier,  6  Class,  7  Qualifier,  8  SystemVariable,  9  SystemFunction 10     }

创建我们自己的ShaderEditor类,代码如下:

 

 1     internal sealed class GLSLClassifier : ITagger<ClassificationTag> {

 2         internal GLSLClassifier(ITextBuffer buffer, IClassificationTypeRegistryService typeService) {

 3             textBuffer = buffer;

 4             typeDic = new Dictionary<GLSLTokenType, IClassificationType>();

 5             typeDic[GLSLTokenType.Text] = typeService.GetClassificationType("GLSLText");

 6             typeDic[GLSLTokenType.Identifier] = typeService.GetClassificationType("GLSLIdentifier");

 7             typeDic[GLSLTokenType.Keyword] = typeService.GetClassificationType("GLSLKeyword");

 8             typeDic[GLSLTokenType.Class] = typeService.GetClassificationType("GLSLClass");

 9             typeDic[GLSLTokenType.Comment] = typeService.GetClassificationType("GLSLComment");

10             typeDic[GLSLTokenType.Qualifier] = typeService.GetClassificationType("GLSLQualifier");

11             typeDic[GLSLTokenType.SystemVariable] = typeService.GetClassificationType("GLSLVariable");

12             typeDic[GLSLTokenType.SystemFunction] = typeService.GetClassificationType("GLSLFunction");

13         }

14 

15         public event EventHandler<SnapshotSpanEventArgs> TagsChanged {

16             add { }

17             remove { }

18         }

19 

20         public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) {

21             IClassificationType classification = typeDic[GLSLTokenType.Text];

22             string text = spans[0].Snapshot.GetText();

23             yield return new TagSpan<ClassificationTag>(

24                 new SnapshotSpan(spans[0].Snapshot, new Span(0, text.Length)),

25                 new ClassificationTag(classification));

26             scanner.SetSource(text, 0);

27             int tok;

28             do {

29                 tok = scanner.nextToken();

30                 int pos = scanner.getPos();

31                 int len = scanner.getLength();

32                 int total = text.Length;

33                 if (pos < 0 || len < 0 || pos > total) {

34                     continue;

35                 }

36                 if (pos + len > total) {

37                     len = total - pos;

38                 }

39                 if (typeDic.TryGetValue((GLSLTokenType)tok, out classification)) {

40                     yield return new TagSpan<ClassificationTag>(

41                         new SnapshotSpan(spans[0].Snapshot, new Span(pos, len)),

42                         new ClassificationTag(classification));

43                 }

44             } while (tok > (int)Tokens.EOF);

45         }

46 

47         ITextBuffer textBuffer;

48         IDictionary<GLSLTokenType, IClassificationType> typeDic;

49         Scanner scanner = new Scanner();

50     }

 

TagsChanged事件保证在代码发生改变时实时刷新编辑器。

构造方法中,通过typeService.GetClassificationType("GLSLIdentifier")取得我们定义的实例,并把它们和枚举关联起来,

GetClassificationType的参数传入着色类型的Name。

GetTags方法是由系统调用的迭代方法,yield return new TagSpan<ClassificationTag>()返回我们的着色对象,即可实现着色效果。

编译并运行,可以看到vsh和fsh已经有了语法着色了。

 

本文由哈萨雅琪原创,转载请注明出处。

 

你可能感兴趣的:(sdk)