Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fragment Shader Include Support #46

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ sandbox.setUniform("u_color",1,0,0);

// Load a new texture and assign it to "uniform sampler2D u_texture"
sandbox.setUniform("u_texture","data/texture.jpg");
```
### Include Files

Basic include support is offered for **fragment shaders only** in the style of [glslViewer](https://github.com/patriciogonzalezvivo/glslViewer). Includes must come after your ```#ifdef GL_ES...#endif``` definition. In order to utilize this support you can define imports in your fragment shader as follows:

```
#ifdef GL_ES
precision mediump float;
#endif

#include data/random.glsl
#include data/additional.glsl

```

### Quick start demo
Expand Down
4 changes: 4 additions & 0 deletions data/additional.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
float circle(vec2 st)
{
return smoothstep(0.0,0.001,-(length(st-0.5)-0.33));
}
9 changes: 9 additions & 0 deletions data/random.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
float random(vec2 st){ return fract( sin( dot(st,vec2(3.678,678.90)) )*678910.0 ); }
float random (in float x) { return fract(sin(x)*43758.5453123); }

vec2 random2(vec2 st)
{
st = vec2( dot(st,vec2(127.1,311.7)),
dot(st,vec2(269.5,183.3)) );
return -1.0 + 2.0*fract(sin(st)*43758.5453123);
}
20 changes: 20 additions & 0 deletions data/test.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifdef GL_ES
precision mediump float;
#endif

#include data/random.glsl
#include data/additional.glsl

#define SECONDS 6.0

uniform vec2 u_resolution;
uniform float u_time;

