* draw pen skin lines and stamps to a framebuffer * skip reading pixels and draw stamp to framebuffer * update silhouette with readPixels * draw pen canvas to buffer when its dirty Composite lines and stamps on browser preferred side (cpu/gpu) until the export texture is needed. Then blend the canvas with the current buffer contents. Updating this way invalidates useProgram optimization and the renderer currently does not have a way to know this. * draw lines on framebuffer through fragment shader * optimize draw regions and pen skin matrix creation * control draw regions * mobile gpus need high precision floats for line drawing * optimize cpu pen line math * sampled pen line caps * sampleless pen skin lines, lint, document pen skin buffer ops * add PenSkin._canvasDirty to constructor * remove DRAW_MODE_line * comment PenSkin reused memory, use memory in drawRectangle * turn draw region id's into optional method handlers A region ID object may have an enter and exit method on it that are used by default when entering and exiting that region. * remove old DRAW_MODE_line precision setting * standardize vert lines on 4 spaces * fixup! turn draw region id's into optional method handlers * do not draw when updating pen skin silhouette * do not premultiply stamp colors by alpha * fixup! do not draw when updating pen skin silhouette * do not premultiply line color * add a small rim around the line for aliasing * variable pen line alias amount * reverse offset pen line on y axis by relative alias amount Reverse the offset to keep small line overlap to a minimum. * fixup! reverse offset pen line on y axis by relative alias amount * medium precision gpu floats
183 lines
5.2 KiB
JavaScript
183 lines
5.2 KiB
JavaScript
const twgl = require('twgl.js');
|
|
|
|
|
|
class ShaderManager {
|
|
/**
|
|
* @param {WebGLRenderingContext} gl WebGL rendering context to create shaders for
|
|
* @constructor
|
|
*/
|
|
constructor (gl) {
|
|
this._gl = gl;
|
|
|
|
/**
|
|
* The cache of all shaders compiled so far, filled on demand.
|
|
* @type {Object<ShaderManager.DRAW_MODE, Array<ProgramInfo>>}
|
|
* @private
|
|
*/
|
|
this._shaderCache = {};
|
|
for (const modeName in ShaderManager.DRAW_MODE) {
|
|
if (ShaderManager.DRAW_MODE.hasOwnProperty(modeName)) {
|
|
this._shaderCache[modeName] = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the shader for a particular set of active effects.
|
|
* Build the shader if necessary.
|
|
* @param {ShaderManager.DRAW_MODE} drawMode Draw normally, silhouette, etc.
|
|
* @param {int} effectBits Bitmask representing the enabled effects.
|
|
* @returns {ProgramInfo} The shader's program info.
|
|
*/
|
|
getShader (drawMode, effectBits) {
|
|
const cache = this._shaderCache[drawMode];
|
|
if (drawMode === ShaderManager.DRAW_MODE.silhouette) {
|
|
// Silhouette mode isn't affected by these effects.
|
|
effectBits &= ~(ShaderManager.EFFECT_INFO.color.mask | ShaderManager.EFFECT_INFO.brightness.mask);
|
|
}
|
|
let shader = cache[effectBits];
|
|
if (!shader) {
|
|
shader = cache[effectBits] = this._buildShader(drawMode, effectBits);
|
|
}
|
|
return shader;
|
|
}
|
|
|
|
/**
|
|
* Build the shader for a particular set of active effects.
|
|
* @param {ShaderManager.DRAW_MODE} drawMode Draw normally, silhouette, etc.
|
|
* @param {int} effectBits Bitmask representing the enabled effects.
|
|
* @returns {ProgramInfo} The new shader's program info.
|
|
* @private
|
|
*/
|
|
_buildShader (drawMode, effectBits) {
|
|
const numEffects = ShaderManager.EFFECTS.length;
|
|
|
|
const defines = [
|
|
`#define DRAW_MODE_${drawMode}`
|
|
];
|
|
for (let index = 0; index < numEffects; ++index) {
|
|
if ((effectBits & (1 << index)) !== 0) {
|
|
defines.push(`#define ENABLE_${ShaderManager.EFFECTS[index]}`);
|
|
}
|
|
}
|
|
|
|
const definesText = `${defines.join('\n')}\n`;
|
|
|
|
/* eslint-disable global-require */
|
|
const vsFullText = definesText + require('raw-loader!./shaders/sprite.vert');
|
|
const fsFullText = definesText + require('raw-loader!./shaders/sprite.frag');
|
|
/* eslint-enable global-require */
|
|
|
|
return twgl.createProgramInfo(this._gl, [vsFullText, fsFullText]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {object} ShaderManager.Effect
|
|
* @prop {int} mask - The bit in 'effectBits' representing the effect.
|
|
* @prop {function} converter - 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.
|
|
* @prop {boolean} shapeChanges - Whether the effect could change the drawn shape.
|
|
*/
|
|
|
|
/**
|
|
* Mapping of each effect name to info about that effect.
|
|
* @enum {ShaderManager.Effect}
|
|
*/
|
|
ShaderManager.EFFECT_INFO = {
|
|
/** Color effect */
|
|
color: {
|
|
uniformName: 'u_color',
|
|
mask: 1 << 0,
|
|
converter: x => (x / 200) % 1,
|
|
shapeChanges: false
|
|
},
|
|
/** Fisheye effect */
|
|
fisheye: {
|
|
uniformName: 'u_fisheye',
|
|
mask: 1 << 1,
|
|
converter: x => Math.max(0, (x + 100) / 100),
|
|
shapeChanges: true
|
|
},
|
|
/** Whirl effect */
|
|
whirl: {
|
|
uniformName: 'u_whirl',
|
|
mask: 1 << 2,
|
|
converter: x => -x * Math.PI / 180,
|
|
shapeChanges: true
|
|
},
|
|
/** Pixelate effect */
|
|
pixelate: {
|
|
uniformName: 'u_pixelate',
|
|
mask: 1 << 3,
|
|
converter: x => Math.abs(x) / 10,
|
|
shapeChanges: true
|
|
},
|
|
/** Mosaic effect */
|
|
mosaic: {
|
|
uniformName: 'u_mosaic',
|
|
mask: 1 << 4,
|
|
converter: x => {
|
|
x = Math.round((Math.abs(x) + 10) / 10);
|
|
/** @todo cap by Math.min(srcWidth, srcHeight) */
|
|
return Math.max(1, Math.min(x, 512));
|
|
},
|
|
shapeChanges: true
|
|
},
|
|
/** Brightness effect */
|
|
brightness: {
|
|
uniformName: 'u_brightness',
|
|
mask: 1 << 5,
|
|
converter: x => Math.max(-100, Math.min(x, 100)) / 100,
|
|
shapeChanges: false
|
|
},
|
|
/** Ghost effect */
|
|
ghost: {
|
|
uniformName: 'u_ghost',
|
|
mask: 1 << 6,
|
|
converter: x => 1 - (Math.max(0, Math.min(x, 100)) / 100),
|
|
shapeChanges: false
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The name of each supported effect.
|
|
* @type {Array}
|
|
*/
|
|
ShaderManager.EFFECTS = Object.keys(ShaderManager.EFFECT_INFO);
|
|
|
|
/**
|
|
* The available draw modes.
|
|
* @readonly
|
|
* @enum {string}
|
|
*/
|
|
ShaderManager.DRAW_MODE = {
|
|
/**
|
|
* Draw normally.
|
|
*/
|
|
default: 'default',
|
|
|
|
/**
|
|
* Draw a silhouette using a solid color.
|
|
*/
|
|
silhouette: 'silhouette',
|
|
|
|
/**
|
|
* Draw only the parts of the drawable which match a particular color.
|
|
*/
|
|
colorMask: 'colorMask',
|
|
|
|
/**
|
|
* Sample a "texture" to draw a line with caps.
|
|
*/
|
|
lineSample: 'lineSample',
|
|
|
|
/**
|
|
* Draw normally except for pre-multiplied alpha
|
|
*/
|
|
stamp: 'stamp'
|
|
};
|
|
|
|
module.exports = ShaderManager;
|