|
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 "use strict"; |
|
7 |
|
8 const {Cc, Ci, Cu} = require("chrome"); |
|
9 |
|
10 let TiltUtils = require("devtools/tilt/tilt-utils"); |
|
11 let {TiltMath, mat4} = require("devtools/tilt/tilt-math"); |
|
12 |
|
13 Cu.import("resource://gre/modules/Services.jsm"); |
|
14 |
|
15 const WEBGL_CONTEXT_NAME = "experimental-webgl"; |
|
16 |
|
17 |
|
18 /** |
|
19 * Module containing thin wrappers around low-level WebGL functions. |
|
20 */ |
|
21 let TiltGL = {}; |
|
22 module.exports = TiltGL; |
|
23 |
|
24 /** |
|
25 * Contains commonly used helper methods used in any 3D application. |
|
26 * |
|
27 * @param {HTMLCanvasElement} aCanvas |
|
28 * the canvas element used for rendering |
|
29 * @param {Function} onError |
|
30 * optional, function called if initialization failed |
|
31 * @param {Function} onLoad |
|
32 * optional, function called if initialization worked |
|
33 */ |
|
34 TiltGL.Renderer = function TGL_Renderer(aCanvas, onError, onLoad) |
|
35 { |
|
36 /** |
|
37 * The WebGL context obtained from the canvas element, used for drawing. |
|
38 */ |
|
39 this.context = TiltGL.create3DContext(aCanvas); |
|
40 |
|
41 // check if the context was created successfully |
|
42 if (!this.context) { |
|
43 TiltUtils.Output.alert("Firefox", TiltUtils.L10n.get("initTilt.error")); |
|
44 TiltUtils.Output.error(TiltUtils.L10n.get("initWebGL.error")); |
|
45 |
|
46 if ("function" === typeof onError) { |
|
47 onError(); |
|
48 } |
|
49 return; |
|
50 } |
|
51 |
|
52 // set the default clear color and depth buffers |
|
53 this.context.clearColor(0, 0, 0, 0); |
|
54 this.context.clearDepth(1); |
|
55 |
|
56 /** |
|
57 * Variables representing the current framebuffer width and height. |
|
58 */ |
|
59 this.width = aCanvas.width; |
|
60 this.height = aCanvas.height; |
|
61 this.initialWidth = this.width; |
|
62 this.initialHeight = this.height; |
|
63 |
|
64 /** |
|
65 * The current model view matrix. |
|
66 */ |
|
67 this.mvMatrix = mat4.identity(mat4.create()); |
|
68 |
|
69 /** |
|
70 * The current projection matrix. |
|
71 */ |
|
72 this.projMatrix = mat4.identity(mat4.create()); |
|
73 |
|
74 /** |
|
75 * The current fill color applied to any objects which can be filled. |
|
76 * These are rectangles, circles, boxes, 2d or 3d primitives in general. |
|
77 */ |
|
78 this._fillColor = []; |
|
79 |
|
80 /** |
|
81 * The current stroke color applied to any objects which can be stroked. |
|
82 * This property mostly refers to lines. |
|
83 */ |
|
84 this._strokeColor = []; |
|
85 |
|
86 /** |
|
87 * Variable representing the current stroke weight. |
|
88 */ |
|
89 this._strokeWeightValue = 0; |
|
90 |
|
91 /** |
|
92 * A shader useful for drawing vertices with only a color component. |
|
93 */ |
|
94 this._colorShader = new TiltGL.Program(this.context, { |
|
95 vs: TiltGL.ColorShader.vs, |
|
96 fs: TiltGL.ColorShader.fs, |
|
97 attributes: ["vertexPosition"], |
|
98 uniforms: ["mvMatrix", "projMatrix", "fill"] |
|
99 }); |
|
100 |
|
101 // create helper functions to create shaders, meshes, buffers and textures |
|
102 this.Program = |
|
103 TiltGL.Program.bind(TiltGL.Program, this.context); |
|
104 this.VertexBuffer = |
|
105 TiltGL.VertexBuffer.bind(TiltGL.VertexBuffer, this.context); |
|
106 this.IndexBuffer = |
|
107 TiltGL.IndexBuffer.bind(TiltGL.IndexBuffer, this.context); |
|
108 this.Texture = |
|
109 TiltGL.Texture.bind(TiltGL.Texture, this.context); |
|
110 |
|
111 // set the default mvp matrices, tint, fill, stroke and other visual props. |
|
112 this.defaults(); |
|
113 |
|
114 // the renderer was created successfully |
|
115 if ("function" === typeof onLoad) { |
|
116 onLoad(); |
|
117 } |
|
118 }; |
|
119 |
|
120 TiltGL.Renderer.prototype = { |
|
121 |
|
122 /** |
|
123 * Clears the color and depth buffers. |
|
124 */ |
|
125 clear: function TGLR_clear() |
|
126 { |
|
127 let gl = this.context; |
|
128 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
|
129 }, |
|
130 |
|
131 /** |
|
132 * Sets if depth testing should be enabled or not. |
|
133 * Disabling could be useful when handling transparency (for example). |
|
134 * |
|
135 * @param {Boolean} aEnabledFlag |
|
136 * true if depth testing should be enabled |
|
137 */ |
|
138 depthTest: function TGLR_depthTest(aEnabledFlag) |
|
139 { |
|
140 let gl = this.context; |
|
141 |
|
142 if (aEnabledFlag) { |
|
143 gl.enable(gl.DEPTH_TEST); |
|
144 } else { |
|
145 gl.disable(gl.DEPTH_TEST); |
|
146 } |
|
147 }, |
|
148 |
|
149 /** |
|
150 * Sets if stencil testing should be enabled or not. |
|
151 * |
|
152 * @param {Boolean} aEnabledFlag |
|
153 * true if stencil testing should be enabled |
|
154 */ |
|
155 stencilTest: function TGLR_stencilTest(aEnabledFlag) |
|
156 { |
|
157 let gl = this.context; |
|
158 |
|
159 if (aEnabledFlag) { |
|
160 gl.enable(gl.STENCIL_TEST); |
|
161 } else { |
|
162 gl.disable(gl.STENCIL_TEST); |
|
163 } |
|
164 }, |
|
165 |
|
166 /** |
|
167 * Sets cull face, either "front", "back" or disabled. |
|
168 * |
|
169 * @param {String} aModeFlag |
|
170 * blending mode, either "front", "back", "both" or falsy |
|
171 */ |
|
172 cullFace: function TGLR_cullFace(aModeFlag) |
|
173 { |
|
174 let gl = this.context; |
|
175 |
|
176 switch (aModeFlag) { |
|
177 case "front": |
|
178 gl.enable(gl.CULL_FACE); |
|
179 gl.cullFace(gl.FRONT); |
|
180 break; |
|
181 case "back": |
|
182 gl.enable(gl.CULL_FACE); |
|
183 gl.cullFace(gl.BACK); |
|
184 break; |
|
185 case "both": |
|
186 gl.enable(gl.CULL_FACE); |
|
187 gl.cullFace(gl.FRONT_AND_BACK); |
|
188 break; |
|
189 default: |
|
190 gl.disable(gl.CULL_FACE); |
|
191 } |
|
192 }, |
|
193 |
|
194 /** |
|
195 * Specifies the orientation of front-facing polygons. |
|
196 * |
|
197 * @param {String} aModeFlag |
|
198 * either "cw" or "ccw" |
|
199 */ |
|
200 frontFace: function TGLR_frontFace(aModeFlag) |
|
201 { |
|
202 let gl = this.context; |
|
203 |
|
204 switch (aModeFlag) { |
|
205 case "cw": |
|
206 gl.frontFace(gl.CW); |
|
207 break; |
|
208 case "ccw": |
|
209 gl.frontFace(gl.CCW); |
|
210 break; |
|
211 } |
|
212 }, |
|
213 |
|
214 /** |
|
215 * Sets blending, either "alpha" or "add" (additive blending). |
|
216 * Anything else disables blending. |
|
217 * |
|
218 * @param {String} aModeFlag |
|
219 * blending mode, either "alpha", "add" or falsy |
|
220 */ |
|
221 blendMode: function TGLR_blendMode(aModeFlag) |
|
222 { |
|
223 let gl = this.context; |
|
224 |
|
225 switch (aModeFlag) { |
|
226 case "alpha": |
|
227 gl.enable(gl.BLEND); |
|
228 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); |
|
229 break; |
|
230 case "add": |
|
231 gl.enable(gl.BLEND); |
|
232 gl.blendFunc(gl.SRC_ALPHA, gl.ONE); |
|
233 break; |
|
234 default: |
|
235 gl.disable(gl.BLEND); |
|
236 } |
|
237 }, |
|
238 |
|
239 /** |
|
240 * Helper function to activate the color shader. |
|
241 * |
|
242 * @param {TiltGL.VertexBuffer} aVerticesBuffer |
|
243 * a buffer of vertices positions |
|
244 * @param {Array} aColor |
|
245 * the color fill to be used as [r, g, b, a] with 0..1 range |
|
246 * @param {Array} aMvMatrix |
|
247 * the model view matrix |
|
248 * @param {Array} aProjMatrix |
|
249 * the projection matrix |
|
250 */ |
|
251 useColorShader: function TGLR_useColorShader( |
|
252 aVerticesBuffer, aColor, aMvMatrix, aProjMatrix) |
|
253 { |
|
254 let program = this._colorShader; |
|
255 |
|
256 // use this program |
|
257 program.use(); |
|
258 |
|
259 // bind the attributes and uniforms as necessary |
|
260 program.bindVertexBuffer("vertexPosition", aVerticesBuffer); |
|
261 program.bindUniformMatrix("mvMatrix", aMvMatrix || this.mvMatrix); |
|
262 program.bindUniformMatrix("projMatrix", aProjMatrix || this.projMatrix); |
|
263 program.bindUniformVec4("fill", aColor || this._fillColor); |
|
264 }, |
|
265 |
|
266 /** |
|
267 * Draws bound vertex buffers using the specified parameters. |
|
268 * |
|
269 * @param {Number} aDrawMode |
|
270 * WebGL enum, like TRIANGLES |
|
271 * @param {Number} aCount |
|
272 * the number of indices to be rendered |
|
273 */ |
|
274 drawVertices: function TGLR_drawVertices(aDrawMode, aCount) |
|
275 { |
|
276 this.context.drawArrays(aDrawMode, 0, aCount); |
|
277 }, |
|
278 |
|
279 /** |
|
280 * Draws bound vertex buffers using the specified parameters. |
|
281 * This function also makes use of an index buffer. |
|
282 * |
|
283 * @param {Number} aDrawMode |
|
284 * WebGL enum, like TRIANGLES |
|
285 * @param {TiltGL.IndexBuffer} aIndicesBuffer |
|
286 * indices for the vertices buffer |
|
287 */ |
|
288 drawIndexedVertices: function TGLR_drawIndexedVertices( |
|
289 aDrawMode, aIndicesBuffer) |
|
290 { |
|
291 let gl = this.context; |
|
292 |
|
293 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, aIndicesBuffer._ref); |
|
294 gl.drawElements(aDrawMode, aIndicesBuffer.numItems, gl.UNSIGNED_SHORT, 0); |
|
295 }, |
|
296 |
|
297 /** |
|
298 * Sets the current fill color. |
|
299 * |
|
300 * @param {Array} aColor |
|
301 * the color fill to be used as [r, g, b, a] with 0..1 range |
|
302 * @param {Number} aMultiplyAlpha |
|
303 * optional, scalar to multiply the alpha element with |
|
304 */ |
|
305 fill: function TGLR_fill(aColor, aMultiplyAlpha) |
|
306 { |
|
307 let fill = this._fillColor; |
|
308 |
|
309 fill[0] = aColor[0]; |
|
310 fill[1] = aColor[1]; |
|
311 fill[2] = aColor[2]; |
|
312 fill[3] = aColor[3] * (aMultiplyAlpha || 1); |
|
313 }, |
|
314 |
|
315 /** |
|
316 * Sets the current stroke color. |
|
317 * |
|
318 * @param {Array} aColor |
|
319 * the color stroke to be used as [r, g, b, a] with 0..1 range |
|
320 * @param {Number} aMultiplyAlpha |
|
321 * optional, scalar to multiply the alpha element with |
|
322 */ |
|
323 stroke: function TGLR_stroke(aColor, aMultiplyAlpha) |
|
324 { |
|
325 let stroke = this._strokeColor; |
|
326 |
|
327 stroke[0] = aColor[0]; |
|
328 stroke[1] = aColor[1]; |
|
329 stroke[2] = aColor[2]; |
|
330 stroke[3] = aColor[3] * (aMultiplyAlpha || 1); |
|
331 }, |
|
332 |
|
333 /** |
|
334 * Sets the current stroke weight (line width). |
|
335 * |
|
336 * @param {Number} aWeight |
|
337 * the stroke weight |
|
338 */ |
|
339 strokeWeight: function TGLR_strokeWeight(aWeight) |
|
340 { |
|
341 if (this._strokeWeightValue !== aWeight) { |
|
342 this._strokeWeightValue = aWeight; |
|
343 this.context.lineWidth(aWeight); |
|
344 } |
|
345 }, |
|
346 |
|
347 /** |
|
348 * Sets a default perspective projection, with the near frustum rectangle |
|
349 * mapped to the canvas width and height bounds. |
|
350 */ |
|
351 perspective: function TGLR_perspective() |
|
352 { |
|
353 let fov = 45; |
|
354 let w = this.width; |
|
355 let h = this.height; |
|
356 let x = w / 2; |
|
357 let y = h / 2; |
|
358 let z = y / Math.tan(TiltMath.radians(fov) / 2); |
|
359 let aspect = w / h; |
|
360 let znear = z / 10; |
|
361 let zfar = z * 10; |
|
362 |
|
363 mat4.perspective(fov, aspect, znear, zfar, this.projMatrix, -1); |
|
364 mat4.translate(this.projMatrix, [-x, -y, -z]); |
|
365 mat4.identity(this.mvMatrix); |
|
366 }, |
|
367 |
|
368 /** |
|
369 * Sets a default orthographic projection (recommended for 2d rendering). |
|
370 */ |
|
371 ortho: function TGLR_ortho() |
|
372 { |
|
373 mat4.ortho(0, this.width, this.height, 0, -1, 1, this.projMatrix); |
|
374 mat4.identity(this.mvMatrix); |
|
375 }, |
|
376 |
|
377 /** |
|
378 * Sets a custom projection matrix. |
|
379 * @param {Array} matrix: the custom projection matrix to be used |
|
380 */ |
|
381 projection: function TGLR_projection(aMatrix) |
|
382 { |
|
383 mat4.set(aMatrix, this.projMatrix); |
|
384 mat4.identity(this.mvMatrix); |
|
385 }, |
|
386 |
|
387 /** |
|
388 * Resets the model view matrix to identity. |
|
389 * This is a default matrix with no rotation, no scaling, at (0, 0, 0); |
|
390 */ |
|
391 origin: function TGLR_origin() |
|
392 { |
|
393 mat4.identity(this.mvMatrix); |
|
394 }, |
|
395 |
|
396 /** |
|
397 * Transforms the model view matrix with a new matrix. |
|
398 * Useful for creating custom transformations. |
|
399 * |
|
400 * @param {Array} matrix: the matrix to be multiply the model view with |
|
401 */ |
|
402 transform: function TGLR_transform(aMatrix) |
|
403 { |
|
404 mat4.multiply(this.mvMatrix, aMatrix); |
|
405 }, |
|
406 |
|
407 /** |
|
408 * Translates the model view by the x, y and z coordinates. |
|
409 * |
|
410 * @param {Number} x |
|
411 * the x amount of translation |
|
412 * @param {Number} y |
|
413 * the y amount of translation |
|
414 * @param {Number} z |
|
415 * optional, the z amount of translation |
|
416 */ |
|
417 translate: function TGLR_translate(x, y, z) |
|
418 { |
|
419 mat4.translate(this.mvMatrix, [x, y, z || 0]); |
|
420 }, |
|
421 |
|
422 /** |
|
423 * Rotates the model view by a specified angle on the x, y and z axis. |
|
424 * |
|
425 * @param {Number} angle |
|
426 * the angle expressed in radians |
|
427 * @param {Number} x |
|
428 * the x axis of the rotation |
|
429 * @param {Number} y |
|
430 * the y axis of the rotation |
|
431 * @param {Number} z |
|
432 * the z axis of the rotation |
|
433 */ |
|
434 rotate: function TGLR_rotate(angle, x, y, z) |
|
435 { |
|
436 mat4.rotate(this.mvMatrix, angle, [x, y, z]); |
|
437 }, |
|
438 |
|
439 /** |
|
440 * Rotates the model view by a specified angle on the x axis. |
|
441 * |
|
442 * @param {Number} aAngle |
|
443 * the angle expressed in radians |
|
444 */ |
|
445 rotateX: function TGLR_rotateX(aAngle) |
|
446 { |
|
447 mat4.rotateX(this.mvMatrix, aAngle); |
|
448 }, |
|
449 |
|
450 /** |
|
451 * Rotates the model view by a specified angle on the y axis. |
|
452 * |
|
453 * @param {Number} aAngle |
|
454 * the angle expressed in radians |
|
455 */ |
|
456 rotateY: function TGLR_rotateY(aAngle) |
|
457 { |
|
458 mat4.rotateY(this.mvMatrix, aAngle); |
|
459 }, |
|
460 |
|
461 /** |
|
462 * Rotates the model view by a specified angle on the z axis. |
|
463 * |
|
464 * @param {Number} aAngle |
|
465 * the angle expressed in radians |
|
466 */ |
|
467 rotateZ: function TGLR_rotateZ(aAngle) |
|
468 { |
|
469 mat4.rotateZ(this.mvMatrix, aAngle); |
|
470 }, |
|
471 |
|
472 /** |
|
473 * Scales the model view by the x, y and z coordinates. |
|
474 * |
|
475 * @param {Number} x |
|
476 * the x amount of scaling |
|
477 * @param {Number} y |
|
478 * the y amount of scaling |
|
479 * @param {Number} z |
|
480 * optional, the z amount of scaling |
|
481 */ |
|
482 scale: function TGLR_scale(x, y, z) |
|
483 { |
|
484 mat4.scale(this.mvMatrix, [x, y, z || 1]); |
|
485 }, |
|
486 |
|
487 /** |
|
488 * Performs a custom interpolation between two matrices. |
|
489 * The result is saved in the first operand. |
|
490 * |
|
491 * @param {Array} aMat |
|
492 * the first matrix |
|
493 * @param {Array} aMat2 |
|
494 * the second matrix |
|
495 * @param {Number} aLerp |
|
496 * interpolation amount between the two inputs |
|
497 * @param {Number} aDamping |
|
498 * optional, scalar adjusting the interpolation amortization |
|
499 * @param {Number} aBalance |
|
500 * optional, scalar adjusting the interpolation shift ammount |
|
501 */ |
|
502 lerp: function TGLR_lerp(aMat, aMat2, aLerp, aDamping, aBalance) |
|
503 { |
|
504 if (aLerp < 0 || aLerp > 1) { |
|
505 return; |
|
506 } |
|
507 |
|
508 // calculate the interpolation factor based on the damping and step |
|
509 let f = Math.pow(1 - Math.pow(aLerp, aDamping || 1), 1 / aBalance || 1); |
|
510 |
|
511 // interpolate each element from the two matrices |
|
512 for (let i = 0, len = this.projMatrix.length; i < len; i++) { |
|
513 aMat[i] = aMat[i] + f * (aMat2[i] - aMat[i]); |
|
514 } |
|
515 }, |
|
516 |
|
517 /** |
|
518 * Resets the drawing style to default. |
|
519 */ |
|
520 defaults: function TGLR_defaults() |
|
521 { |
|
522 this.depthTest(true); |
|
523 this.stencilTest(false); |
|
524 this.cullFace(false); |
|
525 this.frontFace("ccw"); |
|
526 this.blendMode("alpha"); |
|
527 this.fill([1, 1, 1, 1]); |
|
528 this.stroke([0, 0, 0, 1]); |
|
529 this.strokeWeight(1); |
|
530 this.perspective(); |
|
531 this.origin(); |
|
532 }, |
|
533 |
|
534 /** |
|
535 * Draws a quad composed of four vertices. |
|
536 * Vertices must be in clockwise order, or else drawing will be distorted. |
|
537 * Do not abuse this function, it is quite slow. |
|
538 * |
|
539 * @param {Array} aV0 |
|
540 * the [x, y, z] position of the first triangle point |
|
541 * @param {Array} aV1 |
|
542 * the [x, y, z] position of the second triangle point |
|
543 * @param {Array} aV2 |
|
544 * the [x, y, z] position of the third triangle point |
|
545 * @param {Array} aV3 |
|
546 * the [x, y, z] position of the fourth triangle point |
|
547 */ |
|
548 quad: function TGLR_quad(aV0, aV1, aV2, aV3) |
|
549 { |
|
550 let gl = this.context; |
|
551 let fill = this._fillColor; |
|
552 let stroke = this._strokeColor; |
|
553 let vert = new TiltGL.VertexBuffer(gl, [aV0[0], aV0[1], aV0[2] || 0, |
|
554 aV1[0], aV1[1], aV1[2] || 0, |
|
555 aV2[0], aV2[1], aV2[2] || 0, |
|
556 aV3[0], aV3[1], aV3[2] || 0], 3); |
|
557 |
|
558 // use the necessary shader and draw the vertices |
|
559 this.useColorShader(vert, fill); |
|
560 this.drawVertices(gl.TRIANGLE_FAN, vert.numItems); |
|
561 |
|
562 this.useColorShader(vert, stroke); |
|
563 this.drawVertices(gl.LINE_LOOP, vert.numItems); |
|
564 |
|
565 TiltUtils.destroyObject(vert); |
|
566 }, |
|
567 |
|
568 /** |
|
569 * Function called when this object is destroyed. |
|
570 */ |
|
571 finalize: function TGLR_finalize() |
|
572 { |
|
573 if (this.context) { |
|
574 TiltUtils.destroyObject(this._colorShader); |
|
575 } |
|
576 } |
|
577 }; |
|
578 |
|
579 /** |
|
580 * Creates a vertex buffer containing an array of elements. |
|
581 * |
|
582 * @param {Object} aContext |
|
583 * a WebGL context |
|
584 * @param {Array} aElementsArray |
|
585 * an array of numbers (floats) |
|
586 * @param {Number} aItemSize |
|
587 * how many items create a block |
|
588 * @param {Number} aNumItems |
|
589 * optional, how many items to use from the array |
|
590 */ |
|
591 TiltGL.VertexBuffer = function TGL_VertexBuffer( |
|
592 aContext, aElementsArray, aItemSize, aNumItems) |
|
593 { |
|
594 /** |
|
595 * The parent WebGL context. |
|
596 */ |
|
597 this._context = aContext; |
|
598 |
|
599 /** |
|
600 * The array buffer. |
|
601 */ |
|
602 this._ref = null; |
|
603 |
|
604 /** |
|
605 * Array of number components contained in the buffer. |
|
606 */ |
|
607 this.components = null; |
|
608 |
|
609 /** |
|
610 * Variables defining the internal structure of the buffer. |
|
611 */ |
|
612 this.itemSize = 0; |
|
613 this.numItems = 0; |
|
614 |
|
615 // if the array is specified in the constructor, initialize directly |
|
616 if (aElementsArray) { |
|
617 this.initBuffer(aElementsArray, aItemSize, aNumItems); |
|
618 } |
|
619 }; |
|
620 |
|
621 TiltGL.VertexBuffer.prototype = { |
|
622 |
|
623 /** |
|
624 * Initializes buffer data to be used for drawing, using an array of floats. |
|
625 * The "aNumItems" param can be specified to use only a portion of the array. |
|
626 * |
|
627 * @param {Array} aElementsArray |
|
628 * an array of floats |
|
629 * @param {Number} aItemSize |
|
630 * how many items create a block |
|
631 * @param {Number} aNumItems |
|
632 * optional, how many items to use from the array |
|
633 */ |
|
634 initBuffer: function TGLVB_initBuffer(aElementsArray, aItemSize, aNumItems) |
|
635 { |
|
636 let gl = this._context; |
|
637 |
|
638 // the aNumItems parameter is optional, we can compute it if not specified |
|
639 aNumItems = aNumItems || aElementsArray.length / aItemSize; |
|
640 |
|
641 // create the Float32Array using the elements array |
|
642 this.components = new Float32Array(aElementsArray); |
|
643 |
|
644 // create an array buffer and bind the elements as a Float32Array |
|
645 this._ref = gl.createBuffer(); |
|
646 gl.bindBuffer(gl.ARRAY_BUFFER, this._ref); |
|
647 gl.bufferData(gl.ARRAY_BUFFER, this.components, gl.STATIC_DRAW); |
|
648 |
|
649 // remember some properties, useful when binding the buffer to a shader |
|
650 this.itemSize = aItemSize; |
|
651 this.numItems = aNumItems; |
|
652 }, |
|
653 |
|
654 /** |
|
655 * Function called when this object is destroyed. |
|
656 */ |
|
657 finalize: function TGLVB_finalize() |
|
658 { |
|
659 if (this._context) { |
|
660 this._context.deleteBuffer(this._ref); |
|
661 } |
|
662 } |
|
663 }; |
|
664 |
|
665 /** |
|
666 * Creates an index buffer containing an array of indices. |
|
667 * |
|
668 * @param {Object} aContext |
|
669 * a WebGL context |
|
670 * @param {Array} aElementsArray |
|
671 * an array of unsigned integers |
|
672 * @param {Number} aNumItems |
|
673 * optional, how many items to use from the array |
|
674 */ |
|
675 TiltGL.IndexBuffer = function TGL_IndexBuffer( |
|
676 aContext, aElementsArray, aNumItems) |
|
677 { |
|
678 /** |
|
679 * The parent WebGL context. |
|
680 */ |
|
681 this._context = aContext; |
|
682 |
|
683 /** |
|
684 * The element array buffer. |
|
685 */ |
|
686 this._ref = null; |
|
687 |
|
688 /** |
|
689 * Array of number components contained in the buffer. |
|
690 */ |
|
691 this.components = null; |
|
692 |
|
693 /** |
|
694 * Variables defining the internal structure of the buffer. |
|
695 */ |
|
696 this.itemSize = 0; |
|
697 this.numItems = 0; |
|
698 |
|
699 // if the array is specified in the constructor, initialize directly |
|
700 if (aElementsArray) { |
|
701 this.initBuffer(aElementsArray, aNumItems); |
|
702 } |
|
703 }; |
|
704 |
|
705 TiltGL.IndexBuffer.prototype = { |
|
706 |
|
707 /** |
|
708 * Initializes a buffer of vertex indices, using an array of unsigned ints. |
|
709 * The item size will automatically default to 1, and the "numItems" will be |
|
710 * equal to the number of items in the array if not specified. |
|
711 * |
|
712 * @param {Array} aElementsArray |
|
713 * an array of numbers (unsigned integers) |
|
714 * @param {Number} aNumItems |
|
715 * optional, how many items to use from the array |
|
716 */ |
|
717 initBuffer: function TGLIB_initBuffer(aElementsArray, aNumItems) |
|
718 { |
|
719 let gl = this._context; |
|
720 |
|
721 // the aNumItems parameter is optional, we can compute it if not specified |
|
722 aNumItems = aNumItems || aElementsArray.length; |
|
723 |
|
724 // create the Uint16Array using the elements array |
|
725 this.components = new Uint16Array(aElementsArray); |
|
726 |
|
727 // create an array buffer and bind the elements as a Uint16Array |
|
728 this._ref = gl.createBuffer(); |
|
729 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._ref); |
|
730 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.components, gl.STATIC_DRAW); |
|
731 |
|
732 // remember some properties, useful when binding the buffer to a shader |
|
733 this.itemSize = 1; |
|
734 this.numItems = aNumItems; |
|
735 }, |
|
736 |
|
737 /** |
|
738 * Function called when this object is destroyed. |
|
739 */ |
|
740 finalize: function TGLIB_finalize() |
|
741 { |
|
742 if (this._context) { |
|
743 this._context.deleteBuffer(this._ref); |
|
744 } |
|
745 } |
|
746 }; |
|
747 |
|
748 /** |
|
749 * A program is composed of a vertex and a fragment shader. |
|
750 * |
|
751 * @param {Object} aProperties |
|
752 * optional, an object containing the following properties: |
|
753 * {String} vs: the vertex shader source code |
|
754 * {String} fs: the fragment shader source code |
|
755 * {Array} attributes: an array of attributes as strings |
|
756 * {Array} uniforms: an array of uniforms as strings |
|
757 */ |
|
758 TiltGL.Program = function(aContext, aProperties) |
|
759 { |
|
760 // make sure the properties parameter is a valid object |
|
761 aProperties = aProperties || {}; |
|
762 |
|
763 /** |
|
764 * The parent WebGL context. |
|
765 */ |
|
766 this._context = aContext; |
|
767 |
|
768 /** |
|
769 * A reference to the actual GLSL program. |
|
770 */ |
|
771 this._ref = null; |
|
772 |
|
773 /** |
|
774 * Each program has an unique id assigned. |
|
775 */ |
|
776 this._id = -1; |
|
777 |
|
778 /** |
|
779 * Two arrays: an attributes array, containing all the cached attributes |
|
780 * and a uniforms array, containing all the cached uniforms. |
|
781 */ |
|
782 this._attributes = null; |
|
783 this._uniforms = null; |
|
784 |
|
785 // if the sources are specified in the constructor, initialize directly |
|
786 if (aProperties.vs && aProperties.fs) { |
|
787 this.initProgram(aProperties); |
|
788 } |
|
789 }; |
|
790 |
|
791 TiltGL.Program.prototype = { |
|
792 |
|
793 /** |
|
794 * Initializes a shader program, using specified source code as strings. |
|
795 * |
|
796 * @param {Object} aProperties |
|
797 * an object containing the following properties: |
|
798 * {String} vs: the vertex shader source code |
|
799 * {String} fs: the fragment shader source code |
|
800 * {Array} attributes: an array of attributes as strings |
|
801 * {Array} uniforms: an array of uniforms as strings |
|
802 */ |
|
803 initProgram: function TGLP_initProgram(aProperties) |
|
804 { |
|
805 this._ref = TiltGL.ProgramUtils.create(this._context, aProperties); |
|
806 |
|
807 // cache for faster access |
|
808 this._id = this._ref.id; |
|
809 this._attributes = this._ref.attributes; |
|
810 this._uniforms = this._ref.uniforms; |
|
811 |
|
812 // cleanup |
|
813 delete this._ref.id; |
|
814 delete this._ref.attributes; |
|
815 delete this._ref.uniforms; |
|
816 }, |
|
817 |
|
818 /** |
|
819 * Uses the shader program as current one for the WebGL context; it also |
|
820 * enables vertex attributes necessary to enable when using this program. |
|
821 * This method also does some useful caching, as the function "useProgram" |
|
822 * could take quite a lot of time. |
|
823 */ |
|
824 use: function TGLP_use() |
|
825 { |
|
826 let id = this._id; |
|
827 let utils = TiltGL.ProgramUtils; |
|
828 |
|
829 // check if the program wasn't already active |
|
830 if (utils._activeProgram !== id) { |
|
831 utils._activeProgram = id; |
|
832 |
|
833 // use the the program if it wasn't already set |
|
834 this._context.useProgram(this._ref); |
|
835 this.cleanupVertexAttrib(); |
|
836 |
|
837 // enable any necessary vertex attributes using the cache |
|
838 for each (let attribute in this._attributes) { |
|
839 this._context.enableVertexAttribArray(attribute); |
|
840 utils._enabledAttributes.push(attribute); |
|
841 } |
|
842 } |
|
843 }, |
|
844 |
|
845 /** |
|
846 * Disables all currently enabled vertex attribute arrays. |
|
847 */ |
|
848 cleanupVertexAttrib: function TGLP_cleanupVertexAttrib() |
|
849 { |
|
850 let utils = TiltGL.ProgramUtils; |
|
851 |
|
852 for each (let attribute in utils._enabledAttributes) { |
|
853 this._context.disableVertexAttribArray(attribute); |
|
854 } |
|
855 utils._enabledAttributes = []; |
|
856 }, |
|
857 |
|
858 /** |
|
859 * Binds a vertex buffer as an array buffer for a specific shader attribute. |
|
860 * |
|
861 * @param {String} aAtribute |
|
862 * the attribute name obtained from the shader |
|
863 * @param {Float32Array} aBuffer |
|
864 * the buffer to be bound |
|
865 */ |
|
866 bindVertexBuffer: function TGLP_bindVertexBuffer(aAtribute, aBuffer) |
|
867 { |
|
868 // get the cached attribute value from the shader |
|
869 let gl = this._context; |
|
870 let attr = this._attributes[aAtribute]; |
|
871 let size = aBuffer.itemSize; |
|
872 |
|
873 gl.bindBuffer(gl.ARRAY_BUFFER, aBuffer._ref); |
|
874 gl.vertexAttribPointer(attr, size, gl.FLOAT, false, 0, 0); |
|
875 }, |
|
876 |
|
877 /** |
|
878 * Binds a uniform matrix to the current shader. |
|
879 * |
|
880 * @param {String} aUniform |
|
881 * the uniform name to bind the variable to |
|
882 * @param {Float32Array} m |
|
883 * the matrix to be bound |
|
884 */ |
|
885 bindUniformMatrix: function TGLP_bindUniformMatrix(aUniform, m) |
|
886 { |
|
887 this._context.uniformMatrix4fv(this._uniforms[aUniform], false, m); |
|
888 }, |
|
889 |
|
890 /** |
|
891 * Binds a uniform vector of 4 elements to the current shader. |
|
892 * |
|
893 * @param {String} aUniform |
|
894 * the uniform name to bind the variable to |
|
895 * @param {Float32Array} v |
|
896 * the vector to be bound |
|
897 */ |
|
898 bindUniformVec4: function TGLP_bindUniformVec4(aUniform, v) |
|
899 { |
|
900 this._context.uniform4fv(this._uniforms[aUniform], v); |
|
901 }, |
|
902 |
|
903 /** |
|
904 * Binds a simple float element to the current shader. |
|
905 * |
|
906 * @param {String} aUniform |
|
907 * the uniform name to bind the variable to |
|
908 * @param {Number} v |
|
909 * the variable to be bound |
|
910 */ |
|
911 bindUniformFloat: function TGLP_bindUniformFloat(aUniform, f) |
|
912 { |
|
913 this._context.uniform1f(this._uniforms[aUniform], f); |
|
914 }, |
|
915 |
|
916 /** |
|
917 * Binds a uniform texture for a sampler to the current shader. |
|
918 * |
|
919 * @param {String} aSampler |
|
920 * the sampler name to bind the texture to |
|
921 * @param {TiltGL.Texture} aTexture |
|
922 * the texture to be bound |
|
923 */ |
|
924 bindTexture: function TGLP_bindTexture(aSampler, aTexture) |
|
925 { |
|
926 let gl = this._context; |
|
927 |
|
928 gl.activeTexture(gl.TEXTURE0); |
|
929 gl.bindTexture(gl.TEXTURE_2D, aTexture._ref); |
|
930 gl.uniform1i(this._uniforms[aSampler], 0); |
|
931 }, |
|
932 |
|
933 /** |
|
934 * Function called when this object is destroyed. |
|
935 */ |
|
936 finalize: function TGLP_finalize() |
|
937 { |
|
938 if (this._context) { |
|
939 this._context.useProgram(null); |
|
940 this._context.deleteProgram(this._ref); |
|
941 } |
|
942 } |
|
943 }; |
|
944 |
|
945 /** |
|
946 * Utility functions for handling GLSL shaders and programs. |
|
947 */ |
|
948 TiltGL.ProgramUtils = { |
|
949 |
|
950 /** |
|
951 * Initializes a shader program, using specified source code as strings, |
|
952 * returning the newly created shader program, by compiling and linking the |
|
953 * vertex and fragment shader. |
|
954 * |
|
955 * @param {Object} aContext |
|
956 * a WebGL context |
|
957 * @param {Object} aProperties |
|
958 * an object containing the following properties: |
|
959 * {String} vs: the vertex shader source code |
|
960 * {String} fs: the fragment shader source code |
|
961 * {Array} attributes: an array of attributes as strings |
|
962 * {Array} uniforms: an array of uniforms as strings |
|
963 */ |
|
964 create: function TGLPU_create(aContext, aProperties) |
|
965 { |
|
966 // make sure the properties parameter is a valid object |
|
967 aProperties = aProperties || {}; |
|
968 |
|
969 // compile the two shaders |
|
970 let vertShader = this.compile(aContext, aProperties.vs, "vertex"); |
|
971 let fragShader = this.compile(aContext, aProperties.fs, "fragment"); |
|
972 let program = this.link(aContext, vertShader, fragShader); |
|
973 |
|
974 aContext.deleteShader(vertShader); |
|
975 aContext.deleteShader(fragShader); |
|
976 |
|
977 return this.cache(aContext, aProperties, program); |
|
978 }, |
|
979 |
|
980 /** |
|
981 * Compiles a shader source of a specific type, either vertex or fragment. |
|
982 * |
|
983 * @param {Object} aContext |
|
984 * a WebGL context |
|
985 * @param {String} aShaderSource |
|
986 * the source code for the shader |
|
987 * @param {String} aShaderType |
|
988 * the shader type ("vertex" or "fragment") |
|
989 * |
|
990 * @return {WebGLShader} the compiled shader |
|
991 */ |
|
992 compile: function TGLPU_compile(aContext, aShaderSource, aShaderType) |
|
993 { |
|
994 let gl = aContext, shader, status; |
|
995 |
|
996 // make sure the shader source is valid |
|
997 if ("string" !== typeof aShaderSource || aShaderSource.length < 1) { |
|
998 TiltUtils.Output.error( |
|
999 TiltUtils.L10n.get("compileShader.source.error")); |
|
1000 return null; |
|
1001 } |
|
1002 |
|
1003 // also make sure the necessary shader mime type is valid |
|
1004 if (aShaderType === "vertex") { |
|
1005 shader = gl.createShader(gl.VERTEX_SHADER); |
|
1006 } else if (aShaderType === "fragment") { |
|
1007 shader = gl.createShader(gl.FRAGMENT_SHADER); |
|
1008 } else { |
|
1009 TiltUtils.Output.error( |
|
1010 TiltUtils.L10n.format("compileShader.type.error", [aShaderSource])); |
|
1011 return null; |
|
1012 } |
|
1013 |
|
1014 // set the shader source and compile it |
|
1015 gl.shaderSource(shader, aShaderSource); |
|
1016 gl.compileShader(shader); |
|
1017 |
|
1018 // remember the shader source (useful for debugging and caching) |
|
1019 shader.src = aShaderSource; |
|
1020 |
|
1021 // verify the compile status; if something went wrong, log the error |
|
1022 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
|
1023 status = gl.getShaderInfoLog(shader); |
|
1024 |
|
1025 TiltUtils.Output.error( |
|
1026 TiltUtils.L10n.format("compileShader.compile.error", [status])); |
|
1027 return null; |
|
1028 } |
|
1029 |
|
1030 // return the newly compiled shader from the specified source |
|
1031 return shader; |
|
1032 }, |
|
1033 |
|
1034 /** |
|
1035 * Links two compiled vertex or fragment shaders together to form a program. |
|
1036 * |
|
1037 * @param {Object} aContext |
|
1038 * a WebGL context |
|
1039 * @param {WebGLShader} aVertShader |
|
1040 * the compiled vertex shader |
|
1041 * @param {WebGLShader} aFragShader |
|
1042 * the compiled fragment shader |
|
1043 * |
|
1044 * @return {WebGLProgram} the newly created and linked shader program |
|
1045 */ |
|
1046 link: function TGLPU_link(aContext, aVertShader, aFragShader) |
|
1047 { |
|
1048 let gl = aContext, program, status; |
|
1049 |
|
1050 // create a program and attach the compiled vertex and fragment shaders |
|
1051 program = gl.createProgram(); |
|
1052 |
|
1053 // attach the vertex and fragment shaders to the program |
|
1054 gl.attachShader(program, aVertShader); |
|
1055 gl.attachShader(program, aFragShader); |
|
1056 gl.linkProgram(program); |
|
1057 |
|
1058 // verify the link status; if something went wrong, log the error |
|
1059 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { |
|
1060 status = gl.getProgramInfoLog(program); |
|
1061 |
|
1062 TiltUtils.Output.error( |
|
1063 TiltUtils.L10n.format("linkProgram.error", [status])); |
|
1064 return null; |
|
1065 } |
|
1066 |
|
1067 // generate an id for the program |
|
1068 program.id = this._count++; |
|
1069 |
|
1070 return program; |
|
1071 }, |
|
1072 |
|
1073 /** |
|
1074 * Caches shader attributes and uniforms as properties for a program object. |
|
1075 * |
|
1076 * @param {Object} aContext |
|
1077 * a WebGL context |
|
1078 * @param {Object} aProperties |
|
1079 * an object containing the following properties: |
|
1080 * {Array} attributes: optional, an array of attributes as strings |
|
1081 * {Array} uniforms: optional, an array of uniforms as strings |
|
1082 * @param {WebGLProgram} aProgram |
|
1083 * the shader program used for caching |
|
1084 * |
|
1085 * @return {WebGLProgram} the same program |
|
1086 */ |
|
1087 cache: function TGLPU_cache(aContext, aProperties, aProgram) |
|
1088 { |
|
1089 // make sure the properties parameter is a valid object |
|
1090 aProperties = aProperties || {}; |
|
1091 |
|
1092 // make sure the attributes and uniforms cache objects are created |
|
1093 aProgram.attributes = {}; |
|
1094 aProgram.uniforms = {}; |
|
1095 |
|
1096 Object.defineProperty(aProgram.attributes, "length", |
|
1097 { value: 0, writable: true, enumerable: false, configurable: true }); |
|
1098 |
|
1099 Object.defineProperty(aProgram.uniforms, "length", |
|
1100 { value: 0, writable: true, enumerable: false, configurable: true }); |
|
1101 |
|
1102 |
|
1103 let attr = aProperties.attributes; |
|
1104 let unif = aProperties.uniforms; |
|
1105 |
|
1106 if (attr) { |
|
1107 for (let i = 0, len = attr.length; i < len; i++) { |
|
1108 // try to get a shader attribute from the program |
|
1109 let param = attr[i]; |
|
1110 let loc = aContext.getAttribLocation(aProgram, param); |
|
1111 |
|
1112 if ("number" === typeof loc && loc > -1) { |
|
1113 // if we get an attribute location, store it |
|
1114 // bind the new parameter only if it was not already defined |
|
1115 if (aProgram.attributes[param] === undefined) { |
|
1116 aProgram.attributes[param] = loc; |
|
1117 aProgram.attributes.length++; |
|
1118 } |
|
1119 } |
|
1120 } |
|
1121 } |
|
1122 |
|
1123 if (unif) { |
|
1124 for (let i = 0, len = unif.length; i < len; i++) { |
|
1125 // try to get a shader uniform from the program |
|
1126 let param = unif[i]; |
|
1127 let loc = aContext.getUniformLocation(aProgram, param); |
|
1128 |
|
1129 if ("object" === typeof loc && loc) { |
|
1130 // if we get a uniform object, store it |
|
1131 // bind the new parameter only if it was not already defined |
|
1132 if (aProgram.uniforms[param] === undefined) { |
|
1133 aProgram.uniforms[param] = loc; |
|
1134 aProgram.uniforms.length++; |
|
1135 } |
|
1136 } |
|
1137 } |
|
1138 } |
|
1139 |
|
1140 return aProgram; |
|
1141 }, |
|
1142 |
|
1143 /** |
|
1144 * The total number of programs created. |
|
1145 */ |
|
1146 _count: 0, |
|
1147 |
|
1148 /** |
|
1149 * Represents the current active shader, identified by an id. |
|
1150 */ |
|
1151 _activeProgram: -1, |
|
1152 |
|
1153 /** |
|
1154 * Represents the current enabled attributes. |
|
1155 */ |
|
1156 _enabledAttributes: [] |
|
1157 }; |
|
1158 |
|
1159 /** |
|
1160 * This constructor creates a texture from an Image. |
|
1161 * |
|
1162 * @param {Object} aContext |
|
1163 * a WebGL context |
|
1164 * @param {Object} aProperties |
|
1165 * optional, an object containing the following properties: |
|
1166 * {Image} source: the source image for the texture |
|
1167 * {String} format: the format of the texture ("RGB" or "RGBA") |
|
1168 * {String} fill: optional, color to fill the transparent bits |
|
1169 * {String} stroke: optional, color to draw an outline |
|
1170 * {Number} strokeWeight: optional, the width of the outline |
|
1171 * {String} minFilter: either "nearest" or "linear" |
|
1172 * {String} magFilter: either "nearest" or "linear" |
|
1173 * {String} wrapS: either "repeat" or "clamp" |
|
1174 * {String} wrapT: either "repeat" or "clamp" |
|
1175 * {Boolean} mipmap: true if should generate mipmap |
|
1176 */ |
|
1177 TiltGL.Texture = function(aContext, aProperties) |
|
1178 { |
|
1179 // make sure the properties parameter is a valid object |
|
1180 aProperties = aProperties || {}; |
|
1181 |
|
1182 /** |
|
1183 * The parent WebGL context. |
|
1184 */ |
|
1185 this._context = aContext; |
|
1186 |
|
1187 /** |
|
1188 * A reference to the WebGL texture object. |
|
1189 */ |
|
1190 this._ref = null; |
|
1191 |
|
1192 /** |
|
1193 * Each texture has an unique id assigned. |
|
1194 */ |
|
1195 this._id = -1; |
|
1196 |
|
1197 /** |
|
1198 * Variables specifying the width and height of the texture. |
|
1199 * If these values are less than 0, the texture hasn't loaded yet. |
|
1200 */ |
|
1201 this.width = -1; |
|
1202 this.height = -1; |
|
1203 |
|
1204 /** |
|
1205 * Specifies if the texture has loaded or not. |
|
1206 */ |
|
1207 this.loaded = false; |
|
1208 |
|
1209 // if the image is specified in the constructor, initialize directly |
|
1210 if ("object" === typeof aProperties.source) { |
|
1211 this.initTexture(aProperties); |
|
1212 } else { |
|
1213 TiltUtils.Output.error( |
|
1214 TiltUtils.L10n.get("initTexture.source.error")); |
|
1215 } |
|
1216 }; |
|
1217 |
|
1218 TiltGL.Texture.prototype = { |
|
1219 |
|
1220 /** |
|
1221 * Initializes a texture from a pre-existing image or canvas. |
|
1222 * |
|
1223 * @param {Image} aImage |
|
1224 * the source image or canvas |
|
1225 * @param {Object} aProperties |
|
1226 * an object containing the following properties: |
|
1227 * {Image} source: the source image for the texture |
|
1228 * {String} format: the format of the texture ("RGB" or "RGBA") |
|
1229 * {String} fill: optional, color to fill the transparent bits |
|
1230 * {String} stroke: optional, color to draw an outline |
|
1231 * {Number} strokeWeight: optional, the width of the outline |
|
1232 * {String} minFilter: either "nearest" or "linear" |
|
1233 * {String} magFilter: either "nearest" or "linear" |
|
1234 * {String} wrapS: either "repeat" or "clamp" |
|
1235 * {String} wrapT: either "repeat" or "clamp" |
|
1236 * {Boolean} mipmap: true if should generate mipmap |
|
1237 */ |
|
1238 initTexture: function TGLT_initTexture(aProperties) |
|
1239 { |
|
1240 this._ref = TiltGL.TextureUtils.create(this._context, aProperties); |
|
1241 |
|
1242 // cache for faster access |
|
1243 this._id = this._ref.id; |
|
1244 this.width = this._ref.width; |
|
1245 this.height = this._ref.height; |
|
1246 this.loaded = true; |
|
1247 |
|
1248 // cleanup |
|
1249 delete this._ref.id; |
|
1250 delete this._ref.width; |
|
1251 delete this._ref.height; |
|
1252 delete this.onload; |
|
1253 }, |
|
1254 |
|
1255 /** |
|
1256 * Function called when this object is destroyed. |
|
1257 */ |
|
1258 finalize: function TGLT_finalize() |
|
1259 { |
|
1260 if (this._context) { |
|
1261 this._context.deleteTexture(this._ref); |
|
1262 } |
|
1263 } |
|
1264 }; |
|
1265 |
|
1266 /** |
|
1267 * Utility functions for creating and manipulating textures. |
|
1268 */ |
|
1269 TiltGL.TextureUtils = { |
|
1270 |
|
1271 /** |
|
1272 * Initializes a texture from a pre-existing image or canvas. |
|
1273 * |
|
1274 * @param {Object} aContext |
|
1275 * a WebGL context |
|
1276 * @param {Image} aImage |
|
1277 * the source image or canvas |
|
1278 * @param {Object} aProperties |
|
1279 * an object containing some of the following properties: |
|
1280 * {Image} source: the source image for the texture |
|
1281 * {String} format: the format of the texture ("RGB" or "RGBA") |
|
1282 * {String} fill: optional, color to fill the transparent bits |
|
1283 * {String} stroke: optional, color to draw an outline |
|
1284 * {Number} strokeWeight: optional, the width of the outline |
|
1285 * {String} minFilter: either "nearest" or "linear" |
|
1286 * {String} magFilter: either "nearest" or "linear" |
|
1287 * {String} wrapS: either "repeat" or "clamp" |
|
1288 * {String} wrapT: either "repeat" or "clamp" |
|
1289 * {Boolean} mipmap: true if should generate mipmap |
|
1290 * |
|
1291 * @return {WebGLTexture} the created texture |
|
1292 */ |
|
1293 create: function TGLTU_create(aContext, aProperties) |
|
1294 { |
|
1295 // make sure the properties argument is an object |
|
1296 aProperties = aProperties || {}; |
|
1297 |
|
1298 if (!aProperties.source) { |
|
1299 return null; |
|
1300 } |
|
1301 |
|
1302 let gl = aContext; |
|
1303 let width = aProperties.source.width; |
|
1304 let height = aProperties.source.height; |
|
1305 let format = gl[aProperties.format || "RGB"]; |
|
1306 |
|
1307 // make sure the image is power of two before binding to a texture |
|
1308 let source = this.resizeImageToPowerOfTwo(aProperties); |
|
1309 |
|
1310 // first, create the texture to hold the image data |
|
1311 let texture = gl.createTexture(); |
|
1312 |
|
1313 // attach the image data to the newly create texture |
|
1314 gl.bindTexture(gl.TEXTURE_2D, texture); |
|
1315 gl.texImage2D(gl.TEXTURE_2D, 0, format, format, gl.UNSIGNED_BYTE, source); |
|
1316 this.setTextureParams(gl, aProperties); |
|
1317 |
|
1318 // do some cleanup |
|
1319 gl.bindTexture(gl.TEXTURE_2D, null); |
|
1320 |
|
1321 // remember the width and the height |
|
1322 texture.width = width; |
|
1323 texture.height = height; |
|
1324 |
|
1325 // generate an id for the texture |
|
1326 texture.id = this._count++; |
|
1327 |
|
1328 return texture; |
|
1329 }, |
|
1330 |
|
1331 /** |
|
1332 * Sets texture parameters for the current texture binding. |
|
1333 * Optionally, you can also (re)set the current texture binding manually. |
|
1334 * |
|
1335 * @param {Object} aContext |
|
1336 * a WebGL context |
|
1337 * @param {Object} aProperties |
|
1338 * an object containing the texture properties |
|
1339 */ |
|
1340 setTextureParams: function TGLTU_setTextureParams(aContext, aProperties) |
|
1341 { |
|
1342 // make sure the properties argument is an object |
|
1343 aProperties = aProperties || {}; |
|
1344 |
|
1345 let gl = aContext; |
|
1346 let minFilter = gl.TEXTURE_MIN_FILTER; |
|
1347 let magFilter = gl.TEXTURE_MAG_FILTER; |
|
1348 let wrapS = gl.TEXTURE_WRAP_S; |
|
1349 let wrapT = gl.TEXTURE_WRAP_T; |
|
1350 |
|
1351 // bind a new texture if necessary |
|
1352 if (aProperties.texture) { |
|
1353 gl.bindTexture(gl.TEXTURE_2D, aProperties.texture.ref); |
|
1354 } |
|
1355 |
|
1356 // set the minification filter |
|
1357 if ("nearest" === aProperties.minFilter) { |
|
1358 gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.NEAREST); |
|
1359 } else if ("linear" === aProperties.minFilter && aProperties.mipmap) { |
|
1360 gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.LINEAR_MIPMAP_LINEAR); |
|
1361 } else { |
|
1362 gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.LINEAR); |
|
1363 } |
|
1364 |
|
1365 // set the magnification filter |
|
1366 if ("nearest" === aProperties.magFilter) { |
|
1367 gl.texParameteri(gl.TEXTURE_2D, magFilter, gl.NEAREST); |
|
1368 } else { |
|
1369 gl.texParameteri(gl.TEXTURE_2D, magFilter, gl.LINEAR); |
|
1370 } |
|
1371 |
|
1372 // set the wrapping on the x-axis for the texture |
|
1373 if ("repeat" === aProperties.wrapS) { |
|
1374 gl.texParameteri(gl.TEXTURE_2D, wrapS, gl.REPEAT); |
|
1375 } else { |
|
1376 gl.texParameteri(gl.TEXTURE_2D, wrapS, gl.CLAMP_TO_EDGE); |
|
1377 } |
|
1378 |
|
1379 // set the wrapping on the y-axis for the texture |
|
1380 if ("repeat" === aProperties.wrapT) { |
|
1381 gl.texParameteri(gl.TEXTURE_2D, wrapT, gl.REPEAT); |
|
1382 } else { |
|
1383 gl.texParameteri(gl.TEXTURE_2D, wrapT, gl.CLAMP_TO_EDGE); |
|
1384 } |
|
1385 |
|
1386 // generate mipmap if necessary |
|
1387 if (aProperties.mipmap) { |
|
1388 gl.generateMipmap(gl.TEXTURE_2D); |
|
1389 } |
|
1390 }, |
|
1391 |
|
1392 /** |
|
1393 * This shim renders a content window to a canvas element, but clamps the |
|
1394 * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE. |
|
1395 * |
|
1396 * @param {Window} aContentWindow |
|
1397 * the content window to get a texture from |
|
1398 * @param {Number} aMaxImageSize |
|
1399 * the maximum image size to be used |
|
1400 * |
|
1401 * @return {Image} the new content window image |
|
1402 */ |
|
1403 createContentImage: function TGLTU_createContentImage( |
|
1404 aContentWindow, aMaxImageSize) |
|
1405 { |
|
1406 // calculate the total width and height of the content page |
|
1407 let size = TiltUtils.DOM.getContentWindowDimensions(aContentWindow); |
|
1408 |
|
1409 // use a custom canvas element and a 2d context to draw the window |
|
1410 let canvas = TiltUtils.DOM.initCanvas(null); |
|
1411 canvas.width = TiltMath.clamp(size.width, 0, aMaxImageSize); |
|
1412 canvas.height = TiltMath.clamp(size.height, 0, aMaxImageSize); |
|
1413 |
|
1414 // use the 2d context.drawWindow() magic |
|
1415 let ctx = canvas.getContext("2d"); |
|
1416 ctx.drawWindow(aContentWindow, 0, 0, canvas.width, canvas.height, "#fff"); |
|
1417 |
|
1418 return canvas; |
|
1419 }, |
|
1420 |
|
1421 /** |
|
1422 * Scales an image's width and height to next power of two. |
|
1423 * If the image already has power of two sizes, it is immediately returned, |
|
1424 * otherwise, a new image is created. |
|
1425 * |
|
1426 * @param {Image} aImage |
|
1427 * the image to be scaled |
|
1428 * @param {Object} aProperties |
|
1429 * an object containing the following properties: |
|
1430 * {Image} source: the source image to resize |
|
1431 * {Boolean} resize: true to resize the image if it has npot dimensions |
|
1432 * {String} fill: optional, color to fill the transparent bits |
|
1433 * {String} stroke: optional, color to draw an image outline |
|
1434 * {Number} strokeWeight: optional, the width of the outline |
|
1435 * |
|
1436 * @return {Image} the resized image |
|
1437 */ |
|
1438 resizeImageToPowerOfTwo: function TGLTU_resizeImageToPowerOfTwo(aProperties) |
|
1439 { |
|
1440 // make sure the properties argument is an object |
|
1441 aProperties = aProperties || {}; |
|
1442 |
|
1443 if (!aProperties.source) { |
|
1444 return null; |
|
1445 } |
|
1446 |
|
1447 let isPowerOfTwoWidth = TiltMath.isPowerOfTwo(aProperties.source.width); |
|
1448 let isPowerOfTwoHeight = TiltMath.isPowerOfTwo(aProperties.source.height); |
|
1449 |
|
1450 // first check if the image is not already power of two |
|
1451 if (!aProperties.resize || (isPowerOfTwoWidth && isPowerOfTwoHeight)) { |
|
1452 return aProperties.source; |
|
1453 } |
|
1454 |
|
1455 // calculate the power of two dimensions for the npot image |
|
1456 let width = TiltMath.nextPowerOfTwo(aProperties.source.width); |
|
1457 let height = TiltMath.nextPowerOfTwo(aProperties.source.height); |
|
1458 |
|
1459 // create a canvas, then we will use a 2d context to scale the image |
|
1460 let canvas = TiltUtils.DOM.initCanvas(null, { |
|
1461 width: width, |
|
1462 height: height |
|
1463 }); |
|
1464 |
|
1465 let ctx = canvas.getContext("2d"); |
|
1466 |
|
1467 // optional fill (useful when handling transparent images) |
|
1468 if (aProperties.fill) { |
|
1469 ctx.fillStyle = aProperties.fill; |
|
1470 ctx.fillRect(0, 0, width, height); |
|
1471 } |
|
1472 |
|
1473 // draw the image with power of two dimensions |
|
1474 ctx.drawImage(aProperties.source, 0, 0, width, height); |
|
1475 |
|
1476 // optional stroke (useful when creating textures for edges) |
|
1477 if (aProperties.stroke) { |
|
1478 ctx.strokeStyle = aProperties.stroke; |
|
1479 ctx.lineWidth = aProperties.strokeWeight; |
|
1480 ctx.strokeRect(0, 0, width, height); |
|
1481 } |
|
1482 |
|
1483 return canvas; |
|
1484 }, |
|
1485 |
|
1486 /** |
|
1487 * The total number of textures created. |
|
1488 */ |
|
1489 _count: 0 |
|
1490 }; |
|
1491 |
|
1492 /** |
|
1493 * A color shader. The only useful thing it does is set the gl_FragColor. |
|
1494 * |
|
1495 * @param {Attribute} vertexPosition: the vertex position |
|
1496 * @param {Uniform} mvMatrix: the model view matrix |
|
1497 * @param {Uniform} projMatrix: the projection matrix |
|
1498 * @param {Uniform} color: the color to set the gl_FragColor to |
|
1499 */ |
|
1500 TiltGL.ColorShader = { |
|
1501 |
|
1502 /** |
|
1503 * Vertex shader. |
|
1504 */ |
|
1505 vs: [ |
|
1506 "attribute vec3 vertexPosition;", |
|
1507 |
|
1508 "uniform mat4 mvMatrix;", |
|
1509 "uniform mat4 projMatrix;", |
|
1510 |
|
1511 "void main() {", |
|
1512 " gl_Position = projMatrix * mvMatrix * vec4(vertexPosition, 1.0);", |
|
1513 "}" |
|
1514 ].join("\n"), |
|
1515 |
|
1516 /** |
|
1517 * Fragment shader. |
|
1518 */ |
|
1519 fs: [ |
|
1520 "#ifdef GL_ES", |
|
1521 "precision lowp float;", |
|
1522 "#endif", |
|
1523 |
|
1524 "uniform vec4 fill;", |
|
1525 |
|
1526 "void main() {", |
|
1527 " gl_FragColor = fill;", |
|
1528 "}" |
|
1529 ].join("\n") |
|
1530 }; |
|
1531 |
|
1532 TiltGL.isWebGLForceEnabled = function TGL_isWebGLForceEnabled() |
|
1533 { |
|
1534 return Services.prefs.getBoolPref("webgl.force-enabled"); |
|
1535 }; |
|
1536 |
|
1537 /** |
|
1538 * Tests if the WebGL OpenGL or Angle renderer is available using the |
|
1539 * GfxInfo service. |
|
1540 * |
|
1541 * @return {Boolean} true if WebGL is available |
|
1542 */ |
|
1543 TiltGL.isWebGLSupported = function TGL_isWebGLSupported() |
|
1544 { |
|
1545 let supported = false; |
|
1546 |
|
1547 try { |
|
1548 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); |
|
1549 let angle = gfxInfo.FEATURE_WEBGL_ANGLE; |
|
1550 let opengl = gfxInfo.FEATURE_WEBGL_OPENGL; |
|
1551 |
|
1552 // if either the Angle or OpenGL renderers are available, WebGL should work |
|
1553 supported = gfxInfo.getFeatureStatus(angle) === gfxInfo.FEATURE_NO_INFO || |
|
1554 gfxInfo.getFeatureStatus(opengl) === gfxInfo.FEATURE_NO_INFO; |
|
1555 } catch(e) { |
|
1556 if (e && e.message) { TiltUtils.Output.error(e.message); } |
|
1557 return false; |
|
1558 } |
|
1559 return supported; |
|
1560 }; |
|
1561 |
|
1562 /** |
|
1563 * Helper function to create a 3D context. |
|
1564 * |
|
1565 * @param {HTMLCanvasElement} aCanvas |
|
1566 * the canvas to get the WebGL context from |
|
1567 * @param {Object} aFlags |
|
1568 * optional, flags used for initialization |
|
1569 * |
|
1570 * @return {Object} the WebGL context, or null if anything failed |
|
1571 */ |
|
1572 TiltGL.create3DContext = function TGL_create3DContext(aCanvas, aFlags) |
|
1573 { |
|
1574 TiltGL.clearCache(); |
|
1575 |
|
1576 // try to get a valid context from an existing canvas |
|
1577 let context = null; |
|
1578 |
|
1579 try { |
|
1580 context = aCanvas.getContext(WEBGL_CONTEXT_NAME, aFlags); |
|
1581 } catch(e) { |
|
1582 if (e && e.message) { TiltUtils.Output.error(e.message); } |
|
1583 return null; |
|
1584 } |
|
1585 return context; |
|
1586 }; |
|
1587 |
|
1588 /** |
|
1589 * Clears the cache and sets all the variables to default. |
|
1590 */ |
|
1591 TiltGL.clearCache = function TGL_clearCache() |
|
1592 { |
|
1593 TiltGL.ProgramUtils._activeProgram = -1; |
|
1594 TiltGL.ProgramUtils._enabledAttributes = []; |
|
1595 }; |