threejs-lessons-helper.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  1. /*
  2. * Copyright 2012, Gregg Tavares.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Gregg Tavares. nor the names of his
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. /* global define */
  32. (function(root, factory) { // eslint-disable-line
  33. if (typeof define === 'function' && define.amd) {
  34. // AMD. Register as an anonymous module.
  35. define([], function() {
  36. return factory.call(root);
  37. });
  38. } else {
  39. // Browser globals
  40. root.threejsLessonsHelper = factory.call(root);
  41. }
  42. }(this, function() {
  43. 'use strict'; // eslint-disable-line
  44. const topWindow = this;
  45. /**
  46. * Check if the page is embedded.
  47. * @param {Window?) w window to check
  48. * @return {boolean} True of we are in an iframe
  49. */
  50. function isInIFrame(w) {
  51. w = w || topWindow;
  52. return w !== w.top;
  53. }
  54. function updateCSSIfInIFrame() {
  55. if (isInIFrame()) {
  56. try {
  57. document.getElementsByTagName('html')[0].className = 'iframe';
  58. } catch (e) {
  59. // eslint-disable-line
  60. }
  61. try {
  62. document.body.className = 'iframe';
  63. } catch (e) {
  64. // eslint-disable-line
  65. }
  66. }
  67. }
  68. function isInEditor() {
  69. return window.location.href.substring(0, 4) === 'blob';
  70. }
  71. /**
  72. * Creates a webgl context. If creation fails it will
  73. * change the contents of the container of the <canvas>
  74. * tag to an error message with the correct links for WebGL.
  75. * @param {HTMLCanvasElement} canvas. The canvas element to
  76. * create a context from.
  77. * @param {WebGLContextCreationAttirbutes} opt_attribs Any
  78. * creation attributes you want to pass in.
  79. * @return {WebGLRenderingContext} The created context.
  80. * @memberOf module:webgl-utils
  81. */
  82. function showNeedWebGL(canvas) {
  83. const doc = canvas.ownerDocument;
  84. if (doc) {
  85. const temp = doc.createElement('div');
  86. temp.innerHTML = `
  87. <div style="
  88. position: absolute;
  89. left: 0;
  90. top: 0;
  91. background-color: #DEF;
  92. width: 100vw;
  93. height: 100vh;
  94. display: flex;
  95. flex-flow: column;
  96. justify-content: center;
  97. align-content: center;
  98. align-items: center;
  99. ">
  100. <div style="text-align: center;">
  101. It doesn't appear your browser supports WebGL.<br/>
  102. <a href="http://get.webgl.org" target="_blank">Click here for more information.</a>
  103. </div>
  104. </div>
  105. `;
  106. const div = temp.querySelector('div');
  107. doc.body.appendChild(div);
  108. }
  109. }
  110. const origConsole = {};
  111. function setupConsole() {
  112. const parent = document.createElement('div');
  113. parent.className = 'console';
  114. Object.assign(parent.style, {
  115. fontFamily: 'monospace',
  116. fontSize: 'medium',
  117. maxHeight: '50%',
  118. position: 'fixed',
  119. bottom: 0,
  120. left: 0,
  121. width: '100%',
  122. overflow: 'auto',
  123. background: 'rgba(221, 221, 221, 0.9)',
  124. });
  125. const toggle = document.createElement('div');
  126. let show = false;
  127. Object.assign(toggle.style, {
  128. position: 'absolute',
  129. right: 0,
  130. bottom: 0,
  131. background: '#EEE',
  132. 'font-size': 'smaller',
  133. cursor: 'pointer',
  134. });
  135. toggle.addEventListener('click', showHideConsole);
  136. function showHideConsole() {
  137. show = !show;
  138. toggle.textContent = show ? '☒' : '☐';
  139. parent.style.display = show ? '' : 'none';
  140. }
  141. showHideConsole();
  142. const maxLines = 100;
  143. const lines = [];
  144. let added = false;
  145. function addLine(type, str, color, prefix) {
  146. const div = document.createElement('div');
  147. div.textContent = prefix + str;
  148. div.className = type;
  149. div.style.color = color;
  150. parent.appendChild(div);
  151. lines.push(div);
  152. if (!added) {
  153. added = true;
  154. document.body.appendChild(parent);
  155. document.body.appendChild(toggle);
  156. }
  157. // scrollIntoView only works in Chrome
  158. // In Firefox and Safari scrollIntoView inside an iframe moves
  159. // that element into the view. It should argably only move that
  160. // element inside the iframe itself, otherwise that's giving
  161. // any random iframe control to bring itself into view against
  162. // the parent's wishes.
  163. //
  164. // note that even if we used a solution (which is to manually set
  165. // scrollTop) there's a UI issue that if the user manaully scrolls
  166. // we want to stop scrolling automatically and if they move back
  167. // to the bottom we want to pick up scrolling automatically.
  168. // Kind of a PITA so TBD
  169. //
  170. // div.scrollIntoView();
  171. }
  172. function addLines(type, str, color, prefix) {
  173. while (lines.length > maxLines) {
  174. const div = lines.shift();
  175. div.parentNode.removeChild(div);
  176. }
  177. addLine(type, str, color, prefix);
  178. }
  179. function wrapFunc(obj, funcName, color, prefix) {
  180. const oldFn = obj[funcName];
  181. origConsole[funcName] = oldFn.bind(obj);
  182. return function(...args) {
  183. addLines(funcName, [...args].join(' '), color, prefix);
  184. oldFn.apply(obj, arguments);
  185. };
  186. }
  187. window.console.log = wrapFunc(window.console, 'log', 'black', '');
  188. window.console.warn = wrapFunc(window.console, 'warn', 'black', '⚠');
  189. window.console.error = wrapFunc(window.console, 'error', 'red', '❌');
  190. }
  191. /**
  192. * Gets a WebGL context.
  193. * makes its backing store the size it is displayed.
  194. * @param {HTMLCanvasElement} canvas a canvas element.
  195. * @memberOf module:webgl-utils
  196. */
  197. let setupLesson = function(canvas) {
  198. // only once
  199. setupLesson = function() {};
  200. if (canvas) {
  201. canvas.addEventListener('webglcontextlost', function(e) {
  202. // the default is to do nothing. Preventing the default
  203. // means allowing context to be restored
  204. e.preventDefault();
  205. const div = document.createElement('div');
  206. div.className = 'contextlost';
  207. div.innerHTML = '<div>Context Lost: Click To Reload</div>';
  208. div.addEventListener('click', function() {
  209. window.location.reload();
  210. });
  211. document.body.appendChild(div);
  212. });
  213. canvas.addEventListener('webglcontextrestored', function() {
  214. // just reload the page. Easiest.
  215. window.location.reload();
  216. });
  217. }
  218. if (isInIFrame()) {
  219. updateCSSIfInIFrame();
  220. }
  221. };
  222. /**
  223. * Get's the iframe in the parent document
  224. * that is displaying the specified window .
  225. * @param {Window} window window to check.
  226. * @return {HTMLIFrameElement?) the iframe element if window is in an iframe
  227. */
  228. function getIFrameForWindow(window) {
  229. if (!isInIFrame(window)) {
  230. return;
  231. }
  232. const iframes = window.parent.document.getElementsByTagName('iframe');
  233. for (let ii = 0; ii < iframes.length; ++ii) {
  234. const iframe = iframes[ii];
  235. if (iframe.contentDocument === window.document) {
  236. return iframe; // eslint-disable-line
  237. }
  238. }
  239. }
  240. /**
  241. * Returns true if window is on screen. The main window is
  242. * always on screen windows in iframes might not be.
  243. * @param {Window} window the window to check.
  244. * @return {boolean} true if window is on screen.
  245. */
  246. function isFrameVisible(window) {
  247. try {
  248. const iframe = getIFrameForWindow(window);
  249. if (!iframe) {
  250. return true;
  251. }
  252. const bounds = iframe.getBoundingClientRect();
  253. const isVisible = bounds.top < window.parent.innerHeight && bounds.bottom >= 0 &&
  254. bounds.left < window.parent.innerWidth && bounds.right >= 0;
  255. return isVisible && isFrameVisible(window.parent);
  256. } catch (e) {
  257. return true; // We got a security error?
  258. }
  259. }
  260. /**
  261. * Returns true if element is on screen.
  262. * @param {HTMLElement} element the element to check.
  263. * @return {boolean} true if element is on screen.
  264. */
  265. function isOnScreen(element) {
  266. let isVisible = true;
  267. if (element) {
  268. const bounds = element.getBoundingClientRect();
  269. isVisible = bounds.top < topWindow.innerHeight && bounds.bottom >= 0;
  270. }
  271. return isVisible && isFrameVisible(topWindow);
  272. }
  273. // Replace requestAnimationFrame.
  274. if (topWindow.requestAnimationFrame) {
  275. topWindow.requestAnimationFrame = (function(oldRAF) {
  276. return function(callback, element) {
  277. const handler = function() {
  278. return oldRAF(isOnScreen(element) ? callback : handler, element);
  279. };
  280. return handler();
  281. };
  282. }(topWindow.requestAnimationFrame));
  283. }
  284. updateCSSIfInIFrame();
  285. //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
  286. /*
  287. ** Copyright (c) 2012 The Khronos Group Inc.
  288. **
  289. ** Permission is hereby granted, free of charge, to any person obtaining a
  290. ** copy of this software and/or associated documentation files (the
  291. ** "Materials"), to deal in the Materials without restriction, including
  292. ** without limitation the rights to use, copy, modify, merge, publish,
  293. ** distribute, sublicense, and/or sell copies of the Materials, and to
  294. ** permit persons to whom the Materials are furnished to do so, subject to
  295. ** the following conditions:
  296. **
  297. ** The above copyright notice and this permission notice shall be included
  298. ** in all copies or substantial portions of the Materials.
  299. **
  300. ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  301. ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  302. ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  303. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  304. ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  305. ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  306. ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
  307. */
  308. /**
  309. * Types of contexts we have added to map
  310. */
  311. const mappedContextTypes = {};
  312. /**
  313. * Map of numbers to names.
  314. * @type {Object}
  315. */
  316. const glEnums = {};
  317. /**
  318. * Map of names to numbers.
  319. * @type {Object}
  320. */
  321. const enumStringToValue = {};
  322. /**
  323. * Initializes this module. Safe to call more than once.
  324. * @param {!WebGLRenderingContext} ctx A WebGL context. If
  325. * you have more than one context it doesn't matter which one
  326. * you pass in, it is only used to pull out constants.
  327. */
  328. function addEnumsForContext(ctx, type) {
  329. if (!mappedContextTypes[type]) {
  330. mappedContextTypes[type] = true;
  331. for (const propertyName in ctx) {
  332. if (typeof ctx[propertyName] === 'number') {
  333. glEnums[ctx[propertyName]] = propertyName;
  334. enumStringToValue[propertyName] = ctx[propertyName];
  335. }
  336. }
  337. }
  338. }
  339. function enumArrayToString(enums) {
  340. const enumStrings = [];
  341. if (enums.length) {
  342. for (let i = 0; i < enums.length; ++i) {
  343. enums.push(glEnumToString(enums[i])); // eslint-disable-line
  344. }
  345. return '[' + enumStrings.join(', ') + ']';
  346. }
  347. return enumStrings.toString();
  348. }
  349. function makeBitFieldToStringFunc(enums) {
  350. return function(value) {
  351. let orResult = 0;
  352. const orEnums = [];
  353. for (let i = 0; i < enums.length; ++i) {
  354. const enumValue = enumStringToValue[enums[i]];
  355. if ((value & enumValue) !== 0) {
  356. orResult |= enumValue;
  357. orEnums.push(glEnumToString(enumValue)); // eslint-disable-line
  358. }
  359. }
  360. if (orResult === value) {
  361. return orEnums.join(' | ');
  362. } else {
  363. return glEnumToString(value); // eslint-disable-line
  364. }
  365. };
  366. }
  367. const destBufferBitFieldToString = makeBitFieldToStringFunc([
  368. 'COLOR_BUFFER_BIT',
  369. 'DEPTH_BUFFER_BIT',
  370. 'STENCIL_BUFFER_BIT',
  371. ]);
  372. /**
  373. * Which arguments are enums based on the number of arguments to the function.
  374. * So
  375. * 'texImage2D': {
  376. * 9: { 0:true, 2:true, 6:true, 7:true },
  377. * 6: { 0:true, 2:true, 3:true, 4:true },
  378. * },
  379. *
  380. * means if there are 9 arguments then 6 and 7 are enums, if there are 6
  381. * arguments 3 and 4 are enums. Maybe a function as well in which case
  382. * value is passed to function and returns a string
  383. *
  384. * @type {!Object.<number, (!Object.<number, string>|function)}
  385. */
  386. const glValidEnumContexts = {
  387. // Generic setters and getters
  388. 'enable': {1: { 0:true }},
  389. 'disable': {1: { 0:true }},
  390. 'getParameter': {1: { 0:true }},
  391. // Rendering
  392. 'drawArrays': {3:{ 0:true }},
  393. 'drawElements': {4:{ 0:true, 2:true }},
  394. 'drawArraysInstanced': {4: { 0:true }},
  395. 'drawElementsInstanced': {5: {0:true, 2: true }},
  396. 'drawRangeElements': {6: {0:true, 4: true }},
  397. // Shaders
  398. 'createShader': {1: { 0:true }},
  399. 'getShaderParameter': {2: { 1:true }},
  400. 'getProgramParameter': {2: { 1:true }},
  401. 'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
  402. // Vertex attributes
  403. 'getVertexAttrib': {2: { 1:true }},
  404. 'vertexAttribPointer': {6: { 2:true }},
  405. 'vertexAttribIPointer': {5: { 2:true }}, // WebGL2
  406. // Textures
  407. 'bindTexture': {2: { 0:true }},
  408. 'activeTexture': {1: { 0:true }},
  409. 'getTexParameter': {2: { 0:true, 1:true }},
  410. 'texParameterf': {3: { 0:true, 1:true }},
  411. 'texParameteri': {3: { 0:true, 1:true, 2:true }},
  412. 'texImage2D': {
  413. 9: { 0:true, 2:true, 6:true, 7:true },
  414. 6: { 0:true, 2:true, 3:true, 4:true },
  415. 10: { 0:true, 2:true, 6:true, 7:true }, // WebGL2
  416. },
  417. 'texImage3D': {
  418. 10: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  419. 11: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  420. },
  421. 'texSubImage2D': {
  422. 9: { 0:true, 6:true, 7:true },
  423. 7: { 0:true, 4:true, 5:true },
  424. 10: { 0:true, 6:true, 7:true }, // WebGL2
  425. },
  426. 'texSubImage3D': {
  427. 11: { 0:true, 8:true, 9:true }, // WebGL2
  428. 12: { 0:true, 8:true, 9:true }, // WebGL2
  429. },
  430. 'texStorage2D': { 5: { 0:true, 2:true }}, // WebGL2
  431. 'texStorage3D': { 6: { 0:true, 2:true }}, // WebGL2
  432. 'copyTexImage2D': {8: { 0:true, 2:true }},
  433. 'copyTexSubImage2D': {8: { 0:true }},
  434. 'copyTexSubImage3D': {9: { 0:true }}, // WebGL2
  435. 'generateMipmap': {1: { 0:true }},
  436. 'compressedTexImage2D': {
  437. 7: { 0: true, 2:true },
  438. 8: { 0: true, 2:true }, // WebGL2
  439. },
  440. 'compressedTexSubImage2D': {
  441. 8: { 0: true, 6:true },
  442. 9: { 0: true, 6:true }, // WebGL2
  443. },
  444. 'compressedTexImage3D': {
  445. 8: { 0: true, 2: true, }, // WebGL2
  446. 9: { 0: true, 2: true, }, // WebGL2
  447. },
  448. 'compressedTexSubImage3D': {
  449. 9: { 0: true, 8: true, }, // WebGL2
  450. 10: { 0: true, 8: true, }, // WebGL2
  451. },
  452. // Buffer objects
  453. 'bindBuffer': {2: { 0:true }},
  454. 'bufferData': {
  455. 3: { 0:true, 2:true },
  456. 4: { 0:true, 2:true }, // WebGL2
  457. 5: { 0:true, 2:true }, // WebGL2
  458. },
  459. 'bufferSubData': {
  460. 3: { 0:true },
  461. 4: { 0:true }, // WebGL2
  462. 5: { 0:true }, // WebGL2
  463. },
  464. 'copyBufferSubData': {
  465. 5: { 0:true }, // WeBGL2
  466. },
  467. 'getBufferParameter': {2: { 0:true, 1:true }},
  468. 'getBufferSubData': {
  469. 3: { 0: true, }, // WebGL2
  470. 4: { 0: true, }, // WebGL2
  471. 5: { 0: true, }, // WebGL2
  472. },
  473. // Renderbuffers and framebuffers
  474. 'pixelStorei': {2: { 0:true, 1:true }},
  475. 'readPixels': {
  476. 7: { 4:true, 5:true },
  477. 8: { 4:true, 5:true }, // WebGL2
  478. },
  479. 'bindRenderbuffer': {2: { 0:true }},
  480. 'bindFramebuffer': {2: { 0:true }},
  481. 'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }}, // WebGL2
  482. 'checkFramebufferStatus': {1: { 0:true }},
  483. 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
  484. 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
  485. 'framebufferTextureLayer': {5: {0:true, 1:true }}, // WebGL2
  486. 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
  487. 'getInternalformatParameter': {3: {0:true, 1:true, 2:true }}, // WebGL2
  488. 'getRenderbufferParameter': {2: { 0:true, 1:true }},
  489. 'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }}, // WebGL2
  490. 'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }}, // WebGL2
  491. 'readBuffer': {1: {0: true}}, // WebGL2
  492. 'renderbufferStorage': {4: { 0:true, 1:true }},
  493. 'renderbufferStorageMultisample': {5: { 0: true, 2: true }}, // WebGL2
  494. // Frame buffer operations (clear, blend, depth test, stencil)
  495. 'clear': {1: { 0: destBufferBitFieldToString }},
  496. 'depthFunc': {1: { 0:true }},
  497. 'blendFunc': {2: { 0:true, 1:true }},
  498. 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  499. 'blendEquation': {1: { 0:true }},
  500. 'blendEquationSeparate': {2: { 0:true, 1:true }},
  501. 'stencilFunc': {3: { 0:true }},
  502. 'stencilFuncSeparate': {4: { 0:true, 1:true }},
  503. 'stencilMaskSeparate': {2: { 0:true }},
  504. 'stencilOp': {3: { 0:true, 1:true, 2:true }},
  505. 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  506. // Culling
  507. 'cullFace': {1: { 0:true }},
  508. 'frontFace': {1: { 0:true }},
  509. // ANGLE_instanced_arrays extension
  510. 'drawArraysInstancedANGLE': {4: { 0:true }},
  511. 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
  512. // EXT_blend_minmax extension
  513. 'blendEquationEXT': {1: { 0:true }},
  514. // Multiple Render Targets
  515. 'drawBuffersWebGL': {1: {0: enumArrayToString, }}, // WEBGL_draw_bufers
  516. 'drawBuffers': {1: {0: enumArrayToString, }}, // WebGL2
  517. 'clearBufferfv': {
  518. 4: {0: true }, // WebGL2
  519. 5: {0: true }, // WebGL2
  520. },
  521. 'clearBufferiv': {
  522. 4: {0: true }, // WebGL2
  523. 5: {0: true }, // WebGL2
  524. },
  525. 'clearBufferuiv': {
  526. 4: {0: true }, // WebGL2
  527. 5: {0: true }, // WebGL2
  528. },
  529. 'clearBufferfi': { 4: {0: true}}, // WebGL2
  530. // QueryObjects
  531. 'beginQuery': { 2: { 0: true }}, // WebGL2
  532. 'endQuery': { 1: { 0: true }}, // WebGL2
  533. 'getQuery': { 2: { 0: true, 1: true }}, // WebGL2
  534. 'getQueryParameter': { 2: { 1: true }}, // WebGL2
  535. // Sampler Objects
  536. 'samplerParameteri': { 3: { 1: true }}, // WebGL2
  537. 'samplerParameterf': { 3: { 1: true }}, // WebGL2
  538. 'getSamplerParameter': { 2: { 1: true }}, // WebGL2
  539. // Sync objects
  540. 'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }}, // WebGL2
  541. 'fenceSync': { 2: { 0: true }}, // WebGL2
  542. 'getSyncParameter': { 2: { 1: true }}, // WebGL2
  543. // Transform Feedback
  544. 'bindTransformFeedback': { 2: { 0: true }}, // WebGL2
  545. 'beginTransformFeedback': { 1: { 0: true }}, // WebGL2
  546. // Uniform Buffer Objects and Transform Feedback Buffers
  547. 'bindBufferBase': { 3: { 0: true }}, // WebGL2
  548. 'bindBufferRange': { 5: { 0: true }}, // WebGL2
  549. 'getIndexedParameter': { 2: { 0: true }}, // WebGL2
  550. 'getActiveUniforms': { 3: { 2: true }}, // WebGL2
  551. 'getActiveUniformBlockParameter': { 3: { 2: true }}, // WebGL2
  552. };
  553. /**
  554. * Gets an string version of an WebGL enum.
  555. *
  556. * Example:
  557. * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
  558. *
  559. * @param {number} value Value to return an enum for
  560. * @return {string} The string version of the enum.
  561. */
  562. function glEnumToString(value) {
  563. const name = glEnums[value];
  564. return (name !== undefined) ? ('gl.' + name) :
  565. ('/*UNKNOWN WebGL ENUM*/ 0x' + value.toString(16) + '');
  566. }
  567. /**
  568. * Returns the string version of a WebGL argument.
  569. * Attempts to convert enum arguments to strings.
  570. * @param {string} functionName the name of the WebGL function.
  571. * @param {number} numArgs the number of arguments passed to the function.
  572. * @param {number} argumentIndx the index of the argument.
  573. * @param {*} value The value of the argument.
  574. * @return {string} The value as a string.
  575. */
  576. function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
  577. const funcInfos = glValidEnumContexts[functionName];
  578. if (funcInfos !== undefined) {
  579. const funcInfo = funcInfos[numArgs];
  580. if (funcInfo !== undefined) {
  581. const argType = funcInfo[argumentIndex];
  582. if (argType) {
  583. if (typeof argType === 'function') {
  584. return argType(value);
  585. } else {
  586. return glEnumToString(value);
  587. }
  588. }
  589. }
  590. }
  591. if (value === null) {
  592. return 'null';
  593. } else if (value === undefined) {
  594. return 'undefined';
  595. } else {
  596. return value.toString();
  597. }
  598. }
  599. /**
  600. * Converts the arguments of a WebGL function to a string.
  601. * Attempts to convert enum arguments to strings.
  602. *
  603. * @param {string} functionName the name of the WebGL function.
  604. * @param {number} args The arguments.
  605. * @return {string} The arguments as a string.
  606. */
  607. function glFunctionArgsToString(functionName, args) {
  608. // apparently we can't do args.join(",");
  609. const argStrs = [];
  610. const numArgs = args.length;
  611. for (let ii = 0; ii < numArgs; ++ii) {
  612. argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
  613. }
  614. return argStrs.join(', ');
  615. }
  616. function makePropertyWrapper(wrapper, original, propertyName) {
  617. wrapper.__defineGetter__(propertyName, function() { // eslint-disable-line
  618. return original[propertyName];
  619. });
  620. // TODO(gmane): this needs to handle properties that take more than
  621. // one value?
  622. wrapper.__defineSetter__(propertyName, function(value) { // eslint-disable-line
  623. original[propertyName] = value;
  624. });
  625. }
  626. /**
  627. * Given a WebGL context returns a wrapped context that calls
  628. * gl.getError after every command and calls a function if the
  629. * result is not gl.NO_ERROR.
  630. *
  631. * @param {!WebGLRenderingContext} ctx The webgl context to
  632. * wrap.
  633. * @param {!function(err, funcName, args): void} opt_onErrorFunc
  634. * The function to call when gl.getError returns an
  635. * error. If not specified the default function calls
  636. * console.log with a message.
  637. * @param {!function(funcName, args): void} opt_onFunc The
  638. * function to call when each webgl function is called.
  639. * You can use this to log all calls for example.
  640. * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
  641. * to call getError on if different than ctx.
  642. */
  643. function makeDebugContext(ctx, options) {
  644. options = options || {};
  645. const errCtx = options.errCtx || ctx;
  646. const onFunc = options.funcFunc;
  647. const sharedState = options.sharedState || {
  648. numDrawCallsRemaining: options.maxDrawCalls || -1,
  649. wrappers: {},
  650. };
  651. options.sharedState = sharedState;
  652. const errorFunc = options.errorFunc || function(err, functionName, args) {
  653. console.error("WebGL error " + glEnumToString(err) + " in " + functionName + // eslint-disable-line
  654. '(' + glFunctionArgsToString(functionName, args) + ')');
  655. };
  656. // Holds booleans for each GL error so after we get the error ourselves
  657. // we can still return it to the client app.
  658. const glErrorShadow = { };
  659. const wrapper = {};
  660. function removeChecks() {
  661. Object.keys(sharedState.wrappers).forEach(function(name) {
  662. const pair = sharedState.wrappers[name];
  663. const wrapper = pair.wrapper;
  664. const orig = pair.orig;
  665. for (const propertyName in wrapper) {
  666. if (typeof wrapper[propertyName] === 'function') {
  667. wrapper[propertyName] = orig[propertyName].bind(orig);
  668. }
  669. }
  670. });
  671. }
  672. function checkMaxDrawCalls() {
  673. if (sharedState.numDrawCallsRemaining === 0) {
  674. removeChecks();
  675. }
  676. --sharedState.numDrawCallsRemaining;
  677. }
  678. function noop() {
  679. }
  680. // Makes a function that calls a WebGL function and then calls getError.
  681. function makeErrorWrapper(ctx, functionName) {
  682. const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
  683. return function() {
  684. if (onFunc) {
  685. onFunc(functionName, arguments);
  686. }
  687. const result = ctx[functionName].apply(ctx, arguments);
  688. const err = errCtx.getError();
  689. if (err !== 0) {
  690. glErrorShadow[err] = true;
  691. errorFunc(err, functionName, arguments);
  692. }
  693. check();
  694. return result;
  695. };
  696. }
  697. function makeGetExtensionWrapper(ctx, wrapped) {
  698. return function() {
  699. const extensionName = arguments[0];
  700. let ext = sharedState.wrappers[extensionName];
  701. if (!ext) {
  702. ext = wrapped.apply(ctx, arguments);
  703. if (ext) {
  704. const origExt = ext;
  705. ext = makeDebugContext(ext, options);
  706. sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
  707. addEnumsForContext(origExt, extensionName);
  708. }
  709. }
  710. return ext;
  711. };
  712. }
  713. // Make a an object that has a copy of every property of the WebGL context
  714. // but wraps all functions.
  715. for (const propertyName in ctx) {
  716. if (typeof ctx[propertyName] === 'function') {
  717. if (propertyName !== 'getExtension') {
  718. wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
  719. } else {
  720. const wrapped = makeErrorWrapper(ctx, propertyName);
  721. wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
  722. }
  723. } else {
  724. makePropertyWrapper(wrapper, ctx, propertyName);
  725. }
  726. }
  727. // Override the getError function with one that returns our saved results.
  728. if (wrapper.getError) {
  729. wrapper.getError = function() {
  730. for (const err in glErrorShadow) {
  731. if (glErrorShadow.hasOwnProperty(err)) {
  732. if (glErrorShadow[err]) {
  733. glErrorShadow[err] = false;
  734. return err;
  735. }
  736. }
  737. }
  738. return ctx.NO_ERROR;
  739. };
  740. }
  741. if (wrapper.bindBuffer) {
  742. sharedState.wrappers['webgl'] = { wrapper: wrapper, orig: ctx };
  743. addEnumsForContext(ctx, ctx.bindBufferBase ? 'WebGL2' : 'WebGL');
  744. }
  745. return wrapper;
  746. }
  747. //------------
  748. function captureJSErrors() {
  749. // capture JavaScript Errors
  750. window.addEventListener('error', function(e) {
  751. const msg = e.message || e.error;
  752. const url = e.filename;
  753. let lineNo = e.lineno || 1;
  754. const colNo = e.colno || 1;
  755. const isUserScript = (url === window.location.href);
  756. if (isUserScript) {
  757. try {
  758. lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
  759. } catch (ex) {
  760. origConsole.error(ex);
  761. }
  762. }
  763. console.error("line:", lineNo, ":", msg); // eslint-disable-line
  764. origConsole.error(e.error);
  765. });
  766. }
  767. // adapted from http://stackoverflow.com/a/2401861/128511
  768. function getBrowser() {
  769. const userAgent = navigator.userAgent;
  770. let m = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
  771. if (/trident/i.test(m[1])) {
  772. m = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
  773. return {
  774. name: 'IE',
  775. version: m[1],
  776. };
  777. }
  778. if (m[1] === 'Chrome') {
  779. const temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/);
  780. if (temp) {
  781. return {
  782. name: temp[1].replace('OPR', 'Opera'),
  783. version: temp[2],
  784. };
  785. }
  786. }
  787. m = m[2] ? [m[1], m[2]] : [navigator.appName, navigator.appVersion, '-?'];
  788. const version = userAgent.match(/version\/(\d+)/i);
  789. if (version) {
  790. m.splice(1, 1, version[1]);
  791. }
  792. return {
  793. name: m[0],
  794. version: m[1],
  795. };
  796. }
  797. const isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
  798. function installWebGLLessonSetup() {
  799. HTMLCanvasElement.prototype.getContext = (function(oldFn) {
  800. return function() {
  801. const type = arguments[0];
  802. const isWebGL = isWebGLRE.test(type);
  803. if (isWebGL) {
  804. setupLesson(this);
  805. }
  806. const args = [].slice.apply(arguments);
  807. args[1] = Object.assign({
  808. powerPreference: 'low-power',
  809. }, args[1]);
  810. const ctx = oldFn.apply(this, args);
  811. if (!ctx && isWebGL) {
  812. showNeedWebGL(this);
  813. }
  814. return ctx;
  815. };
  816. }(HTMLCanvasElement.prototype.getContext));
  817. }
  818. function installWebGLDebugContextCreator() {
  819. // capture GL errors
  820. HTMLCanvasElement.prototype.getContext = (function(oldFn) {
  821. return function() {
  822. let ctx = oldFn.apply(this, arguments);
  823. // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
  824. // but that might fail if wrapped by debugging extension
  825. if (ctx && ctx.bindTexture) {
  826. ctx = makeDebugContext(ctx, {
  827. maxDrawCalls: 100,
  828. errorFunc: function(err, funcName, args) {
  829. const numArgs = args.length;
  830. const enumedArgs = [].map.call(args, function(arg, ndx) {
  831. let str = glFunctionArgToString(funcName, numArgs, ndx, arg);
  832. // shorten because of long arrays
  833. if (str.length > 200) {
  834. str = str.substring(0, 200) + '...';
  835. }
  836. return str;
  837. });
  838. const browser = getBrowser();
  839. let lineNdx;
  840. let matcher;
  841. if ((/chrome|opera/i).test(browser.name)) {
  842. lineNdx = 3;
  843. matcher = function(line) {
  844. const m = /at ([^(]+)*\(*(.*?):(\d+):(\d+)/.exec(line);
  845. if (m) {
  846. let userFnName = m[1];
  847. let url = m[2];
  848. const lineNo = parseInt(m[3]);
  849. const colNo = parseInt(m[4]);
  850. if (url === '') {
  851. url = userFnName;
  852. userFnName = '';
  853. }
  854. return {
  855. url: url,
  856. lineNo: lineNo,
  857. colNo: colNo,
  858. funcName: userFnName,
  859. };
  860. }
  861. return undefined;
  862. };
  863. } else if ((/firefox|safari/i).test(browser.name)) {
  864. lineNdx = 2;
  865. matcher = function(line) {
  866. const m = /@(.*?):(\d+):(\d+)/.exec(line);
  867. if (m) {
  868. const url = m[1];
  869. const lineNo = parseInt(m[2]);
  870. const colNo = parseInt(m[3]);
  871. return {
  872. url: url,
  873. lineNo: lineNo,
  874. colNo: colNo,
  875. };
  876. }
  877. return undefined;
  878. };
  879. }
  880. let lineInfo = '';
  881. if (matcher) {
  882. try {
  883. const error = new Error();
  884. const lines = error.stack.split('\n');
  885. // window.fooLines = lines;
  886. // lines.forEach(function(line, ndx) {
  887. // origConsole.log("#", ndx, line);
  888. // });
  889. const info = matcher(lines[lineNdx]);
  890. if (info) {
  891. let lineNo = info.lineNo;
  892. const colNo = info.colNo;
  893. const url = info.url;
  894. const isUserScript = (url === window.location.href);
  895. if (isUserScript) {
  896. lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
  897. }
  898. lineInfo = ' line:' + lineNo + ':' + colNo;
  899. }
  900. } catch (e) {
  901. origConsole.error(e);
  902. }
  903. }
  904. console.error( // eslint-disable-line
  905. 'WebGL error' + lineInfo, glEnumToString(err), 'in',
  906. funcName, '(', enumedArgs.join(', '), ')');
  907. },
  908. });
  909. }
  910. return ctx;
  911. };
  912. }(HTMLCanvasElement.prototype.getContext));
  913. }
  914. installWebGLLessonSetup();
  915. if (isInEditor()) {
  916. setupConsole();
  917. captureJSErrors();
  918. if (window.threejsLessonSettings === undefined || window.threejsLessonSettings.glDebug !== false) {
  919. installWebGLDebugContextCreator();
  920. }
  921. }
  922. return {
  923. setupLesson: setupLesson,
  924. showNeedWebGL: showNeedWebGL,
  925. makeDebugContext: makeDebugContext,
  926. glFunctionArgsToString: glFunctionArgsToString,
  927. };
  928. }));