使用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為我們生成了如下幾個文件。

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已經有了語法着色了。

 

本文由哈薩雅琪原創,轉載請注明出處。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM