使用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為我們生成了如下幾個文件。
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#語言的掃描器,下載地址:
解壓后在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已經有了語法着色了。
本文由哈薩雅琪原創,轉載請注明出處。