threejs-lessons-helper.js 33 KB

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