webgl.js 41 KB

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