Refactor ShaderManager's DRAW_MODE and effect info

Draw modes are now enabled/disabled by bitmask and can be combined.
Combining silhouette mode and color mask mode will be useful for
color-touching-color, for example.

Effect converters are now one field in a larger EFFECT_INFO map, which
also contains bitmask values for each effect. Drawable no longer makes
any assumptions regarding how ShaderManager will unpack the effect bits.

The shader cache is now an array of arrays, where the outer index is the
mask of active draw modes and the inner index is the mask of active
effects (as it was before). Effects which cannot impact the shape of a
Drawable are masked out in silhouette mode in order to avoid compiling
redundant shaders.
This commit is contained in:
Christopher Willis-Ford 2016-06-09 12:17:12 -07:00
parent bb357ba8bc
commit 91cc797c3b
2 changed files with 94 additions and 46 deletions

View file

@ -47,7 +47,7 @@ function Drawable(gl) {
var numEffects = ShaderManager.EFFECTS.length;
for (var index = 0; index < numEffects; ++index) {
var effectName = ShaderManager.EFFECTS[index];
var converter = ShaderManager.EFFECT_VALUE_CONVERTER[effectName];
var converter = ShaderManager.EFFECT_INFO[effectName].converter;
this._uniforms['u_' + effectName] = converter(0);
}
@ -314,18 +314,18 @@ Drawable.prototype.updateProperties = function (properties) {
}
var numEffects = ShaderManager.EFFECTS.length;
for (var index = 0; index < numEffects; ++index) {
var propertyName = ShaderManager.EFFECTS[index];
if (propertyName in properties) {
var rawValue = properties[propertyName];
var mask = 1 << index;
var effectName = ShaderManager.EFFECTS[index];
if (effectName in properties) {
var rawValue = properties[effectName];
var effectInfo = ShaderManager.EFFECT_INFO[effectName];
if (rawValue != 0) {
this._effectBits |= mask;
this._effectBits |= effectInfo.mask;
}
else {
this._effectBits &= ~mask;
this._effectBits &= ~effectInfo.mask;
}
var converter = ShaderManager.EFFECT_VALUE_CONVERTER[propertyName];
this._uniforms['u_' + propertyName] = converter(rawValue);
var converter = effectInfo.converter;
this._uniforms['u_' + effectName] = converter(rawValue);
}
}
};
@ -334,7 +334,7 @@ Drawable.prototype.updateProperties = function (properties) {
* Set the dimensions of this Drawable's skin.
* @param {int} width The width of the new skin.
* @param {int} height The height of the new skin.
* @param {int} costumeResolution The resolution to use for this skin.
* @param {int} [costumeResolution] The resolution to use for this skin.
* @private
*/
Drawable.prototype._setSkinSize = function (width, height, costumeResolution) {

View file

@ -5,49 +5,84 @@ function ShaderManager(gl) {
/**
* The cache of all shaders compiled so far. These are generated on demand.
* @type {Object.<ShaderManager.DRAW_MODE, Array.<module:twgl.ProgramInfo>>}
* The outer index represents the draw mode and the inner index the effects.
* @type {Array.<Array.<module:twgl.ProgramInfo>>}
* @private
*/
this._shaderCache = {};
for (var modeName in ShaderManager.DRAW_MODE) {
if (ShaderManager.DRAW_MODE.hasOwnProperty(modeName)) {
this._shaderCache[modeName] = [];
this._shaderCache = [];
var drawModes = Object.keys(ShaderManager.DRAW_MODE);
var shaderCache = this._shaderCache;
function initCache(index, mask) {
if (index >= drawModes.length) {
shaderCache[mask] = shaderCache[mask] || [];
}
else {
var drawModeName = drawModes[index];
// Fill high index first to potentially reduce array resize count
initCache(index+1, mask | ShaderManager.DRAW_MODE[drawModeName]);
initCache(index+1, mask);
}
}
initCache(0, 0);
}
module.exports = ShaderManager;
/**
* Mapping of each effect to a conversion function. The conversion function
* takes a Scratch value (generally in the range 0..100 or -100..100) and maps
* it to a value useful to the shader. This may not be reversible.
* @type {Object.<string,function>}
* @private
* Mapping of each effect name to info about that effect.
* The info includes:
* - The bit in 'effectBits' representing the effect.
* - A conversion function which takes a Scratch value (generally in the range
* 0..100 or -100..100) and maps it to a value useful to the shader. This
* mapping may not be reversible.
* @type {Object.<string,Object.<string,*>>}
*/
ShaderManager.EFFECT_VALUE_CONVERTER = {
color: function(x) {
return (x / 200) % 1;
ShaderManager.EFFECT_INFO = {
color: {
mask: 1 << 0,
converter: function(x) {
return (x / 200) % 1;
}
},
fisheye: function(x) {
return Math.max(0, (x + 100) / 100);
fisheye: {
mask: 1 << 1,
converter: function(x) {
return Math.max(0, (x + 100) / 100);
}
},
whirl: function(x) {
return x * Math.PI / 180;
whirl: {
mask: 1 << 2,
converter: function(x) {
return x * Math.PI / 180;
}
},
pixelate: function(x) {
return Math.abs(x) / 10;
pixelate: {
mask: 1 << 3,
converter: function(x) {
return Math.abs(x) / 10;
}
},
mosaic: function(x) {
x = Math.round((Math.abs(x) + 10) / 10);
// TODO: cap by Math.min(srcWidth, srcHeight)
return Math.max(1, Math.min(x, 512));
mosaic: {
mask: 1 << 4,
converter: function(x) {
x = Math.round((Math.abs(x) + 10) / 10);
// TODO: cap by Math.min(srcWidth, srcHeight)
return Math.max(1, Math.min(x, 512));
}
},
brightness: function(x) {
return Math.max(-100, Math.min(x, 100)) / 100;
brightness: {
mask: 1 << 5,
converter: function(x) {
return Math.max(-100, Math.min(x, 100)) / 100;
}
},
ghost: function(x) {
return 1 - Math.max(0, Math.min(x, 100)) / 100;
ghost: {
mask: 1 << 6,
converter: function(x) {
return 1 - Math.max(0, Math.min(x, 100)) / 100;
}
}
};
@ -55,28 +90,28 @@ ShaderManager.EFFECT_VALUE_CONVERTER = {
* The name of each supported effect.
* @type {Array}
*/
ShaderManager.EFFECTS = Object.keys(ShaderManager.EFFECT_VALUE_CONVERTER);
ShaderManager.EFFECTS = Object.keys(ShaderManager.EFFECT_INFO);
/**
* The available draw modes.
* The available draw modes. May be combined with bitwise OR.
* @readonly
* @enum {string}
* @enum {int}
*/
ShaderManager.DRAW_MODE = {
/**
* Draw normally.
*/
default: 'default',
default: 0,
/**
* Draw a silhouette using a solid color.
*/
silhouette: 'silhouette',
silhouette: 1 << 0,
/**
* Draw only the parts of the drawable which match a particular color.
*/
colorMask: 'colorMask'
colorMask: 1 << 1
};
/**
@ -88,6 +123,13 @@ ShaderManager.DRAW_MODE = {
*/
ShaderManager.prototype.getShader = function (drawMode, effectBits) {
var cache = this._shaderCache[drawMode];
if ((drawMode & ShaderManager.DRAW_MODE.silhouette) != 0) {
// Silhouette mode isn't affected by these effects.
effectBits &= ~(
ShaderManager.EFFECT_INFO.color.mask |
ShaderManager.EFFECT_INFO.brightness.mask
);
}
var shader = cache[effectBits];
if (!shader) {
shader = cache[effectBits] = this._buildShader(drawMode, effectBits);
@ -105,9 +147,15 @@ ShaderManager.prototype.getShader = function (drawMode, effectBits) {
ShaderManager.prototype._buildShader = function (drawMode, effectBits) {
var numEffects = ShaderManager.EFFECTS.length;
var defines = [
'#define DRAW_MODE_' + drawMode
];
var defines = [];
for (var drawModeName in ShaderManager.DRAW_MODE) {
if (ShaderManager.DRAW_MODE.hasOwnProperty(drawModeName)) {
var mask = ShaderManager.DRAW_MODE[drawModeName];
if ((drawMode & mask) != 0) {
defines.push('#define DRAW_MODE_' + drawModeName);
}
}
}
for (var index = 0; index < numEffects; ++index) {
if ((effectBits & (1 << index)) != 0) {
defines.push('#define ENABLE_' + ShaderManager.EFFECTS[index]);