ArticlePosted on April 2015
Live WebGL Shader Editor
Reading time: 5 minutes
Topics: WebGL, GLSL, JavaScript
This is a legacy post, ported from the old system:
Images might be too
low quality, code might be outdated, and some links might not work.
This article explains how to augment JavaScript APIs and how to use it to create a plug-n-play, live GLSL shader editor.
This is a JavaScript library that aims to provide the same functionality as Firefox DevTools Shader Editor: modify the source of shaders in real-time, in the browser, without reloading, easy to include and totally implementation-agnostic. Using function hooks to modify the relevant methods of WebGLRenderingContext, it's possible to add this features with JavaScript only.
Better check the demo first: A simple cube with three.js
Extending APIs with hooks
One of the best things about JavaScript is its malleable nature. Some coders like it, some don't. I personally love the fact that we can polyfill, augment and re-invent almost any function.
The idea is simple, given an original function (originalFn) and a function that you want to execute along every time originalFn is called (hookFn), pass both to a function that creates a closure that will execute both. We're using Function.prototype.apply() to pass whatever arguments are in the original function signature.
Generic hook function
JavaScript - library
function hook( originalFn, hookFn ) {
return function() {
hookFn.apply( this, arguments );
return originalFn.apply( this, arguments );
}
}
The order of execution can be switched, in case you want to keep the return value of the originalFn call.
To use it, simply do something like this:
Using a hook function to instrument host methods
JavaScript - library
Document.prototype.getElementById = hook(
Document.prototype.getElementById,
function(){
console.log( 'document.getElementById', arguments );
}
);
I've used this method for many tools:
- rStats uses it to instrument WebGLRenderingContext
- Augmented CompileShader uses it to provide better error handling when compiling shaders
- THREE.AugmentedConsole uses it to augment console.log()
- Also in this test of Web Audio API instrumenting WA-Hook
It's precisely this last experiment -trying to emulate the Firefox DevTools Web Audio Inspector- that gave me the idea that the Shader Editor -also in Firefox DevTools- could also be done as a library and embedded in a Chrome DevTools extension.
Creating the Shader Editor
To create a shader in WebGL we use a few methods, let's intercept those:
- createProgram: we keep a reference to each created program, and store it in a dictionary.
- createShader: we keep a reference to the created shader, and store it in another dictionary.
- shaderSource: we find the shader from the dictionary, and assign the passed source.
- compileShader: nothing to do here, just listing it.
- attachShader: we find the shader reference from the dictionary, find the program reference from the dictionary, and link both.
- linkProgram: nothing to do here either, just making sure it's in the list.
This works so far! We can show a UI with the list of programs, from the programs dictionary. When the user selects a program from the list, we populate the vertex shader editor and the fragment shader editor with the appropritate source code, and every time the user modifies the code, we have the reference to the shader, we can set the shader source again, test if it compiles, and if it compiles, attach it to the program and link the program.
Uniform problems
It turns out that if you cache the an uniform location (retrieved with getUniformLocation), the methods that are used to assign the uniform (uniform1f, uniformMatrix2fv, etc.) no longer work, because even though the program has not actually changed, the uniforms are related to a different program.
So we need to also agument getUniformLocation. This the bit hacky part:
- We keep a reference to the original getUniformLocation method.
- We agument getUniformLocation and keep a dictionary of references of: program, uniform name and location.
- We intercept ALL methods to assign an uniform
- Everytime a method to assign a uniform is called, we actually use the original, unadultarated getUniformLocation to find the real location, and assign it.
Proof of concept and beyond
So this is a proof of concept. It's as easy to use as including the script before anything else (before the app JavaScript code starts using WebGL context):
Including the Live WebGL Editor in your page
HTML - index.html
<script src="ShaderEditor.js"></script>
So this works with Firefox, Chrome, Safari, etc. It works but it needs more time dedicated to it, I'm pretty sure it will fail for a lot of cases.
Additionally, I've added CodeMirror for formating and syntax highlighting, and expanded compileShader to add in-editor error reporting.
But the main idea is to create a Chrome DevTools extension. And I have really no idea how to get that done, so if anyone is willing to help, let's do it!