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