Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 "use strict";
6 const {Cc, Ci, Cu, Cr} = require("chrome");
7 const events = require("sdk/event/core");
8 const protocol = require("devtools/server/protocol");
9 const { ContentObserver } = require("devtools/content-observer");
11 const { on, once, off, emit } = events;
12 const { method, Arg, Option, RetVal } = protocol;
14 const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
16 // These traits are bit masks. Make sure they're powers of 2.
17 const PROGRAM_DEFAULT_TRAITS = 0;
18 const PROGRAM_BLACKBOX_TRAIT = 1;
19 const PROGRAM_HIGHLIGHT_TRAIT = 2;
21 exports.register = function(handle) {
22 handle.addTabActor(WebGLActor, "webglActor");
23 }
25 exports.unregister = function(handle) {
26 handle.removeTabActor(WebGLActor);
27 }
29 /**
30 * A WebGL Shader contributing to building a WebGL Program.
31 * You can either retrieve, or compile the source of a shader, which will
32 * automatically inflict the necessary changes to the WebGL state.
33 */
34 let ShaderActor = protocol.ActorClass({
35 typeName: "gl-shader",
37 /**
38 * Create the shader actor.
39 *
40 * @param DebuggerServerConnection conn
41 * The server connection.
42 * @param WebGLProgram program
43 * The WebGL program being linked.
44 * @param WebGLShader shader
45 * The cooresponding vertex or fragment shader.
46 * @param WebGLProxy proxy
47 * The proxy methods for the WebGL context owning this shader.
48 */
49 initialize: function(conn, program, shader, proxy) {
50 protocol.Actor.prototype.initialize.call(this, conn);
51 this.program = program;
52 this.shader = shader;
53 this.text = proxy.getShaderSource(shader);
54 this.linkedProxy = proxy;
55 },
57 /**
58 * Gets the source code for this shader.
59 */
60 getText: method(function() {
61 return this.text;
62 }, {
63 response: { text: RetVal("string") }
64 }),
66 /**
67 * Sets and compiles new source code for this shader.
68 */
69 compile: method(function(text) {
70 // Get the shader and corresponding program to change via the WebGL proxy.
71 let { linkedProxy: proxy, shader, program } = this;
73 // Get the new shader source to inject.
74 let oldText = this.text;
75 let newText = text;
77 // Overwrite the shader's source.
78 let error = proxy.compileShader(program, shader, this.text = newText);
80 // If something went wrong, revert to the previous shader.
81 if (error.compile || error.link) {
82 proxy.compileShader(program, shader, this.text = oldText);
83 return error;
84 }
85 return undefined;
86 }, {
87 request: { text: Arg(0, "string") },
88 response: { error: RetVal("nullable:json") }
89 })
90 });
92 /**
93 * The corresponding Front object for the ShaderActor.
94 */
95 let ShaderFront = protocol.FrontClass(ShaderActor, {
96 initialize: function(client, form) {
97 protocol.Front.prototype.initialize.call(this, client, form);
98 }
99 });
101 /**
102 * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
103 * of two shaders: a vertex shader and a fragment shader.
104 */
105 let ProgramActor = protocol.ActorClass({
106 typeName: "gl-program",
108 /**
109 * Create the program actor.
110 *
111 * @param DebuggerServerConnection conn
112 * The server connection.
113 * @param WebGLProgram program
114 * The WebGL program being linked.
115 * @param WebGLShader[] shaders
116 * The WebGL program's cooresponding vertex and fragment shaders.
117 * @param WebGLCache cache
118 * The state storage for the WebGL context owning this program.
119 * @param WebGLProxy proxy
120 * The proxy methods for the WebGL context owning this program.
121 */
122 initialize: function(conn, [program, shaders, cache, proxy]) {
123 protocol.Actor.prototype.initialize.call(this, conn);
124 this._shaderActorsCache = { vertex: null, fragment: null };
125 this.program = program;
126 this.shaders = shaders;
127 this.linkedCache = cache;
128 this.linkedProxy = proxy;
129 },
131 get ownerWindow() this.linkedCache.ownerWindow,
132 get ownerContext() this.linkedCache.ownerContext,
134 /**
135 * Gets the vertex shader linked to this program. This method guarantees
136 * a single actor instance per shader.
137 */
138 getVertexShader: method(function() {
139 return this._getShaderActor("vertex");
140 }, {
141 response: { shader: RetVal("gl-shader") }
142 }),
144 /**
145 * Gets the fragment shader linked to this program. This method guarantees
146 * a single actor instance per shader.
147 */
148 getFragmentShader: method(function() {
149 return this._getShaderActor("fragment");
150 }, {
151 response: { shader: RetVal("gl-shader") }
152 }),
154 /**
155 * Highlights any geometry rendered using this program.
156 */
157 highlight: method(function(tint) {
158 this.linkedProxy.highlightTint = tint;
159 this.linkedCache.setProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
160 }, {
161 request: { tint: Arg(0, "array:number") },
162 oneway: true
163 }),
165 /**
166 * Allows geometry to be rendered normally using this program.
167 */
168 unhighlight: method(function() {
169 this.linkedCache.unsetProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
170 }, {
171 oneway: true
172 }),
174 /**
175 * Prevents any geometry from being rendered using this program.
176 */
177 blackbox: method(function() {
178 this.linkedCache.setProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
179 }, {
180 oneway: true
181 }),
183 /**
184 * Allows geometry to be rendered using this program.
185 */
186 unblackbox: method(function() {
187 this.linkedCache.unsetProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
188 }, {
189 oneway: true
190 }),
192 /**
193 * Returns a cached ShaderActor instance based on the required shader type.
194 *
195 * @param string type
196 * Either "vertex" or "fragment".
197 * @return ShaderActor
198 * The respective shader actor instance.
199 */
200 _getShaderActor: function(type) {
201 if (this._shaderActorsCache[type]) {
202 return this._shaderActorsCache[type];
203 }
204 let proxy = this.linkedProxy;
205 let shader = proxy.getShaderOfType(this.shaders, type);
206 let shaderActor = new ShaderActor(this.conn, this.program, shader, proxy);
207 return this._shaderActorsCache[type] = shaderActor;
208 }
209 });
211 /**
212 * The corresponding Front object for the ProgramActor.
213 */
214 let ProgramFront = protocol.FrontClass(ProgramActor, {
215 initialize: function(client, form) {
216 protocol.Front.prototype.initialize.call(this, client, form);
217 }
218 });
220 /**
221 * The WebGL Actor handles simple interaction with a WebGL context via a few
222 * high-level methods. After instantiating this actor, you'll need to set it
223 * up by calling setup().
224 */
225 let WebGLActor = exports.WebGLActor = protocol.ActorClass({
226 typeName: "webgl",
227 initialize: function(conn, tabActor) {
228 protocol.Actor.prototype.initialize.call(this, conn);
229 this.tabActor = tabActor;
230 this._onGlobalCreated = this._onGlobalCreated.bind(this);
231 this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
232 this._onProgramLinked = this._onProgramLinked.bind(this);
233 },
234 destroy: function(conn) {
235 protocol.Actor.prototype.destroy.call(this, conn);
236 this.finalize();
237 },
239 /**
240 * Starts waiting for the current tab actor's document global to be
241 * created, in order to instrument the Canvas context and become
242 * aware of everything the content does WebGL-wise.
243 *
244 * See ContentObserver and WebGLInstrumenter for more details.
245 */
246 setup: method(function({ reload }) {
247 if (this._initialized) {
248 return;
249 }
250 this._initialized = true;
252 this._programActorsCache = [];
253 this._contentObserver = new ContentObserver(this.tabActor);
254 this._webglObserver = new WebGLObserver();
256 on(this._contentObserver, "global-created", this._onGlobalCreated);
257 on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
258 on(this._webglObserver, "program-linked", this._onProgramLinked);
260 if (reload) {
261 this.tabActor.window.location.reload();
262 }
263 }, {
264 request: { reload: Option(0, "boolean") },
265 oneway: true
266 }),
268 /**
269 * Stops listening for document global changes and puts this actor
270 * to hibernation. This method is called automatically just before the
271 * actor is destroyed.
272 */
273 finalize: method(function() {
274 if (!this._initialized) {
275 return;
276 }
277 this._initialized = false;
279 this._contentObserver.stopListening();
280 off(this._contentObserver, "global-created", this._onGlobalCreated);
281 off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
282 off(this._webglObserver, "program-linked", this._onProgramLinked);
284 this._programActorsCache = null;
285 this._contentObserver = null;
286 this._webglObserver = null;
287 }, {
288 oneway: true
289 }),
291 /**
292 * Gets an array of cached program actors for the current tab actor's window.
293 * This is useful for dealing with bfcache, when no new programs are linked.
294 */
295 getPrograms: method(function() {
296 let id = ContentObserver.GetInnerWindowID(this.tabActor.window);
297 return this._programActorsCache.filter(e => e.ownerWindow == id);
298 }, {
299 response: { programs: RetVal("array:gl-program") }
300 }),
302 /**
303 * Events emitted by this actor. The "program-linked" event is fired
304 * every time a WebGL program was linked with its respective two shaders.
305 */
306 events: {
307 "program-linked": {
308 type: "programLinked",
309 program: Arg(0, "gl-program")
310 }
311 },
313 /**
314 * Invoked whenever the current tab actor's document global is created.
315 */
316 _onGlobalCreated: function(window) {
317 WebGLInstrumenter.handle(window, this._webglObserver);
318 },
320 /**
321 * Invoked whenever the current tab actor's inner window is destroyed.
322 */
323 _onGlobalDestroyed: function(id) {
324 removeFromArray(this._programActorsCache, e => e.ownerWindow == id);
325 this._webglObserver.unregisterContextsForWindow(id);
326 },
328 /**
329 * Invoked whenever an observed WebGL context links a program.
330 */
331 _onProgramLinked: function(...args) {
332 let programActor = new ProgramActor(this.conn, args);
333 this._programActorsCache.push(programActor);
334 events.emit(this, "program-linked", programActor);
335 }
336 });
338 /**
339 * The corresponding Front object for the WebGLActor.
340 */
341 let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
342 initialize: function(client, { webglActor }) {
343 protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
344 client.addActorPool(this);
345 this.manage(this);
346 }
347 });
349 /**
350 * Instruments a HTMLCanvasElement with the appropriate inspection methods.
351 */
352 let WebGLInstrumenter = {
353 /**
354 * Overrides the getContext method in the HTMLCanvasElement prototype.
355 *
356 * @param nsIDOMWindow window
357 * The window to perform the instrumentation in.
358 * @param WebGLObserver observer
359 * The observer watching function calls in the context.
360 */
361 handle: function(window, observer) {
362 let self = this;
364 let id = ContentObserver.GetInnerWindowID(window);
365 let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
366 let canvasPrototype = canvasElem.prototype;
367 let originalGetContext = canvasPrototype.getContext;
369 /**
370 * Returns a drawing context on the canvas, or null if the context ID is
371 * not supported. This override creates an observer for the targeted context
372 * type and instruments specific functions in the targeted context instance.
373 */
374 canvasPrototype.getContext = function(name, options) {
375 // Make sure a context was able to be created.
376 let context = originalGetContext.call(this, name, options);
377 if (!context) {
378 return context;
379 }
380 // Make sure a WebGL (not a 2D) context will be instrumented.
381 if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
382 return context;
383 }
384 // Repeated calls to 'getContext' return the same instance, no need to
385 // instrument everything again.
386 if (observer.for(context)) {
387 return context;
388 }
390 // Create a separate state storage for this context.
391 observer.registerContextForWindow(id, context);
393 // Link our observer to the new WebGL context methods.
394 for (let { timing, callback, functions } of self._methods) {
395 for (let func of functions) {
396 self._instrument(observer, context, func, callback, timing);
397 }
398 }
400 // Return the decorated context back to the content consumer, which
401 // will continue using it normally.
402 return context;
403 };
404 },
406 /**
407 * Overrides a specific method in a HTMLCanvasElement context.
408 *
409 * @param WebGLObserver observer
410 * The observer watching function calls in the context.
411 * @param WebGLRenderingContext context
412 * The targeted WebGL context instance.
413 * @param string funcName
414 * The function to override.
415 * @param array callbackName [optional]
416 * The two callback function names in the observer, corresponding to
417 * the "before" and "after" invocation times. If unspecified, they will
418 * default to the name of the function to override.
419 * @param number timing [optional]
420 * When to issue the callback in relation to the actual context
421 * function call. Availalble values are -1 for "before" (default)
422 * 1 for "after" and 0 for "before and after".
423 */
424 _instrument: function(observer, context, funcName, callbackName = [], timing = -1) {
425 let { cache, proxy } = observer.for(context);
426 let originalFunc = context[funcName];
427 let beforeFuncName = callbackName[0] || funcName;
428 let afterFuncName = callbackName[1] || callbackName[0] || funcName;
430 context[funcName] = function(...glArgs) {
431 if (timing <= 0 && !observer.suppressHandlers) {
432 let glBreak = observer[beforeFuncName](glArgs, cache, proxy);
433 if (glBreak) return undefined;
434 }
436 let glResult = originalFunc.apply(this, glArgs);
438 if (timing >= 0 && !observer.suppressHandlers) {
439 let glBreak = observer[afterFuncName](glArgs, glResult, cache, proxy);
440 if (glBreak) return undefined;
441 }
443 return glResult;
444 };
445 },
447 /**
448 * Override mappings for WebGL methods.
449 */
450 _methods: [{
451 timing: 1, // after
452 functions: [
453 "linkProgram", "getAttribLocation", "getUniformLocation"
454 ]
455 }, {
456 timing: -1, // before
457 callback: [
458 "toggleVertexAttribArray"
459 ],
460 functions: [
461 "enableVertexAttribArray", "disableVertexAttribArray"
462 ]
463 }, {
464 timing: -1, // before
465 callback: [
466 "attribute_"
467 ],
468 functions: [
469 "vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
470 "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
471 "vertexAttribPointer"
472 ]
473 }, {
474 timing: -1, // before
475 callback: [
476 "uniform_"
477 ],
478 functions: [
479 "uniform1i", "uniform2i", "uniform3i", "uniform4i",
480 "uniform1f", "uniform2f", "uniform3f", "uniform4f",
481 "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
482 "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
483 "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
484 ]
485 }, {
486 timing: -1, // before
487 functions: [
488 "useProgram", "enable", "disable", "blendColor",
489 "blendEquation", "blendEquationSeparate",
490 "blendFunc", "blendFuncSeparate"
491 ]
492 }, {
493 timing: 0, // before and after
494 callback: [
495 "beforeDraw_", "afterDraw_"
496 ],
497 functions: [
498 "drawArrays", "drawElements"
499 ]
500 }]
501 // TODO: It'd be a good idea to handle other functions as well:
502 // - getActiveUniform
503 // - getUniform
504 // - getActiveAttrib
505 // - getVertexAttrib
506 };
508 /**
509 * An observer that captures a WebGL context's method calls.
510 */
511 function WebGLObserver() {
512 this._contexts = new Map();
513 }
515 WebGLObserver.prototype = {
516 _contexts: null,
518 /**
519 * Creates a WebGLCache and a WebGLProxy for the specified window and context.
520 *
521 * @param number id
522 * The id of the window containing the WebGL context.
523 * @param WebGLRenderingContext context
524 * The WebGL context used in the cache and proxy instances.
525 */
526 registerContextForWindow: function(id, context) {
527 let cache = new WebGLCache(id, context);
528 let proxy = new WebGLProxy(id, context, cache, this);
529 cache.refreshState(proxy);
531 this._contexts.set(context, {
532 ownerWindow: id,
533 cache: cache,
534 proxy: proxy
535 });
536 },
538 /**
539 * Removes all WebGLCache and WebGLProxy instances for a particular window.
540 *
541 * @param number id
542 * The id of the window containing the WebGL context.
543 */
544 unregisterContextsForWindow: function(id) {
545 removeFromMap(this._contexts, e => e.ownerWindow == id);
546 },
548 /**
549 * Gets the WebGLCache and WebGLProxy instances for a particular context.
550 *
551 * @param WebGLRenderingContext context
552 * The WebGL context used in the cache and proxy instances.
553 * @return object
554 * An object containing the corresponding { cache, proxy } instances.
555 */
556 for: function(context) {
557 return this._contexts.get(context);
558 },
560 /**
561 * Set this flag to true to stop observing any context function calls.
562 */
563 suppressHandlers: false,
565 /**
566 * Called immediately *after* 'linkProgram' is requested in the context.
567 *
568 * @param array glArgs
569 * Overridable arguments with which the function is called.
570 * @param void glResult
571 * The returned value of the original function call.
572 * @param WebGLCache cache
573 * The state storage for the WebGL context initiating this call.
574 * @param WebGLProxy proxy
575 * The proxy methods for the WebGL context initiating this call.
576 */
577 linkProgram: function(glArgs, glResult, cache, proxy) {
578 let program = glArgs[0];
579 let shaders = proxy.getAttachedShaders(program);
580 cache.addProgram(program, PROGRAM_DEFAULT_TRAITS);
581 emit(this, "program-linked", program, shaders, cache, proxy);
582 },
584 /**
585 * Called immediately *after* 'getAttribLocation' is requested in the context.
586 *
587 * @param array glArgs
588 * Overridable arguments with which the function is called.
589 * @param GLint glResult
590 * The returned value of the original function call.
591 * @param WebGLCache cache
592 * The state storage for the WebGL context initiating this call.
593 */
594 getAttribLocation: function(glArgs, glResult, cache) {
595 // Make sure the attribute's value is legal before caching.
596 if (glResult < 0) {
597 return;
598 }
599 let [program, name] = glArgs;
600 cache.addAttribute(program, name, glResult);
601 },
603 /**
604 * Called immediately *after* 'getUniformLocation' is requested in the context.
605 *
606 * @param array glArgs
607 * Overridable arguments with which the function is called.
608 * @param WebGLUniformLocation glResult
609 * The returned value of the original function call.
610 * @param WebGLCache cache
611 * The state storage for the WebGL context initiating this call.
612 */
613 getUniformLocation: function(glArgs, glResult, cache) {
614 // Make sure the uniform's value is legal before caching.
615 if (!glResult) {
616 return;
617 }
618 let [program, name] = glArgs;
619 cache.addUniform(program, name, glResult);
620 },
622 /**
623 * Called immediately *before* 'enableVertexAttribArray' or
624 * 'disableVertexAttribArray'is requested in the context.
625 *
626 * @param array glArgs
627 * Overridable arguments with which the function is called.
628 * @param WebGLCache cache
629 * The state storage for the WebGL context initiating this call.
630 */
631 toggleVertexAttribArray: function(glArgs, cache) {
632 glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
633 return glArgs[0] < 0; // Return true to break original function call.
634 },
636 /**
637 * Called immediately *before* 'attribute_' is requested in the context.
638 *
639 * @param array glArgs
640 * Overridable arguments with which the function is called.
641 * @param WebGLCache cache
642 * The state storage for the WebGL context initiating this call.
643 */
644 attribute_: function(glArgs, cache) {
645 glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
646 return glArgs[0] < 0; // Return true to break original function call.
647 },
649 /**
650 * Called immediately *before* 'uniform_' is requested in the context.
651 *
652 * @param array glArgs
653 * Overridable arguments with which the function is called.
654 * @param WebGLCache cache
655 * The state storage for the WebGL context initiating this call.
656 */
657 uniform_: function(glArgs, cache) {
658 glArgs[0] = cache.getCurrentUniformLocation(glArgs[0]);
659 return !glArgs[0]; // Return true to break original function call.
660 },
662 /**
663 * Called immediately *before* 'useProgram' is requested in the context.
664 *
665 * @param array glArgs
666 * Overridable arguments with which the function is called.
667 * @param WebGLCache cache
668 * The state storage for the WebGL context initiating this call.
669 */
670 useProgram: function(glArgs, cache) {
671 // Manually keeping a cache and not using gl.getParameter(CURRENT_PROGRAM)
672 // because gl.get* functions are slow as potatoes.
673 cache.currentProgram = glArgs[0];
674 },
676 /**
677 * Called immediately *before* 'enable' is requested in the context.
678 *
679 * @param array glArgs
680 * Overridable arguments with which the function is called.
681 * @param WebGLCache cache
682 * The state storage for the WebGL context initiating this call.
683 */
684 enable: function(glArgs, cache) {
685 cache.currentState[glArgs[0]] = true;
686 },
688 /**
689 * Called immediately *before* 'disable' is requested in the context.
690 *
691 * @param array glArgs
692 * Overridable arguments with which the function is called.
693 * @param WebGLCache cache
694 * The state storage for the WebGL context initiating this call.
695 */
696 disable: function(glArgs, cache) {
697 cache.currentState[glArgs[0]] = false;
698 },
700 /**
701 * Called immediately *before* 'blendColor' is requested in the context.
702 *
703 * @param array glArgs
704 * Overridable arguments with which the function is called.
705 * @param WebGLCache cache
706 * The state storage for the WebGL context initiating this call.
707 */
708 blendColor: function(glArgs, cache) {
709 let blendColor = cache.currentState.blendColor;
710 blendColor[0] = glArgs[0];
711 blendColor[1] = glArgs[1];
712 blendColor[2] = glArgs[2];
713 blendColor[3] = glArgs[3];
714 },
716 /**
717 * Called immediately *before* 'blendEquation' is requested in the context.
718 *
719 * @param array glArgs
720 * Overridable arguments with which the function is called.
721 * @param WebGLCache cache
722 * The state storage for the WebGL context initiating this call.
723 */
724 blendEquation: function(glArgs, cache) {
725 let state = cache.currentState;
726 state.blendEquationRgb = state.blendEquationAlpha = glArgs[0];
727 },
729 /**
730 * Called immediately *before* 'blendEquationSeparate' is requested in the context.
731 *
732 * @param array glArgs
733 * Overridable arguments with which the function is called.
734 * @param WebGLCache cache
735 * The state storage for the WebGL context initiating this call.
736 */
737 blendEquationSeparate: function(glArgs, cache) {
738 let state = cache.currentState;
739 state.blendEquationRgb = glArgs[0];
740 state.blendEquationAlpha = glArgs[1];
741 },
743 /**
744 * Called immediately *before* 'blendFunc' is requested in the context.
745 *
746 * @param array glArgs
747 * Overridable arguments with which the function is called.
748 * @param WebGLCache cache
749 * The state storage for the WebGL context initiating this call.
750 */
751 blendFunc: function(glArgs, cache) {
752 let state = cache.currentState;
753 state.blendSrcRgb = state.blendSrcAlpha = glArgs[0];
754 state.blendDstRgb = state.blendDstAlpha = glArgs[1];
755 },
757 /**
758 * Called immediately *before* 'blendFuncSeparate' is requested in the context.
759 *
760 * @param array glArgs
761 * Overridable arguments with which the function is called.
762 * @param WebGLCache cache
763 * The state storage for the WebGL context initiating this call.
764 */
765 blendFuncSeparate: function(glArgs, cache) {
766 let state = cache.currentState;
767 state.blendSrcRgb = glArgs[0];
768 state.blendDstRgb = glArgs[1];
769 state.blendSrcAlpha = glArgs[2];
770 state.blendDstAlpha = glArgs[3];
771 },
773 /**
774 * Called immediately *before* 'drawArrays' or 'drawElements' is requested
775 * in the context.
776 *
777 * @param array glArgs
778 * Overridable arguments with which the function is called.
779 * @param WebGLCache cache
780 * The state storage for the WebGL context initiating this call.
781 * @param WebGLProxy proxy
782 * The proxy methods for the WebGL context initiating this call.
783 */
784 beforeDraw_: function(glArgs, cache, proxy) {
785 let traits = cache.currentProgramTraits;
787 // Handle program blackboxing.
788 if (traits & PROGRAM_BLACKBOX_TRAIT) {
789 return true; // Return true to break original function call.
790 }
791 // Handle program highlighting.
792 if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
793 proxy.enableHighlighting();
794 }
796 return false;
797 },
799 /**
800 * Called immediately *after* 'drawArrays' or 'drawElements' is requested
801 * in the context.
802 *
803 * @param array glArgs
804 * Overridable arguments with which the function is called.
805 * @param void glResult
806 * The returned value of the original function call.
807 * @param WebGLCache cache
808 * The state storage for the WebGL context initiating this call.
809 * @param WebGLProxy proxy
810 * The proxy methods for the WebGL context initiating this call.
811 */
812 afterDraw_: function(glArgs, glResult, cache, proxy) {
813 let traits = cache.currentProgramTraits;
815 // Handle program highlighting.
816 if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
817 proxy.disableHighlighting();
818 }
819 }
820 };
822 /**
823 * A mechanism for storing a single WebGL context's state, programs, shaders,
824 * attributes or uniforms.
825 *
826 * @param number id
827 * The id of the window containing the WebGL context.
828 * @param WebGLRenderingContext context
829 * The WebGL context for which the state is stored.
830 */
831 function WebGLCache(id, context) {
832 this._id = id;
833 this._gl = context;
834 this._programs = new Map();
835 this.currentState = {};
836 }
838 WebGLCache.prototype = {
839 _id: 0,
840 _gl: null,
841 _programs: null,
842 _currentProgramInfo: null,
843 _currentAttributesMap: null,
844 _currentUniformsMap: null,
846 get ownerWindow() this._id,
847 get ownerContext() this._gl,
849 /**
850 * A collection of flags or properties representing the context's state.
851 * Implemented as an object hash and not a Map instance because keys are
852 * always either strings or numbers.
853 */
854 currentState: null,
856 /**
857 * Populates the current state with values retrieved from the context.
858 *
859 * @param WebGLProxy proxy
860 * The proxy methods for the WebGL context owning the state.
861 */
862 refreshState: function(proxy) {
863 let gl = this._gl;
864 let s = this.currentState;
866 // Populate only with the necessary parameters. Not all default WebGL
867 // state values are required.
868 s[gl.BLEND] = proxy.isEnabled("BLEND");
869 s.blendColor = proxy.getParameter("BLEND_COLOR");
870 s.blendEquationRgb = proxy.getParameter("BLEND_EQUATION_RGB");
871 s.blendEquationAlpha = proxy.getParameter("BLEND_EQUATION_ALPHA");
872 s.blendSrcRgb = proxy.getParameter("BLEND_SRC_RGB");
873 s.blendSrcAlpha = proxy.getParameter("BLEND_SRC_ALPHA");
874 s.blendDstRgb = proxy.getParameter("BLEND_DST_RGB");
875 s.blendDstAlpha = proxy.getParameter("BLEND_DST_ALPHA");
876 },
878 /**
879 * Adds a program to the cache.
880 *
881 * @param WebGLProgram program
882 * The shader for which the traits are to be cached.
883 * @param number traits
884 * A default properties mask set for the program.
885 */
886 addProgram: function(program, traits) {
887 this._programs.set(program, {
888 traits: traits,
889 attributes: [], // keys are GLints (numbers)
890 uniforms: new Map() // keys are WebGLUniformLocations (objects)
891 });
892 },
894 /**
895 * Adds a specific trait to a program. The effect of such properties is
896 * determined by the consumer of this cache.
897 *
898 * @param WebGLProgram program
899 * The program to add the trait to.
900 * @param number trait
901 * The property added to the program.
902 */
903 setProgramTrait: function(program, trait) {
904 this._programs.get(program).traits |= trait;
905 },
907 /**
908 * Removes a specific trait from a program.
909 *
910 * @param WebGLProgram program
911 * The program to remove the trait from.
912 * @param number trait
913 * The property removed from the program.
914 */
915 unsetProgramTrait: function(program, trait) {
916 this._programs.get(program).traits &= ~trait;
917 },
919 /**
920 * Sets the currently used program in the context.
921 * @param WebGLProgram program
922 */
923 set currentProgram(program) {
924 let programInfo = this._programs.get(program);
925 if (programInfo == null) {
926 return;
927 }
928 this._currentProgramInfo = programInfo;
929 this._currentAttributesMap = programInfo.attributes;
930 this._currentUniformsMap = programInfo.uniforms;
931 },
933 /**
934 * Gets the traits for the currently used program.
935 * @return number
936 */
937 get currentProgramTraits() {
938 return this._currentProgramInfo.traits;
939 },
941 /**
942 * Adds an attribute to the cache.
943 *
944 * @param WebGLProgram program
945 * The program for which the attribute is bound.
946 * @param string name
947 * The attribute name.
948 * @param GLint value
949 * The attribute value.
950 */
951 addAttribute: function(program, name, value) {
952 this._programs.get(program).attributes[value] = {
953 name: name,
954 value: value
955 };
956 },
958 /**
959 * Adds a uniform to the cache.
960 *
961 * @param WebGLProgram program
962 * The program for which the uniform is bound.
963 * @param string name
964 * The uniform name.
965 * @param WebGLUniformLocation value
966 * The uniform value.
967 */
968 addUniform: function(program, name, value) {
969 this._programs.get(program).uniforms.set(new XPCNativeWrapper(value), {
970 name: name,
971 value: value
972 });
973 },
975 /**
976 * Updates the attribute locations for a specific program.
977 * This is necessary, for example, when the shader is relinked and all the
978 * attribute locations become obsolete.
979 *
980 * @param WebGLProgram program
981 * The program for which the attributes need updating.
982 */
983 updateAttributesForProgram: function(program) {
984 let attributes = this._programs.get(program).attributes;
985 for (let attribute of attributes) {
986 attribute.value = this._gl.getAttribLocation(program, attribute.name);
987 }
988 },
990 /**
991 * Updates the uniform locations for a specific program.
992 * This is necessary, for example, when the shader is relinked and all the
993 * uniform locations become obsolete.
994 *
995 * @param WebGLProgram program
996 * The program for which the uniforms need updating.
997 */
998 updateUniformsForProgram: function(program) {
999 let uniforms = this._programs.get(program).uniforms;
1000 for (let [, uniform] of uniforms) {
1001 uniform.value = this._gl.getUniformLocation(program, uniform.name);
1002 }
1003 },
1005 /**
1006 * Gets the actual attribute location in a specific program.
1007 * When relinked, all the attribute locations become obsolete and are updated
1008 * in the cache. This method returns the (current) real attribute location.
1009 *
1010 * @param GLint initialValue
1011 * The initial attribute value.
1012 * @return GLint
1013 * The current attribute value, or the initial value if it's already
1014 * up to date with its corresponding program.
1015 */
1016 getCurrentAttributeLocation: function(initialValue) {
1017 let attributes = this._currentAttributesMap;
1018 let currentInfo = attributes ? attributes[initialValue] : null;
1019 return currentInfo ? currentInfo.value : initialValue;
1020 },
1022 /**
1023 * Gets the actual uniform location in a specific program.
1024 * When relinked, all the uniform locations become obsolete and are updated
1025 * in the cache. This method returns the (current) real uniform location.
1026 *
1027 * @param WebGLUniformLocation initialValue
1028 * The initial uniform value.
1029 * @return WebGLUniformLocation
1030 * The current uniform value, or the initial value if it's already
1031 * up to date with its corresponding program.
1032 */
1033 getCurrentUniformLocation: function(initialValue) {
1034 let uniforms = this._currentUniformsMap;
1035 let currentInfo = uniforms ? uniforms.get(initialValue) : null;
1036 return currentInfo ? currentInfo.value : initialValue;
1037 }
1038 };
1040 /**
1041 * A mechanism for injecting or qureying state into/from a single WebGL context.
1042 *
1043 * Any interaction with a WebGL context should go through this proxy.
1044 * Otherwise, the corresponding observer would register the calls as coming
1045 * from content, which is usually not desirable. Infinite call stacks are bad.
1046 *
1047 * @param number id
1048 * The id of the window containing the WebGL context.
1049 * @param WebGLRenderingContext context
1050 * The WebGL context used for the proxy methods.
1051 * @param WebGLCache cache
1052 * The state storage for the corresponding context.
1053 * @param WebGLObserver observer
1054 * The observer watching function calls in the corresponding context.
1055 */
1056 function WebGLProxy(id, context, cache, observer) {
1057 this._id = id;
1058 this._gl = context;
1059 this._cache = cache;
1060 this._observer = observer;
1062 let exports = [
1063 "isEnabled",
1064 "getParameter",
1065 "getAttachedShaders",
1066 "getShaderSource",
1067 "getShaderOfType",
1068 "compileShader",
1069 "enableHighlighting",
1070 "disableHighlighting"
1071 ];
1072 exports.forEach(e => this[e] = (...args) => this._call(e, args));
1073 }
1075 WebGLProxy.prototype = {
1076 _id: 0,
1077 _gl: null,
1078 _cache: null,
1079 _observer: null,
1081 get ownerWindow() this._id,
1082 get ownerContext() this._gl,
1084 /**
1085 * Test whether a WebGL capability is enabled.
1086 *
1087 * @param string name
1088 * The WebGL capability name, for example "BLEND".
1089 * @return boolean
1090 * True if enabled, false otherwise.
1091 */
1092 _isEnabled: function(name) {
1093 return this._gl.isEnabled(this._gl[name]);
1094 },
1096 /**
1097 * Returns the value for the specified WebGL parameter name.
1098 *
1099 * @param string name
1100 * The WebGL parameter name, for example "BLEND_COLOR".
1101 * @return any
1102 * The corresponding parameter's value.
1103 */
1104 _getParameter: function(name) {
1105 return this._gl.getParameter(this._gl[name]);
1106 },
1108 /**
1109 * Returns the renderbuffer property value for the specified WebGL parameter.
1110 * If no renderbuffer binding is available, null is returned.
1111 *
1112 * @param string name
1113 * The WebGL parameter name, for example "BLEND_COLOR".
1114 * @return any
1115 * The corresponding parameter's value.
1116 */
1117 _getRenderbufferParameter: function(name) {
1118 if (!this._getParameter("RENDERBUFFER_BINDING")) {
1119 return null;
1120 }
1121 let gl = this._gl;
1122 return gl.getRenderbufferParameter(gl.RENDERBUFFER, gl[name]);
1123 },
1125 /**
1126 * Returns the framebuffer property value for the specified WebGL parameter.
1127 * If no framebuffer binding is available, null is returned.
1128 *
1129 * @param string type
1130 * The framebuffer object attachment point, for example "COLOR_ATTACHMENT0".
1131 * @param string name
1132 * The WebGL parameter name, for example "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME".
1133 * If unspecified, defaults to "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE".
1134 * @return any
1135 * The corresponding parameter's value.
1136 */
1137 _getFramebufferAttachmentParameter: function(type, name = "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE") {
1138 if (!this._getParameter("FRAMEBUFFER_BINDING")) {
1139 return null;
1140 }
1141 let gl = this._gl;
1142 return gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[type], gl[name]);
1143 },
1145 /**
1146 * Returns the shader objects attached to a program object.
1147 *
1148 * @param WebGLProgram program
1149 * The program for which to retrieve the attached shaders.
1150 * @return array
1151 * The attached vertex and fragment shaders.
1152 */
1153 _getAttachedShaders: function(program) {
1154 return this._gl.getAttachedShaders(program);
1155 },
1157 /**
1158 * Returns the source code string from a shader object.
1159 *
1160 * @param WebGLShader shader
1161 * The shader for which to retrieve the source code.
1162 * @return string
1163 * The shader's source code.
1164 */
1165 _getShaderSource: function(shader) {
1166 return this._gl.getShaderSource(shader);
1167 },
1169 /**
1170 * Finds a shader of the specified type in a list.
1171 *
1172 * @param WebGLShader[] shaders
1173 * The shaders for which to check the type.
1174 * @param string type
1175 * Either "vertex" or "fragment".
1176 * @return WebGLShader | null
1177 * The shader of the specified type, or null if nothing is found.
1178 */
1179 _getShaderOfType: function(shaders, type) {
1180 let gl = this._gl;
1181 let shaderTypeEnum = {
1182 vertex: gl.VERTEX_SHADER,
1183 fragment: gl.FRAGMENT_SHADER
1184 }[type];
1186 for (let shader of shaders) {
1187 if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == shaderTypeEnum) {
1188 return shader;
1189 }
1190 }
1191 return null;
1192 },
1194 /**
1195 * Changes a shader's source code and relinks the respective program.
1196 *
1197 * @param WebGLProgram program
1198 * The program who's linked shader is to be modified.
1199 * @param WebGLShader shader
1200 * The shader to be modified.
1201 * @param string text
1202 * The new shader source code.
1203 * @return object
1204 * An object containing the compilation and linking status.
1205 */
1206 _compileShader: function(program, shader, text) {
1207 let gl = this._gl;
1208 gl.shaderSource(shader, text);
1209 gl.compileShader(shader);
1210 gl.linkProgram(program);
1212 let error = { compile: "", link: "" };
1214 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
1215 error.compile = gl.getShaderInfoLog(shader);
1216 }
1217 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
1218 error.link = gl.getShaderInfoLog(shader);
1219 }
1221 this._cache.updateAttributesForProgram(program);
1222 this._cache.updateUniformsForProgram(program);
1224 return error;
1225 },
1227 /**
1228 * Enables color blending based on the geometry highlight tint.
1229 */
1230 _enableHighlighting: function() {
1231 let gl = this._gl;
1233 // Avoid changing the blending params when "rendering to texture".
1235 // Check drawing to a custom framebuffer bound to the default renderbuffer.
1236 let hasFramebuffer = this._getParameter("FRAMEBUFFER_BINDING");
1237 let hasRenderbuffer = this._getParameter("RENDERBUFFER_BINDING");
1238 if (hasFramebuffer && !hasRenderbuffer) {
1239 return;
1240 }
1242 // Check drawing to a depth or stencil component of the framebuffer.
1243 let writesDepth = this._getFramebufferAttachmentParameter("DEPTH_ATTACHMENT");
1244 let writesStencil = this._getFramebufferAttachmentParameter("STENCIL_ATTACHMENT");
1245 if (writesDepth || writesStencil) {
1246 return;
1247 }
1249 // Non-premultiplied alpha blending based on a predefined constant color.
1250 // Simply using gl.colorMask won't work, because we want non-tinted colors
1251 // to be drawn as black, not ignored.
1252 gl.enable(gl.BLEND);
1253 gl.blendColor.apply(gl, this.highlightTint);
1254 gl.blendEquation(gl.FUNC_ADD);
1255 gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.CONSTANT_COLOR, gl.ZERO);
1256 this.wasHighlighting = true;
1257 },
1259 /**
1260 * Disables color blending based on the geometry highlight tint, by
1261 * reverting the corresponding params back to their original values.
1262 */
1263 _disableHighlighting: function() {
1264 let gl = this._gl;
1265 let s = this._cache.currentState;
1267 gl[s[gl.BLEND] ? "enable" : "disable"](gl.BLEND);
1268 gl.blendColor.apply(gl, s.blendColor);
1269 gl.blendEquationSeparate(s.blendEquationRgb, s.blendEquationAlpha);
1270 gl.blendFuncSeparate(s.blendSrcRgb, s.blendDstRgb, s.blendSrcAlpha, s.blendDstAlpha);
1271 },
1273 /**
1274 * The color tint used for highlighting geometry.
1275 * @see _enableHighlighting and _disableHighlighting.
1276 */
1277 highlightTint: [0, 0, 0, 0],
1279 /**
1280 * Executes a function in this object.
1281 *
1282 * This method makes sure that any handlers in the context observer are
1283 * suppressed, hence stopping observing any context function calls.
1284 *
1285 * @param string funcName
1286 * The function to call.
1287 * @param array args
1288 * An array of arguments.
1289 * @return any
1290 * The called function result.
1291 */
1292 _call: function(funcName, args) {
1293 let prevState = this._observer.suppressHandlers;
1295 this._observer.suppressHandlers = true;
1296 let result = this["_" + funcName].apply(this, args);
1297 this._observer.suppressHandlers = prevState;
1299 return result;
1300 }
1301 };
1303 // Utility functions.
1305 function removeFromMap(map, predicate) {
1306 for (let [key, value] of map) {
1307 if (predicate(value)) {
1308 map.delete(key);
1309 }
1310 }
1311 };
1313 function removeFromArray(array, predicate) {
1314 for (let value of array) {
1315 if (predicate(value)) {
1316 array.splice(array.indexOf(value), 1);
1317 }
1318 }
1319 }