├── .gitignore ├── GLShaderValidator.py ├── GLShaderValidator.sublime-settings ├── LICENSE ├── Main.sublime-menu ├── README.md ├── essl_to_glsl_linux ├── essl_to_glsl_osx └── essl_to_glsl_win.exe /.gitignore: -------------------------------------------------------------------------------- 1 | GLShaderValidator.pyc 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /GLShaderValidator.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import re 4 | import subprocess 5 | import os 6 | 7 | 8 | class GLShaderError: 9 | """ Represents an error """ 10 | region = None 11 | message = '' 12 | 13 | def __init__(self, region, message): 14 | self.region = region 15 | self.message = message 16 | 17 | 18 | class ANGLECommandLine: 19 | """ Wrapper for ANGLE CLI """ 20 | 21 | packagePath = "GL Shader Validator" 22 | platform = sublime.platform() 23 | errorPattern = re.compile("ERROR: 0:(\d+): '([^\']*)' : (.*)") 24 | permissionChecked = False 25 | ANGLEPath = { 26 | "osx": "./essl_to_glsl_osx", 27 | "linux": "./essl_to_glsl_linux", 28 | "windows": "essl_to_glsl_win.exe" 29 | } 30 | 31 | def ensure_script_permissions(self): 32 | """ Ensures that we have permission to execute the command """ 33 | 34 | if not self.permissionChecked: 35 | os.chmod(sublime.packages_path() + os.sep + self.packagePath + os.sep + self.ANGLEPath[self.platform], 0o755) 36 | 37 | self.permissionChecked = True 38 | return self.permissionChecked 39 | 40 | def validate_contents(self, view): 41 | """ Validates the file contents using ANGLE """ 42 | ANGLEPath = self.ANGLEPath[self.platform] 43 | errors = [] 44 | fileLines = view.lines( 45 | sublime.Region(0, view.size()) 46 | ) 47 | 48 | specCmd = '' 49 | 50 | # Go with WebGL spec 51 | if view.settings().get('glsv_spec') == 0 or view.find("spec: webgl", sublime.IGNORECASE) is not None: 52 | specCmd = '-s=w' 53 | 54 | # Check if the user has changed which spec they 55 | # want to use. If they have, drop the switch 56 | if view.settings().get('glsv_spec') == 1 or view.find("spec: es2", sublime.IGNORECASE) is not None: 57 | specCmd = '' 58 | 59 | if view.settings().get('glsv_spec') == 2 or view.find("spec: css", sublime.IGNORECASE) is not None: 60 | specCmd = '-s=c' 61 | 62 | # We need an extra flag for windows 63 | if self.platform == "windows": 64 | specCmd = specCmd + " -b=h" 65 | 66 | # Create a shell process for essl_to_glsl and pick 67 | # up its output directly 68 | ANGLEProcess = subprocess.Popen( 69 | ANGLEPath + ' ' + specCmd + ' "' + view.file_name() + '"', 70 | cwd=sublime.packages_path() + os.sep + self.packagePath + os.sep, 71 | stdout=subprocess.PIPE, 72 | stderr=subprocess.STDOUT, 73 | shell=True) 74 | 75 | if ANGLEProcess.stdout is not None: 76 | errlines = ANGLEProcess.stdout.readlines() 77 | 78 | # Go through each error, ignoring any comments 79 | for e in errlines: 80 | 81 | e = e.decode("utf-8") 82 | 83 | # Check if there was a permission denied 84 | # error running the essl_to_glsl cmd 85 | 86 | if re.search("permission denied", str(e), flags=re.IGNORECASE): 87 | sublime.error_message("GLShaderValidator: permission denied to use essl_to_glsl command") 88 | return [] 89 | 90 | # ignore ANGLE's comments 91 | if not re.search("^####", e): 92 | 93 | # Break down the error using the regexp 94 | errorDetails = self.errorPattern.match(e) 95 | 96 | # For each match construct an error 97 | # object to pass back 98 | if errorDetails is not None: 99 | errorLine = int(errorDetails.group(1)) - 1 100 | errorToken = errorDetails.group(2) 101 | errorDescription = errorDetails.group(3) 102 | errorLocation = fileLines[errorLine] 103 | 104 | # If there is a token try and locate it 105 | if len(errorToken) > 0: 106 | betterLocation = view.find( 107 | errorToken, 108 | errorLocation.begin(), 109 | sublime.LITERAL) 110 | 111 | # Ensure we have a match before we 112 | # replace the error region 113 | if betterLocation is not None: 114 | errorLocation = betterLocation 115 | 116 | errors.append(GLShaderError( 117 | errorLocation, 118 | errorDescription 119 | )) 120 | 121 | return errors 122 | 123 | 124 | class GLShaderValidatorCommand(sublime_plugin.EventListener): 125 | """ Main Validator Class """ 126 | ANGLECLI = ANGLECommandLine() 127 | errors = None 128 | loadedSettings = False 129 | pluginSettings = None 130 | 131 | # these are the default settings. They are overridden and 132 | # documented in the GLShaderValidator.sublime-settings file 133 | DEFAULT_SETTINGS = { 134 | "glsv_enabled": 1, 135 | "glsv_spec": 0 136 | } 137 | 138 | def __init__(self): 139 | """ Startup """ 140 | # ensure that the script has permissions to run 141 | self.ANGLECLI.ensure_script_permissions() 142 | 143 | def clear_settings(self): 144 | """ Resets the settings value so we will overwrite on the next run """ 145 | for window in sublime.windows(): 146 | for view in window.views(): 147 | if view.settings().get('glsv_configured') is not None: 148 | view.settings().set('glsv_configured', None) 149 | 150 | def apply_settings(self, view): 151 | """ Applies the settings from the settings file """ 152 | 153 | # load in the settings file 154 | if self.pluginSettings is None: 155 | self.pluginSettings = sublime.load_settings(__name__ + ".sublime-settings") 156 | self.pluginSettings.clear_on_change('glsv_validator') 157 | self.pluginSettings.add_on_change('glsv_validator', self.clear_settings) 158 | 159 | if view.settings().get('glsv_configured') is None: 160 | 161 | view.settings().set('glsv_configured', True) 162 | 163 | # Go through the default settings 164 | for setting in self.DEFAULT_SETTINGS: 165 | 166 | # set the value 167 | settingValue = self.DEFAULT_SETTINGS[setting] 168 | 169 | # check if the user has overwritten the value 170 | # and switch to that instead 171 | if self.pluginSettings.get(setting) is not None: 172 | settingValue = self.pluginSettings.get(setting) 173 | 174 | view.settings().set(setting, settingValue) 175 | 176 | def clear_errors(self, view): 177 | """ Removes any errors """ 178 | view.erase_regions('glshadervalidate_errors') 179 | 180 | def is_glsl_or_essl(self, view): 181 | """ Checks that the file is GLSL or ESSL """ 182 | syntax = view.settings().get('syntax') 183 | isShader = False 184 | if syntax is not None: 185 | isShader = re.search('GLSL|ESSL', syntax, flags=re.IGNORECASE) is not None 186 | return isShader 187 | 188 | def is_valid_file_ending(self, view): 189 | """ Checks that the file ending will work for ANGLE """ 190 | isValidFileEnding = re.search('(frag|vert)$', view.file_name()) is not None 191 | return isValidFileEnding 192 | 193 | def show_errors(self, view): 194 | """ Passes over the array of errors and adds outlines """ 195 | 196 | # Go through the errors that came back 197 | errorRegions = [] 198 | for error in self.errors: 199 | errorRegions.append(error.region) 200 | 201 | # Put an outline around each one and a dot on the line 202 | view.add_regions( 203 | 'glshadervalidate_errors', 204 | errorRegions, 205 | 'glshader_error', 206 | 'dot', 207 | sublime.DRAW_OUTLINED 208 | ) 209 | 210 | def on_selection_modified(self, view): 211 | """ Shows a status message for an error region """ 212 | 213 | view.erase_status('glshadervalidator') 214 | 215 | # If we have errors just locate 216 | # the first one and go with that for the status 217 | if self.is_glsl_or_essl(view) and self.errors is not None: 218 | for sel in view.sel(): 219 | for error in self.errors: 220 | if error.region.contains(sel): 221 | view.set_status('glshadervalidator', error.message) 222 | return 223 | 224 | def on_load(self, view): 225 | """ File loaded """ 226 | self.run_validator(view) 227 | 228 | def on_activated(self, view): 229 | """ File activated """ 230 | self.run_validator(view) 231 | 232 | def on_post_save(self, view): 233 | """ File saved """ 234 | self.run_validator(view) 235 | 236 | def run_validator(self, view): 237 | """ Runs a validation pass """ 238 | 239 | # clear the last run 240 | view.erase_status('glshadervalidator') 241 | 242 | # set up the settings if necessary 243 | self.apply_settings(view) 244 | 245 | # early return if they have disabled the linter 246 | if view.settings().get('glsv_enabled') == 0: 247 | self.clear_errors(view) 248 | return 249 | 250 | # early return for anything not syntax 251 | # highlighted as GLSL / ESSL 252 | if not self.is_glsl_or_essl(view): 253 | return 254 | 255 | # ANGLE expects files to be suffixed as .frag or 256 | # .vert so we need to do that check here 257 | if self.is_valid_file_ending(view): 258 | 259 | # Clear the last set of errors 260 | self.clear_errors 261 | 262 | # Get the file and send to ANGLE 263 | self.errors = self.ANGLECLI.validate_contents(view) 264 | self.show_errors(view) 265 | else: 266 | view.set_status('glshadervalidator', "File name must end in .frag or .vert") 267 | -------------------------------------------------------------------------------- /GLShaderValidator.sublime-settings: -------------------------------------------------------------------------------- 1 | /* 2 | GL Shader Validator settings 3 | */ 4 | { 5 | /* 6 | Sets whether the plugin runs: 7 | 8 | 0 = Validation disabled 9 | 1 = Validation enabled [load, save, activation] 10 | 11 | */ 12 | "glsv_enabled": 1, 13 | 14 | /* 15 | Sets the default specification that should be used: 16 | 17 | 0 = WebGL 18 | 1 = GLSL ES 19 | 2 = CSS Shaders 20 | 21 | */ 22 | "glsv_spec": 0 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Google Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "id": "preferences", 5 | "children": [ 6 | { 7 | "caption": "Package Settings", 8 | "id": "package-settings", 9 | "children": [ 10 | { 11 | "caption": "GL Shader Validator", 12 | "id": "glshadervalidator", 13 | "children": [ 14 | { 15 | "command": "open_file", 16 | "args": { 17 | "file": "${packages}/GL Shader Validator/README.md" 18 | }, 19 | "caption": "README" 20 | }, 21 | { 22 | "command": "open_file", 23 | "args": { 24 | "file": "${packages}/GL Shader Validator/LICENSE" 25 | }, 26 | "caption": "LICENSE" 27 | }, 28 | { 29 | "caption": "-" 30 | }, 31 | { 32 | "command": "open_file", 33 | "args": { 34 | "file": "${packages}/GL Shader Validator/GLShaderValidator.sublime-settings" 35 | }, 36 | "caption": "Settings - Default" 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GL Shader Validator 2 | 3 | ![image](http://aerotwist.com/glshadervalidator/screenshot.png) 4 | 5 | This is a [Sublime Text 2 / 3](http://www.sublimetext.com/) plugin that passes GLSL / ESSL to ANGLE's 6 | preprocessor / compiler for validation. 7 | Any errors that ANGLE finds are routed back to Sublime and the tokens in 8 | the shader are highlighted for your convenience and debugging joy. 9 | To see the details of the error check the status message in the bottom left of the 10 | Sublime view. 11 | 12 | ## Installation 13 | 14 | **You can, and probably should, install GL Shader Validator via [Package Control](http://wbond.net/sublime_packages/package_control)** 15 | 16 | If you would like to install it manually, clone this repo into your packages folder 17 | (typically ~/Library/Application Support/Sublime Text 2/Packages). 18 | 19 | ``` 20 | cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages 21 | git clone git://github.com/WebGLTools/GL-Shader-Validator.git "GL Shader Validator" 22 | ``` 23 | 24 | If you're on Windows 7 the path looks more like this, assuming you have Git installed: 25 | 26 | ``` 27 | cd c:\users\YOUR_ACCOUNT\AppData\Roaming\Sublime Text 2\Packages 28 | git clone git://github.com/WebGLTools/GL-Shader-Validator.git "GL Shader Validator" 29 | ``` 30 | 31 | Also if you're using Sublime Text 3 then you just switch the `2` in the path above to `3` and you should be all set. 32 | 33 | ## Usage 34 | 35 | Assuming that you have a [GLSL / ESSL syntax highlighter](https://github.com/euler0/sublime-glsl) installed in Sublime, all you should need to do 36 | is install the plugin and your shader code will be validated as expected. 37 | 38 | It's worth saying that ANGLE expects vertex shaders to have the file 39 | suffix `.vert` and fragment shaders `.frag`. If you do not name your files 40 | with that suffix ANGLE (and therefore the plugin) will not be able 41 | to validate your shaders. Sadness will ensue. 42 | 43 | You can set the default specification to use in the settings: 44 | ``` 45 | Preferences > Package Settings > GL Shader Validator > Settings - Default 46 | ``` 47 | 48 | This can be overridden in a specific shader by adding comments: 49 | `/* spec: webgl */` for WebGL, 50 | `/* spec: es2 */` for OpenGL ES 2.0 or 51 | `/* spec: css */ ` for Custom Filters / CSS Shaders 52 | 53 | ## Permissions 54 | 55 | This plugin requires use of a command line utility called essl_to_glsl, which is bundled with the plugin. By default, 56 | however, the utility will not have execute permissions. The plugin will attempt to enable those permissions automatically when it loads, but 57 | should that fail you will receive the following error message: 58 | 59 | > GLShaderValidator: permission denied to use essl_to_glsl command 60 | 61 | In such instances you should enable execute permissions yourself: 62 | 63 | ``` 64 | cd ~/Library/Application Support/Sublime Text 2/Packages/GL\ Shader\ Validator 65 | chmod +x essl_to_glsl 66 | ``` 67 | 68 | ## Settings 69 | 70 | You can modify the settings file (`GLShaderValidator.sublime-settings`) inside 71 | the plugin folder. You will find the documentation for the settings in 72 | that file. There aren't many of those right now, but if you want more let us 73 | know via the repo's Issues. 74 | 75 | ## Credits 76 | 77 | * [Paul Lewis](http://aerotwist.com) 78 | * [Brendan Kenny](http://extremelysatisfactorytotalitarianism.com/) 79 | -------------------------------------------------------------------------------- /essl_to_glsl_linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/GL-Shader-Validator/6462114ae379d5bee9fecce9ccc07bdc6d782f20/essl_to_glsl_linux -------------------------------------------------------------------------------- /essl_to_glsl_osx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/GL-Shader-Validator/6462114ae379d5bee9fecce9ccc07bdc6d782f20/essl_to_glsl_osx -------------------------------------------------------------------------------- /essl_to_glsl_win.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/GL-Shader-Validator/6462114ae379d5bee9fecce9ccc07bdc6d782f20/essl_to_glsl_win.exe --------------------------------------------------------------------------------