123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- const {Cc, Ci, Cu, Cr} = require("chrome");
- const events = require("sdk/event/core");
- const promise = require("promise");
- const protocol = require("devtools/shared/protocol");
- const { ContentObserver } = require("devtools/shared/content-observer");
- const { on, once, off, emit } = events;
- const { method, Arg, Option, RetVal } = protocol;
- const {
- shaderSpec,
- programSpec,
- webGLSpec,
- } = require("devtools/shared/specs/webgl");
- const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
- // These traits are bit masks. Make sure they're powers of 2.
- const PROGRAM_DEFAULT_TRAITS = 0;
- const PROGRAM_BLACKBOX_TRAIT = 1;
- const PROGRAM_HIGHLIGHT_TRAIT = 2;
- /**
- * A WebGL Shader contributing to building a WebGL Program.
- * You can either retrieve, or compile the source of a shader, which will
- * automatically inflict the necessary changes to the WebGL state.
- */
- var ShaderActor = protocol.ActorClassWithSpec(shaderSpec, {
- /**
- * Create the shader actor.
- *
- * @param DebuggerServerConnection conn
- * The server connection.
- * @param WebGLProgram program
- * The WebGL program being linked.
- * @param WebGLShader shader
- * The cooresponding vertex or fragment shader.
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context owning this shader.
- */
- initialize: function (conn, program, shader, proxy) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this.program = program;
- this.shader = shader;
- this.text = proxy.getShaderSource(shader);
- this.linkedProxy = proxy;
- },
- /**
- * Gets the source code for this shader.
- */
- getText: function () {
- return this.text;
- },
- /**
- * Sets and compiles new source code for this shader.
- */
- compile: function (text) {
- // Get the shader and corresponding program to change via the WebGL proxy.
- let { linkedProxy: proxy, shader, program } = this;
- // Get the new shader source to inject.
- let oldText = this.text;
- let newText = text;
- // Overwrite the shader's source.
- let error = proxy.compileShader(program, shader, this.text = newText);
- // If something went wrong, revert to the previous shader.
- if (error.compile || error.link) {
- proxy.compileShader(program, shader, this.text = oldText);
- return error;
- }
- return undefined;
- }
- });
- /**
- * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
- * of two shaders: a vertex shader and a fragment shader.
- */
- var ProgramActor = protocol.ActorClassWithSpec(programSpec, {
- /**
- * Create the program actor.
- *
- * @param DebuggerServerConnection conn
- * The server connection.
- * @param WebGLProgram program
- * The WebGL program being linked.
- * @param WebGLShader[] shaders
- * The WebGL program's cooresponding vertex and fragment shaders.
- * @param WebGLCache cache
- * The state storage for the WebGL context owning this program.
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context owning this program.
- */
- initialize: function (conn, [program, shaders, cache, proxy]) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._shaderActorsCache = { vertex: null, fragment: null };
- this.program = program;
- this.shaders = shaders;
- this.linkedCache = cache;
- this.linkedProxy = proxy;
- },
- get ownerWindow() {
- return this.linkedCache.ownerWindow;
- },
- get ownerContext() {
- return this.linkedCache.ownerContext;
- },
- /**
- * Gets the vertex shader linked to this program. This method guarantees
- * a single actor instance per shader.
- */
- getVertexShader: function () {
- return this._getShaderActor("vertex");
- },
- /**
- * Gets the fragment shader linked to this program. This method guarantees
- * a single actor instance per shader.
- */
- getFragmentShader: function () {
- return this._getShaderActor("fragment");
- },
- /**
- * Highlights any geometry rendered using this program.
- */
- highlight: function (tint) {
- this.linkedProxy.highlightTint = tint;
- this.linkedCache.setProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
- },
- /**
- * Allows geometry to be rendered normally using this program.
- */
- unhighlight: function () {
- this.linkedCache.unsetProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
- },
- /**
- * Prevents any geometry from being rendered using this program.
- */
- blackbox: function () {
- this.linkedCache.setProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
- },
- /**
- * Allows geometry to be rendered using this program.
- */
- unblackbox: function () {
- this.linkedCache.unsetProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
- },
- /**
- * Returns a cached ShaderActor instance based on the required shader type.
- *
- * @param string type
- * Either "vertex" or "fragment".
- * @return ShaderActor
- * The respective shader actor instance.
- */
- _getShaderActor: function (type) {
- if (this._shaderActorsCache[type]) {
- return this._shaderActorsCache[type];
- }
- let proxy = this.linkedProxy;
- let shader = proxy.getShaderOfType(this.shaders, type);
- let shaderActor = new ShaderActor(this.conn, this.program, shader, proxy);
- return this._shaderActorsCache[type] = shaderActor;
- }
- });
- /**
- * The WebGL Actor handles simple interaction with a WebGL context via a few
- * high-level methods. After instantiating this actor, you'll need to set it
- * up by calling setup().
- */
- var WebGLActor = exports.WebGLActor = protocol.ActorClassWithSpec(webGLSpec, {
- initialize: function (conn, tabActor) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this.tabActor = tabActor;
- this._onGlobalCreated = this._onGlobalCreated.bind(this);
- this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
- this._onProgramLinked = this._onProgramLinked.bind(this);
- },
- destroy: function (conn) {
- protocol.Actor.prototype.destroy.call(this, conn);
- this.finalize();
- },
- /**
- * Starts waiting for the current tab actor's document global to be
- * created, in order to instrument the Canvas context and become
- * aware of everything the content does WebGL-wise.
- *
- * See ContentObserver and WebGLInstrumenter for more details.
- */
- setup: function ({ reload }) {
- if (this._initialized) {
- return;
- }
- this._initialized = true;
- this._programActorsCache = [];
- this._webglObserver = new WebGLObserver();
- on(this.tabActor, "window-ready", this._onGlobalCreated);
- on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
- on(this._webglObserver, "program-linked", this._onProgramLinked);
- if (reload) {
- this.tabActor.window.location.reload();
- }
- },
- /**
- * Stops listening for document global changes and puts this actor
- * to hibernation. This method is called automatically just before the
- * actor is destroyed.
- */
- finalize: function () {
- if (!this._initialized) {
- return;
- }
- this._initialized = false;
- off(this.tabActor, "window-ready", this._onGlobalCreated);
- off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
- off(this._webglObserver, "program-linked", this._onProgramLinked);
- this._programActorsCache = null;
- this._contentObserver = null;
- this._webglObserver = null;
- },
- /**
- * Gets an array of cached program actors for the current tab actor's window.
- * This is useful for dealing with bfcache, when no new programs are linked.
- */
- getPrograms: function () {
- let id = ContentObserver.GetInnerWindowID(this.tabActor.window);
- return this._programActorsCache.filter(e => e.ownerWindow == id);
- },
- /**
- * Waits for one frame via `requestAnimationFrame` on the tab actor's window.
- * Used in tests.
- */
- waitForFrame: function () {
- let deferred = promise.defer();
- this.tabActor.window.requestAnimationFrame(deferred.resolve);
- return deferred.promise;
- },
- /**
- * Gets a pixel's RGBA value from a context specified by selector
- * and the coordinates of the pixel in question.
- * Currently only used in tests.
- *
- * @param string selector
- * A string selector to select the canvas in question from the DOM.
- * @param Object position
- * An object with an `x` and `y` property indicating coordinates of the pixel being inspected.
- * @return Object
- * An object containing `r`, `g`, `b`, and `a` properties of the pixel.
- */
- getPixel: function ({ selector, position }) {
- let { x, y } = position;
- let canvas = this.tabActor.window.document.querySelector(selector);
- let context = XPCNativeWrapper.unwrap(canvas.getContext("webgl"));
- let { proxy } = this._webglObserver.for(context);
- let height = canvas.height;
- let buffer = new this.tabActor.window.Uint8Array(4);
- buffer = XPCNativeWrapper.unwrap(buffer);
- proxy.readPixels(x, height - y - 1, 1, 1, context.RGBA, context.UNSIGNED_BYTE, buffer);
- return { r: buffer[0], g: buffer[1], b: buffer[2], a: buffer[3] };
- },
- /**
- * Gets an array of all cached program actors belonging to all windows.
- * This should only be used for tests.
- */
- _getAllPrograms: function () {
- return this._programActorsCache;
- },
- /**
- * Invoked whenever the current tab actor's document global is created.
- */
- _onGlobalCreated: function ({id, window, isTopLevel}) {
- if (isTopLevel) {
- WebGLInstrumenter.handle(window, this._webglObserver);
- events.emit(this, "global-created", id);
- }
- },
- /**
- * Invoked whenever the current tab actor's inner window is destroyed.
- */
- _onGlobalDestroyed: function ({id, isTopLevel, isFrozen}) {
- if (isTopLevel && !isFrozen) {
- removeFromArray(this._programActorsCache, e => e.ownerWindow == id);
- this._webglObserver.unregisterContextsForWindow(id);
- events.emit(this, "global-destroyed", id);
- }
- },
- /**
- * Invoked whenever an observed WebGL context links a program.
- */
- _onProgramLinked: function (...args) {
- let programActor = new ProgramActor(this.conn, args);
- this._programActorsCache.push(programActor);
- events.emit(this, "program-linked", programActor);
- }
- });
- /**
- * Instruments a HTMLCanvasElement with the appropriate inspection methods.
- */
- var WebGLInstrumenter = {
- /**
- * Overrides the getContext method in the HTMLCanvasElement prototype.
- *
- * @param nsIDOMWindow window
- * The window to perform the instrumentation in.
- * @param WebGLObserver observer
- * The observer watching function calls in the context.
- */
- handle: function (window, observer) {
- let self = this;
- let id = ContentObserver.GetInnerWindowID(window);
- let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
- let canvasPrototype = canvasElem.prototype;
- let originalGetContext = canvasPrototype.getContext;
- /**
- * Returns a drawing context on the canvas, or null if the context ID is
- * not supported. This override creates an observer for the targeted context
- * type and instruments specific functions in the targeted context instance.
- */
- canvasPrototype.getContext = function (name, options) {
- // Make sure a context was able to be created.
- let context = originalGetContext.call(this, name, options);
- if (!context) {
- return context;
- }
- // Make sure a WebGL (not a 2D) context will be instrumented.
- if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
- return context;
- }
- // Repeated calls to 'getContext' return the same instance, no need to
- // instrument everything again.
- if (observer.for(context)) {
- return context;
- }
- // Create a separate state storage for this context.
- observer.registerContextForWindow(id, context);
- // Link our observer to the new WebGL context methods.
- for (let { timing, callback, functions } of self._methods) {
- for (let func of functions) {
- self._instrument(observer, context, func, callback, timing);
- }
- }
- // Return the decorated context back to the content consumer, which
- // will continue using it normally.
- return context;
- };
- },
- /**
- * Overrides a specific method in a HTMLCanvasElement context.
- *
- * @param WebGLObserver observer
- * The observer watching function calls in the context.
- * @param WebGLRenderingContext context
- * The targeted WebGL context instance.
- * @param string funcName
- * The function to override.
- * @param array callbackName [optional]
- * The two callback function names in the observer, corresponding to
- * the "before" and "after" invocation times. If unspecified, they will
- * default to the name of the function to override.
- * @param number timing [optional]
- * When to issue the callback in relation to the actual context
- * function call. Availalble values are -1 for "before" (default)
- * 1 for "after" and 0 for "before and after".
- */
- _instrument: function (observer, context, funcName, callbackName = [], timing = -1) {
- let { cache, proxy } = observer.for(context);
- let originalFunc = context[funcName];
- let beforeFuncName = callbackName[0] || funcName;
- let afterFuncName = callbackName[1] || callbackName[0] || funcName;
- context[funcName] = function (...glArgs) {
- if (timing <= 0 && !observer.suppressHandlers) {
- let glBreak = observer[beforeFuncName](glArgs, cache, proxy);
- if (glBreak) return undefined;
- }
- // Invoking .apply on an unxrayed content function doesn't work, because
- // the arguments array is inaccessible to it. Get Xrays back.
- let glResult = Cu.waiveXrays(Cu.unwaiveXrays(originalFunc).apply(this, glArgs));
- if (timing >= 0 && !observer.suppressHandlers) {
- let glBreak = observer[afterFuncName](glArgs, glResult, cache, proxy);
- if (glBreak) return undefined;
- }
- return glResult;
- };
- },
- /**
- * Override mappings for WebGL methods.
- */
- _methods: [{
- timing: 1, // after
- functions: [
- "linkProgram", "getAttribLocation", "getUniformLocation"
- ]
- }, {
- timing: -1, // before
- callback: [
- "toggleVertexAttribArray"
- ],
- functions: [
- "enableVertexAttribArray", "disableVertexAttribArray"
- ]
- }, {
- timing: -1, // before
- callback: [
- "attribute_"
- ],
- functions: [
- "vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
- "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
- "vertexAttribPointer"
- ]
- }, {
- timing: -1, // before
- callback: [
- "uniform_"
- ],
- functions: [
- "uniform1i", "uniform2i", "uniform3i", "uniform4i",
- "uniform1f", "uniform2f", "uniform3f", "uniform4f",
- "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
- "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
- "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
- ]
- }, {
- timing: -1, // before
- functions: [
- "useProgram", "enable", "disable", "blendColor",
- "blendEquation", "blendEquationSeparate",
- "blendFunc", "blendFuncSeparate"
- ]
- }, {
- timing: 0, // before and after
- callback: [
- "beforeDraw_", "afterDraw_"
- ],
- functions: [
- "drawArrays", "drawElements"
- ]
- }]
- // TODO: It'd be a good idea to handle other functions as well:
- // - getActiveUniform
- // - getUniform
- // - getActiveAttrib
- // - getVertexAttrib
- };
- /**
- * An observer that captures a WebGL context's method calls.
- */
- function WebGLObserver() {
- this._contexts = new Map();
- }
- WebGLObserver.prototype = {
- _contexts: null,
- /**
- * Creates a WebGLCache and a WebGLProxy for the specified window and context.
- *
- * @param number id
- * The id of the window containing the WebGL context.
- * @param WebGLRenderingContext context
- * The WebGL context used in the cache and proxy instances.
- */
- registerContextForWindow: function (id, context) {
- let cache = new WebGLCache(id, context);
- let proxy = new WebGLProxy(id, context, cache, this);
- cache.refreshState(proxy);
- this._contexts.set(context, {
- ownerWindow: id,
- cache: cache,
- proxy: proxy
- });
- },
- /**
- * Removes all WebGLCache and WebGLProxy instances for a particular window.
- *
- * @param number id
- * The id of the window containing the WebGL context.
- */
- unregisterContextsForWindow: function (id) {
- removeFromMap(this._contexts, e => e.ownerWindow == id);
- },
- /**
- * Gets the WebGLCache and WebGLProxy instances for a particular context.
- *
- * @param WebGLRenderingContext context
- * The WebGL context used in the cache and proxy instances.
- * @return object
- * An object containing the corresponding { cache, proxy } instances.
- */
- for: function (context) {
- return this._contexts.get(context);
- },
- /**
- * Set this flag to true to stop observing any context function calls.
- */
- suppressHandlers: false,
- /**
- * Called immediately *after* 'linkProgram' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param void glResult
- * The returned value of the original function call.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context initiating this call.
- */
- linkProgram: function (glArgs, glResult, cache, proxy) {
- let program = glArgs[0];
- let shaders = proxy.getAttachedShaders(program);
- cache.addProgram(program, PROGRAM_DEFAULT_TRAITS);
- emit(this, "program-linked", program, shaders, cache, proxy);
- },
- /**
- * Called immediately *after* 'getAttribLocation' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param GLint glResult
- * The returned value of the original function call.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- getAttribLocation: function (glArgs, glResult, cache) {
- // Make sure the attribute's value is legal before caching.
- if (glResult < 0) {
- return;
- }
- let [program, name] = glArgs;
- cache.addAttribute(program, name, glResult);
- },
- /**
- * Called immediately *after* 'getUniformLocation' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLUniformLocation glResult
- * The returned value of the original function call.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- getUniformLocation: function (glArgs, glResult, cache) {
- // Make sure the uniform's value is legal before caching.
- if (!glResult) {
- return;
- }
- let [program, name] = glArgs;
- cache.addUniform(program, name, glResult);
- },
- /**
- * Called immediately *before* 'enableVertexAttribArray' or
- * 'disableVertexAttribArray'is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- toggleVertexAttribArray: function (glArgs, cache) {
- glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
- return glArgs[0] < 0; // Return true to break original function call.
- },
- /**
- * Called immediately *before* 'attribute_' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- attribute_: function (glArgs, cache) {
- glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
- return glArgs[0] < 0; // Return true to break original function call.
- },
- /**
- * Called immediately *before* 'uniform_' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- uniform_: function (glArgs, cache) {
- glArgs[0] = cache.getCurrentUniformLocation(glArgs[0]);
- return !glArgs[0]; // Return true to break original function call.
- },
- /**
- * Called immediately *before* 'useProgram' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- useProgram: function (glArgs, cache) {
- // Manually keeping a cache and not using gl.getParameter(CURRENT_PROGRAM)
- // because gl.get* functions are slow as potatoes.
- cache.currentProgram = glArgs[0];
- },
- /**
- * Called immediately *before* 'enable' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- enable: function (glArgs, cache) {
- cache.currentState[glArgs[0]] = true;
- },
- /**
- * Called immediately *before* 'disable' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- disable: function (glArgs, cache) {
- cache.currentState[glArgs[0]] = false;
- },
- /**
- * Called immediately *before* 'blendColor' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- blendColor: function (glArgs, cache) {
- let blendColor = cache.currentState.blendColor;
- blendColor[0] = glArgs[0];
- blendColor[1] = glArgs[1];
- blendColor[2] = glArgs[2];
- blendColor[3] = glArgs[3];
- },
- /**
- * Called immediately *before* 'blendEquation' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- blendEquation: function (glArgs, cache) {
- let state = cache.currentState;
- state.blendEquationRgb = state.blendEquationAlpha = glArgs[0];
- },
- /**
- * Called immediately *before* 'blendEquationSeparate' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- blendEquationSeparate: function (glArgs, cache) {
- let state = cache.currentState;
- state.blendEquationRgb = glArgs[0];
- state.blendEquationAlpha = glArgs[1];
- },
- /**
- * Called immediately *before* 'blendFunc' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- blendFunc: function (glArgs, cache) {
- let state = cache.currentState;
- state.blendSrcRgb = state.blendSrcAlpha = glArgs[0];
- state.blendDstRgb = state.blendDstAlpha = glArgs[1];
- },
- /**
- * Called immediately *before* 'blendFuncSeparate' is requested in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- */
- blendFuncSeparate: function (glArgs, cache) {
- let state = cache.currentState;
- state.blendSrcRgb = glArgs[0];
- state.blendDstRgb = glArgs[1];
- state.blendSrcAlpha = glArgs[2];
- state.blendDstAlpha = glArgs[3];
- },
- /**
- * Called immediately *before* 'drawArrays' or 'drawElements' is requested
- * in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context initiating this call.
- */
- beforeDraw_: function (glArgs, cache, proxy) {
- let traits = cache.currentProgramTraits;
- // Handle program blackboxing.
- if (traits & PROGRAM_BLACKBOX_TRAIT) {
- return true; // Return true to break original function call.
- }
- // Handle program highlighting.
- if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
- proxy.enableHighlighting();
- }
- return false;
- },
- /**
- * Called immediately *after* 'drawArrays' or 'drawElements' is requested
- * in the context.
- *
- * @param array glArgs
- * Overridable arguments with which the function is called.
- * @param void glResult
- * The returned value of the original function call.
- * @param WebGLCache cache
- * The state storage for the WebGL context initiating this call.
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context initiating this call.
- */
- afterDraw_: function (glArgs, glResult, cache, proxy) {
- let traits = cache.currentProgramTraits;
- // Handle program highlighting.
- if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
- proxy.disableHighlighting();
- }
- }
- };
- /**
- * A mechanism for storing a single WebGL context's state, programs, shaders,
- * attributes or uniforms.
- *
- * @param number id
- * The id of the window containing the WebGL context.
- * @param WebGLRenderingContext context
- * The WebGL context for which the state is stored.
- */
- function WebGLCache(id, context) {
- this._id = id;
- this._gl = context;
- this._programs = new Map();
- this.currentState = {};
- }
- WebGLCache.prototype = {
- _id: 0,
- _gl: null,
- _programs: null,
- _currentProgramInfo: null,
- _currentAttributesMap: null,
- _currentUniformsMap: null,
- get ownerWindow() {
- return this._id;
- },
- get ownerContext() {
- return this._gl;
- },
- /**
- * A collection of flags or properties representing the context's state.
- * Implemented as an object hash and not a Map instance because keys are
- * always either strings or numbers.
- */
- currentState: null,
- /**
- * Populates the current state with values retrieved from the context.
- *
- * @param WebGLProxy proxy
- * The proxy methods for the WebGL context owning the state.
- */
- refreshState: function (proxy) {
- let gl = this._gl;
- let s = this.currentState;
- // Populate only with the necessary parameters. Not all default WebGL
- // state values are required.
- s[gl.BLEND] = proxy.isEnabled("BLEND");
- s.blendColor = proxy.getParameter("BLEND_COLOR");
- s.blendEquationRgb = proxy.getParameter("BLEND_EQUATION_RGB");
- s.blendEquationAlpha = proxy.getParameter("BLEND_EQUATION_ALPHA");
- s.blendSrcRgb = proxy.getParameter("BLEND_SRC_RGB");
- s.blendSrcAlpha = proxy.getParameter("BLEND_SRC_ALPHA");
- s.blendDstRgb = proxy.getParameter("BLEND_DST_RGB");
- s.blendDstAlpha = proxy.getParameter("BLEND_DST_ALPHA");
- },
- /**
- * Adds a program to the cache.
- *
- * @param WebGLProgram program
- * The shader for which the traits are to be cached.
- * @param number traits
- * A default properties mask set for the program.
- */
- addProgram: function (program, traits) {
- this._programs.set(program, {
- traits: traits,
- attributes: [], // keys are GLints (numbers)
- uniforms: new Map() // keys are WebGLUniformLocations (objects)
- });
- },
- /**
- * Adds a specific trait to a program. The effect of such properties is
- * determined by the consumer of this cache.
- *
- * @param WebGLProgram program
- * The program to add the trait to.
- * @param number trait
- * The property added to the program.
- */
- setProgramTrait: function (program, trait) {
- this._programs.get(program).traits |= trait;
- },
- /**
- * Removes a specific trait from a program.
- *
- * @param WebGLProgram program
- * The program to remove the trait from.
- * @param number trait
- * The property removed from the program.
- */
- unsetProgramTrait: function (program, trait) {
- this._programs.get(program).traits &= ~trait;
- },
- /**
- * Sets the currently used program in the context.
- * @param WebGLProgram program
- */
- set currentProgram(program) {
- let programInfo = this._programs.get(program);
- if (programInfo == null) {
- return;
- }
- this._currentProgramInfo = programInfo;
- this._currentAttributesMap = programInfo.attributes;
- this._currentUniformsMap = programInfo.uniforms;
- },
- /**
- * Gets the traits for the currently used program.
- * @return number
- */
- get currentProgramTraits() {
- return this._currentProgramInfo.traits;
- },
- /**
- * Adds an attribute to the cache.
- *
- * @param WebGLProgram program
- * The program for which the attribute is bound.
- * @param string name
- * The attribute name.
- * @param GLint value
- * The attribute value.
- */
- addAttribute: function (program, name, value) {
- this._programs.get(program).attributes[value] = {
- name: name,
- value: value
- };
- },
- /**
- * Adds a uniform to the cache.
- *
- * @param WebGLProgram program
- * The program for which the uniform is bound.
- * @param string name
- * The uniform name.
- * @param WebGLUniformLocation value
- * The uniform value.
- */
- addUniform: function (program, name, value) {
- this._programs.get(program).uniforms.set(new XPCNativeWrapper(value), {
- name: name,
- value: value
- });
- },
- /**
- * Updates the attribute locations for a specific program.
- * This is necessary, for example, when the shader is relinked and all the
- * attribute locations become obsolete.
- *
- * @param WebGLProgram program
- * The program for which the attributes need updating.
- */
- updateAttributesForProgram: function (program) {
- let attributes = this._programs.get(program).attributes;
- for (let attribute of attributes) {
- attribute.value = this._gl.getAttribLocation(program, attribute.name);
- }
- },
- /**
- * Updates the uniform locations for a specific program.
- * This is necessary, for example, when the shader is relinked and all the
- * uniform locations become obsolete.
- *
- * @param WebGLProgram program
- * The program for which the uniforms need updating.
- */
- updateUniformsForProgram: function (program) {
- let uniforms = this._programs.get(program).uniforms;
- for (let [, uniform] of uniforms) {
- uniform.value = this._gl.getUniformLocation(program, uniform.name);
- }
- },
- /**
- * Gets the actual attribute location in a specific program.
- * When relinked, all the attribute locations become obsolete and are updated
- * in the cache. This method returns the (current) real attribute location.
- *
- * @param GLint initialValue
- * The initial attribute value.
- * @return GLint
- * The current attribute value, or the initial value if it's already
- * up to date with its corresponding program.
- */
- getCurrentAttributeLocation: function (initialValue) {
- let attributes = this._currentAttributesMap;
- let currentInfo = attributes ? attributes[initialValue] : null;
- return currentInfo ? currentInfo.value : initialValue;
- },
- /**
- * Gets the actual uniform location in a specific program.
- * When relinked, all the uniform locations become obsolete and are updated
- * in the cache. This method returns the (current) real uniform location.
- *
- * @param WebGLUniformLocation initialValue
- * The initial uniform value.
- * @return WebGLUniformLocation
- * The current uniform value, or the initial value if it's already
- * up to date with its corresponding program.
- */
- getCurrentUniformLocation: function (initialValue) {
- let uniforms = this._currentUniformsMap;
- let currentInfo = uniforms ? uniforms.get(initialValue) : null;
- return currentInfo ? currentInfo.value : initialValue;
- }
- };
- /**
- * A mechanism for injecting or qureying state into/from a single WebGL context.
- *
- * Any interaction with a WebGL context should go through this proxy.
- * Otherwise, the corresponding observer would register the calls as coming
- * from content, which is usually not desirable. Infinite call stacks are bad.
- *
- * @param number id
- * The id of the window containing the WebGL context.
- * @param WebGLRenderingContext context
- * The WebGL context used for the proxy methods.
- * @param WebGLCache cache
- * The state storage for the corresponding context.
- * @param WebGLObserver observer
- * The observer watching function calls in the corresponding context.
- */
- function WebGLProxy(id, context, cache, observer) {
- this._id = id;
- this._gl = context;
- this._cache = cache;
- this._observer = observer;
- let exports = [
- "isEnabled",
- "getParameter",
- "getAttachedShaders",
- "getShaderSource",
- "getShaderOfType",
- "compileShader",
- "enableHighlighting",
- "disableHighlighting",
- "readPixels"
- ];
- exports.forEach(e => this[e] = (...args) => this._call(e, args));
- }
- WebGLProxy.prototype = {
- _id: 0,
- _gl: null,
- _cache: null,
- _observer: null,
- get ownerWindow() {
- return this._id;
- },
- get ownerContext() {
- return this._gl;
- },
- /**
- * Test whether a WebGL capability is enabled.
- *
- * @param string name
- * The WebGL capability name, for example "BLEND".
- * @return boolean
- * True if enabled, false otherwise.
- */
- _isEnabled: function (name) {
- return this._gl.isEnabled(this._gl[name]);
- },
- /**
- * Returns the value for the specified WebGL parameter name.
- *
- * @param string name
- * The WebGL parameter name, for example "BLEND_COLOR".
- * @return any
- * The corresponding parameter's value.
- */
- _getParameter: function (name) {
- return this._gl.getParameter(this._gl[name]);
- },
- /**
- * Returns the renderbuffer property value for the specified WebGL parameter.
- * If no renderbuffer binding is available, null is returned.
- *
- * @param string name
- * The WebGL parameter name, for example "BLEND_COLOR".
- * @return any
- * The corresponding parameter's value.
- */
- _getRenderbufferParameter: function (name) {
- if (!this._getParameter("RENDERBUFFER_BINDING")) {
- return null;
- }
- let gl = this._gl;
- return gl.getRenderbufferParameter(gl.RENDERBUFFER, gl[name]);
- },
- /**
- * Returns the framebuffer property value for the specified WebGL parameter.
- * If no framebuffer binding is available, null is returned.
- *
- * @param string type
- * The framebuffer object attachment point, for example "COLOR_ATTACHMENT0".
- * @param string name
- * The WebGL parameter name, for example "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME".
- * If unspecified, defaults to "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE".
- * @return any
- * The corresponding parameter's value.
- */
- _getFramebufferAttachmentParameter: function (type, name = "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE") {
- if (!this._getParameter("FRAMEBUFFER_BINDING")) {
- return null;
- }
- let gl = this._gl;
- return gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[type], gl[name]);
- },
- /**
- * Returns the shader objects attached to a program object.
- *
- * @param WebGLProgram program
- * The program for which to retrieve the attached shaders.
- * @return array
- * The attached vertex and fragment shaders.
- */
- _getAttachedShaders: function (program) {
- return this._gl.getAttachedShaders(program);
- },
- /**
- * Returns the source code string from a shader object.
- *
- * @param WebGLShader shader
- * The shader for which to retrieve the source code.
- * @return string
- * The shader's source code.
- */
- _getShaderSource: function (shader) {
- return this._gl.getShaderSource(shader);
- },
- /**
- * Finds a shader of the specified type in a list.
- *
- * @param WebGLShader[] shaders
- * The shaders for which to check the type.
- * @param string type
- * Either "vertex" or "fragment".
- * @return WebGLShader | null
- * The shader of the specified type, or null if nothing is found.
- */
- _getShaderOfType: function (shaders, type) {
- let gl = this._gl;
- let shaderTypeEnum = {
- vertex: gl.VERTEX_SHADER,
- fragment: gl.FRAGMENT_SHADER
- }[type];
- for (let shader of shaders) {
- if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == shaderTypeEnum) {
- return shader;
- }
- }
- return null;
- },
- /**
- * Changes a shader's source code and relinks the respective program.
- *
- * @param WebGLProgram program
- * The program who's linked shader is to be modified.
- * @param WebGLShader shader
- * The shader to be modified.
- * @param string text
- * The new shader source code.
- * @return object
- * An object containing the compilation and linking status.
- */
- _compileShader: function (program, shader, text) {
- let gl = this._gl;
- gl.shaderSource(shader, text);
- gl.compileShader(shader);
- gl.linkProgram(program);
- let error = { compile: "", link: "" };
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
- error.compile = gl.getShaderInfoLog(shader);
- }
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
- error.link = gl.getShaderInfoLog(shader);
- }
- this._cache.updateAttributesForProgram(program);
- this._cache.updateUniformsForProgram(program);
- return error;
- },
- /**
- * Enables color blending based on the geometry highlight tint.
- */
- _enableHighlighting: function () {
- let gl = this._gl;
- // Avoid changing the blending params when "rendering to texture".
- // Check drawing to a custom framebuffer bound to the default renderbuffer.
- let hasFramebuffer = this._getParameter("FRAMEBUFFER_BINDING");
- let hasRenderbuffer = this._getParameter("RENDERBUFFER_BINDING");
- if (hasFramebuffer && !hasRenderbuffer) {
- return;
- }
- // Check drawing to a depth or stencil component of the framebuffer.
- let writesDepth = this._getFramebufferAttachmentParameter("DEPTH_ATTACHMENT");
- let writesStencil = this._getFramebufferAttachmentParameter("STENCIL_ATTACHMENT");
- if (writesDepth || writesStencil) {
- return;
- }
- // Non-premultiplied alpha blending based on a predefined constant color.
- // Simply using gl.colorMask won't work, because we want non-tinted colors
- // to be drawn as black, not ignored.
- gl.enable(gl.BLEND);
- gl.blendColor.apply(gl, this.highlightTint);
- gl.blendEquation(gl.FUNC_ADD);
- gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.CONSTANT_COLOR, gl.ZERO);
- this.wasHighlighting = true;
- },
- /**
- * Disables color blending based on the geometry highlight tint, by
- * reverting the corresponding params back to their original values.
- */
- _disableHighlighting: function () {
- let gl = this._gl;
- let s = this._cache.currentState;
- gl[s[gl.BLEND] ? "enable" : "disable"](gl.BLEND);
- gl.blendColor.apply(gl, s.blendColor);
- gl.blendEquationSeparate(s.blendEquationRgb, s.blendEquationAlpha);
- gl.blendFuncSeparate(s.blendSrcRgb, s.blendDstRgb, s.blendSrcAlpha, s.blendDstAlpha);
- },
- /**
- * Returns the pixel values at the position specified on the canvas.
- */
- _readPixels: function (x, y, w, h, format, type, buffer) {
- this._gl.readPixels(x, y, w, h, format, type, buffer);
- },
- /**
- * The color tint used for highlighting geometry.
- * @see _enableHighlighting and _disableHighlighting.
- */
- highlightTint: [0, 0, 0, 0],
- /**
- * Executes a function in this object.
- *
- * This method makes sure that any handlers in the context observer are
- * suppressed, hence stopping observing any context function calls.
- *
- * @param string funcName
- * The function to call.
- * @param array args
- * An array of arguments.
- * @return any
- * The called function result.
- */
- _call: function (funcName, args) {
- let prevState = this._observer.suppressHandlers;
- this._observer.suppressHandlers = true;
- let result = this["_" + funcName].apply(this, args);
- this._observer.suppressHandlers = prevState;
- return result;
- }
- };
- // Utility functions.
- function removeFromMap(map, predicate) {
- for (let [key, value] of map) {
- if (predicate(value)) {
- map.delete(key);
- }
- }
- }
- function removeFromArray(array, predicate) {
- for (let i = 0; i < array.length;) {
- if (predicate(array[i])) {
- array.splice(i, 1);
- } else {
- i++;
- }
- }
- }
|