UNPKG

5.52 kBJavaScriptView Raw
1const twgl = require('twgl.js');
2
3
4class ShaderManager {
5 /**
6 * @param {WebGLRenderingContext} gl WebGL rendering context to create shaders for
7 * @constructor
8 */
9 constructor (gl) {
10 this._gl = gl;
11
12 /**
13 * The cache of all shaders compiled so far, filled on demand.
14 * @type {Object<ShaderManager.DRAW_MODE, Array<ProgramInfo>>}
15 * @private
16 */
17 this._shaderCache = {};
18 for (const modeName in ShaderManager.DRAW_MODE) {
19 if (ShaderManager.DRAW_MODE.hasOwnProperty(modeName)) {
20 this._shaderCache[modeName] = [];
21 }
22 }
23 }
24
25 /**
26 * Fetch the shader for a particular set of active effects.
27 * Build the shader if necessary.
28 * @param {ShaderManager.DRAW_MODE} drawMode Draw normally, silhouette, etc.
29 * @param {int} effectBits Bitmask representing the enabled effects.
30 * @returns {ProgramInfo} The shader's program info.
31 */
32 getShader (drawMode, effectBits) {
33 const cache = this._shaderCache[drawMode];
34 if (drawMode === ShaderManager.DRAW_MODE.silhouette) {
35 // Silhouette mode isn't affected by these effects.
36 effectBits &= ~(ShaderManager.EFFECT_INFO.color.mask | ShaderManager.EFFECT_INFO.brightness.mask);
37 }
38 let shader = cache[effectBits];
39 if (!shader) {
40 shader = cache[effectBits] = this._buildShader(drawMode, effectBits);
41 }
42 return shader;
43 }
44
45 /**
46 * Build the shader for a particular set of active effects.
47 * @param {ShaderManager.DRAW_MODE} drawMode Draw normally, silhouette, etc.
48 * @param {int} effectBits Bitmask representing the enabled effects.
49 * @returns {ProgramInfo} The new shader's program info.
50 * @private
51 */
52 _buildShader (drawMode, effectBits) {
53 const numEffects = ShaderManager.EFFECTS.length;
54
55 const defines = [
56 `#define DRAW_MODE_${drawMode}`
57 ];
58 for (let index = 0; index < numEffects; ++index) {
59 if ((effectBits & (1 << index)) !== 0) {
60 defines.push(`#define ENABLE_${ShaderManager.EFFECTS[index]}`);
61 }
62 }
63
64 const definesText = `${defines.join('\n')}\n`;
65
66 /* eslint-disable global-require */
67 const vsFullText = definesText + require('raw-loader!./shaders/sprite.vert');
68 const fsFullText = definesText + require('raw-loader!./shaders/sprite.frag');
69 /* eslint-enable global-require */
70
71 return twgl.createProgramInfo(this._gl, [vsFullText, fsFullText]);
72 }
73}
74
75/**
76 * @typedef {object} ShaderManager.Effect
77 * @prop {int} mask - The bit in 'effectBits' representing the effect.
78 * @prop {function} converter - A conversion function which takes a Scratch value (generally in the range
79 * 0..100 or -100..100) and maps it to a value useful to the shader. This
80 * mapping may not be reversible.
81 * @prop {boolean} shapeChanges - Whether the effect could change the drawn shape.
82 */
83
84/**
85 * Mapping of each effect name to info about that effect.
86 * @enum {ShaderManager.Effect}
87 */
88ShaderManager.EFFECT_INFO = {
89 /** Color effect */
90 color: {
91 uniformName: 'u_color',
92 mask: 1 << 0,
93 converter: x => (x / 200) % 1,
94 shapeChanges: false
95 },
96 /** Fisheye effect */
97 fisheye: {
98 uniformName: 'u_fisheye',
99 mask: 1 << 1,
100 converter: x => Math.max(0, (x + 100) / 100),
101 shapeChanges: true
102 },
103 /** Whirl effect */
104 whirl: {
105 uniformName: 'u_whirl',
106 mask: 1 << 2,
107 converter: x => -x * Math.PI / 180,
108 shapeChanges: true
109 },
110 /** Pixelate effect */
111 pixelate: {
112 uniformName: 'u_pixelate',
113 mask: 1 << 3,
114 converter: x => Math.abs(x) / 10,
115 shapeChanges: true
116 },
117 /** Mosaic effect */
118 mosaic: {
119 uniformName: 'u_mosaic',
120 mask: 1 << 4,
121 converter: x => {
122 x = Math.round((Math.abs(x) + 10) / 10);
123 /** @todo cap by Math.min(srcWidth, srcHeight) */
124 return Math.max(1, Math.min(x, 512));
125 },
126 shapeChanges: true
127 },
128 /** Brightness effect */
129 brightness: {
130 uniformName: 'u_brightness',
131 mask: 1 << 5,
132 converter: x => Math.max(-100, Math.min(x, 100)) / 100,
133 shapeChanges: false
134 },
135 /** Ghost effect */
136 ghost: {
137 uniformName: 'u_ghost',
138 mask: 1 << 6,
139 converter: x => 1 - (Math.max(0, Math.min(x, 100)) / 100),
140 shapeChanges: false
141 }
142};
143
144/**
145 * The name of each supported effect.
146 * @type {Array}
147 */
148ShaderManager.EFFECTS = Object.keys(ShaderManager.EFFECT_INFO);
149
150/**
151 * The available draw modes.
152 * @readonly
153 * @enum {string}
154 */
155ShaderManager.DRAW_MODE = {
156 /**
157 * Draw normally. Its output will use premultiplied alpha.
158 */
159 default: 'default',
160
161 /**
162 * Draw with non-premultiplied alpha. Useful for reading pixels from GL into an ImageData object.
163 */
164 straightAlpha: 'straightAlpha',
165
166 /**
167 * Draw a silhouette using a solid color.
168 */
169 silhouette: 'silhouette',
170
171 /**
172 * Draw only the parts of the drawable which match a particular color.
173 */
174 colorMask: 'colorMask',
175
176 /**
177 * Draw a line with caps.
178 */
179 line: 'line',
180
181 /**
182 * Draw the background in a certain color. Must sometimes be used instead of gl.clear.
183 */
184 background: 'background'
185};
186
187module.exports = ShaderManager;