void main()
{
vec2 st = u_resolution.xy/gl_FragCoord.xy;
float t = abs(fract(u_time/SECONDS)-0.5);
vec3 c = vec3(random(st+t));
c += circle(st);
gl_FragColor = vec4(c,1.0);
}
235 changes: 219 additions & 16 deletions dist/GlslCanvas.es.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import xhr from 'xhr';
import 'constants';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
Expand Down Expand Up @@ -948,6 +949,189 @@ Texture.getMaxTextureSize = function (gl) {
// Global set of textures, by name
Texture.activeUnit = -1;

var Includes = function () {
function Includes() {
classCallCheck(this, Includes);

this.files = {};
this.file = '';
}

createClass(Includes, [{
key: 'fileIncluded',
value: function fileIncluded(file) {} // going to over-ride this as callback in super class

}, {
key: 'cancelPromiseCallbacks',
value: function cancelPromiseCallbacks() {
var c = Object.keys(this.files).length;

if (c <= 0) return;

var values = Object.values(this.files);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
for (var _iterator = values[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var value = _step.value;

value.parse = false;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}

this.files = {};
}
}, {
key: 'stripIncludes',
value: function stripIncludes(source) {
var _this = this;

// cancel any current promises
this.cancelPromiseCallbacks();

// define our file to strip here
this.file = source;

var exp = /^#include\s([\w].*)/igm; //previous /#include\s([\w].*)/ig; // ^(?!\/\/).*
var m;
do {
m = exp.exec(source);
if (m) {
console.log(m);
if (this.isFileNew(m[1]) == false) {
(function () {
var src = m[1];
var f = src;
var p = _this.loadIncludeFile(src).then(function (data) {
return { 'src': src, 'data': data };
}).catch(function (res) {
return console.log('include error:', src, ' ', res);
});
var o = {
'src': f,
'promise': p,
'include:': '',
'parse': true
};
_this.files[src] = o;
})();
}
}
} while (m);

source = source.replace(exp, "");
this.file = source;

var promises = Object.values(this.files).map(function (i) {
return i.promise;
});
var t = this;
Promise.all(promises).then(function (includes) {
includes.forEach(function (include) {
t.includeFileLoaded(include.src, include.data);
});
t.fileIncluded(t.file);
});

return source;
}
}, {
key: 'injectIncludeFile',
value: function injectIncludeFile(src, includeSrc) {
var def = /\#ifdef(\s\S*)+\#endif/img;
var header = src.match(def);
src = src.replace(def, header + '\n\n' + includeSrc);
return src;
}
}, {
key: 'includeFileLoaded',
value: function includeFileLoaded(src, include) {
var f = this.files[src];

if (f.parse == false) return false;

this.files[src].include = include;
this.files[src].parse = false;

this.file = this.injectIncludeFile(this.file, include);

// this.fileIncluded(this.file);
return this.file;
}
}, {
key: 'loadIncludeFile',
value: function loadIncludeFile(src) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open('GET', src + "?" + Date.now(), true);
client.overrideMimeType("text/plain");
client.setRequestHeader("Content-type", "text/html; charset=utf-8");
client.onreadystatechange = function () {

if (client.readyState == 4) {
if (client.status == 200 || client.responseText != '') // || client.status == 0
resolve(client.responseText);else reject(client.type);
}
};
client.onerror = function (ex) {
return reject(ex);
};
client.send();
});
}

// need to recreate this based on new structure

}, {
key: 'isFileNew',
value: function isFileNew(src) {
var keys = Object.keys(this.files);
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;

try {
for (var _iterator2 = keys[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var key = _step2.value;

if (key === src) return true;
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}

return false;
}
}]);
return Includes;
}();

/*
The MIT License (MIT)

Expand Down Expand Up @@ -1000,12 +1184,14 @@ var GlslCanvas = function () {
this.uniforms = {};
this.vbo = {};
this.isValid = false;
this.includes = new Includes();

this.BUFFER_COUNT = 0;
// this.TEXTURE_COUNT = 0;

this.vertexString = contextOptions.vertexString || '\n#ifdef GL_ES\nprecision mediump float;\n#endif\n\nattribute vec2 a_position;\nattribute vec2 a_texcoord;\n\nvarying vec2 v_texcoord;\n\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texcoord = a_texcoord;\n}\n';
this.fragmentString = contextOptions.fragmentString || '\n#ifdef GL_ES\nprecision mediump float;\n#endif\n\nvarying vec2 v_texcoord;\n\nvoid main(){\n gl_FragColor = vec4(0.0);\n}\n';
this.defaultVertexString = '\n #ifdef GL_ES\n precision mediump float;\n #endif\n \n attribute vec2 a_position;\n attribute vec2 a_texcoord;\n \n varying vec2 v_texcoord;\n \n void main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texcoord = a_texcoord;\n }\n ';
this.defaultFragmentString = '\n #ifdef GL_ES\n precision mediump float;\n #endif\n \n varying vec2 v_texcoord;\n \n void main(){\n gl_FragColor = vec4(0.0);\n }\n ';
this.vertexString = contextOptions.vertexString || this.defaultVertexString;
this.fragmentString = contextOptions.fragmentString || this.defaultFragmentString;

// GL Context
var gl = setupWebGL(canvas, contextOptions, options.onError);
Expand All @@ -1017,6 +1203,7 @@ var GlslCanvas = function () {
this.timeDelta = 0.0;
this.forceRender = true;
this.paused = false;
this.deferLoadForIncludes = false;
this.realToCSSPixels = window.devicePixelRatio || 1;

// Allow alpha
Expand All @@ -1025,6 +1212,7 @@ var GlslCanvas = function () {
// Load shader
if (canvas.hasAttribute('data-fragment')) {
this.fragmentString = canvas.getAttribute('data-fragment');
this.load();
} else if (canvas.hasAttribute('data-fragment-url')) {
var source = canvas.getAttribute('data-fragment-url');
xhr.get(source, function (error, response, body) {
Expand All @@ -1035,14 +1223,16 @@ var GlslCanvas = function () {
// Load shader
if (canvas.hasAttribute('data-vertex')) {
this.vertexString = canvas.getAttribute('data-vertex');
this.load();
} else if (canvas.hasAttribute('data-vertex-url')) {
var _source = canvas.getAttribute('data-vertex-url');
xhr.get(_source, function (error, response, body) {
_this.load(_this.fragmentString, body);
});
}

this.load();
// need to load our default shaders to progress through process
this.load_after_includes(this.defaultFragmentString, this.defaultVertexString);

if (!this.program) {
return;
Expand Down Expand Up @@ -1123,10 +1313,12 @@ var GlslCanvas = function () {
}
this.program = null;
this.gl = null;
this.includes.cancelPromiseCallbacks();
}
}, {
key: 'load',
value: function load(fragString, vertString) {
var _this2 = this;

// Load vertex shader if there is one
if (vertString) {
Expand All @@ -1138,16 +1330,27 @@ var GlslCanvas = function () {
this.fragmentString = fragString;
}

this.includes.stripIncludes(this.fragmentString);
this.includes.fileIncluded = function (file) {
_this2.fragmentString = file;
_this2.load_after_includes(_this2.fragmentString, _this2.vertexString);
};
}
}, {
key: 'load_after_includes',
value: function load_after_includes(fragString, vertString) {
// Should probably just strip my include and deal with shit here

this.animated = false;
this.nDelta = (this.fragmentString.match(/u_delta/g) || []).length;
this.nTime = (this.fragmentString.match(/u_time/g) || []).length;
this.nDate = (this.fragmentString.match(/u_date/g) || []).length;
this.nMouse = (this.fragmentString.match(/u_mouse/g) || []).length;
this.nDelta = (fragString.match(/u_delta/g) || []).length;
this.nTime = (fragString.match(/u_time/g) || []).length;
this.nDate = (fragString.match(/u_date/g) || []).length;
this.nMouse = (fragString.match(/u_mouse/g) || []).length;
this.animated = this.nDate > 1 || this.nTime > 1 || this.nMouse > 1;

var nTextures = this.fragmentString.search(/sampler2D/g);
var nTextures = fragString.search(/sampler2D/g);
if (nTextures) {
var lines = this.fragmentString.split('\n');
var lines = fragString.split('\n');
for (var i = 0; i < lines.length; i++) {
var match = lines[i].match(/uniform\s*sampler2D\s*([\w]*);\s*\/\/\s*([\w|\:\/\/|\.|\-|\_]*)/i);
if (match) {
Expand All @@ -1163,8 +1366,8 @@ var GlslCanvas = function () {
}
}

var vertexShader = createShader(this, this.vertexString, this.gl.VERTEX_SHADER);
var fragmentShader = createShader(this, this.fragmentString, this.gl.FRAGMENT_SHADER);
var vertexShader = createShader(this, vertString, this.gl.VERTEX_SHADER);
var fragmentShader = createShader(this, fragString, this.gl.FRAGMENT_SHADER);

// If Fragment shader fails load a empty one to sign the error
if (!fragmentShader) {
Expand All @@ -1188,7 +1391,7 @@ var GlslCanvas = function () {
this.change = true;

this.BUFFER_COUNT = 0;
var buffers = this.getBuffers(this.fragmentString);
var buffers = this.getBuffers(fragString);
if (Object.keys(buffers).length) {
this.loadPrograms(buffers);
}
Expand Down Expand Up @@ -1258,7 +1461,7 @@ var GlslCanvas = function () {
}, {
key: 'loadTexture',
value: function loadTexture(name, urlElementOrData, options) {
var _this2 = this;
var _this3 = this;

if (!options) {
options = {};
Expand All @@ -1278,13 +1481,13 @@ var GlslCanvas = function () {
if (this.textures[name]) {
this.textures[name].load(options);
this.textures[name].on('loaded', function (args) {
_this2.forceRender = true;
_this3.forceRender = true;
});
}
} else {
this.textures[name] = new Texture(this.gl, name, options);
this.textures[name].on('loaded', function (args) {
_this2.forceRender = true;
_this3.forceRender = true;
});
}
}
Expand Down
Loading