scratch-render/src/ShaderManager.js
Michael "Z" Goddard 4c8bc5d806
Framebuffer PenSkin (#319)
* 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
2018-08-08 14:30:51 -04:00

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;