lessons-worker-helper.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. /*
  2. * Copyright 2019, 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 */
  32. 'use strict';
  33. (function() {
  34. const lessonSettings = self.lessonSettings || {};
  35. function isInEditor() {
  36. return self.location.href.substring(0, 4) === 'blob';
  37. }
  38. function sendMessage(data) {
  39. self.postMessage({
  40. type: '__editor__',
  41. data,
  42. });
  43. }
  44. const origConsole = {};
  45. function setupConsole() {
  46. function wrapFunc(obj, logType) {
  47. const origFunc = obj[logType].bind(obj);
  48. origConsole[logType] = origFunc;
  49. return function(...args) {
  50. origFunc(...args);
  51. sendMessage({
  52. type: 'log',
  53. logType,
  54. msg: [...args].join(' '),
  55. });
  56. };
  57. }
  58. self.console.log = wrapFunc(self.console, 'log');
  59. self.console.warn = wrapFunc(self.console, 'warn');
  60. self.console.error = wrapFunc(self.console, 'error');
  61. }
  62. /**
  63. * Gets a WebGL context.
  64. * makes its backing store the size it is displayed.
  65. * @param {OffscreenCanvas} canvas a canvas element.
  66. * @memberOf module:webgl-utils
  67. */
  68. let setupLesson = function(canvas) {
  69. // only once
  70. setupLesson = function() {};
  71. if (canvas) {
  72. canvas.addEventListener('webglcontextlost', function(e) {
  73. // the default is to do nothing. Preventing the default
  74. // means allowing context to be restored
  75. e.preventDefault();
  76. sendMessage({
  77. type: 'lostContext',
  78. });
  79. });
  80. }
  81. };
  82. //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
  83. /*
  84. ** Copyright (c) 2012 The Khronos Group Inc.
  85. **
  86. ** Permission is hereby granted, free of charge, to any person obtaining a
  87. ** copy of this software and/or associated documentation files (the
  88. ** "Materials"), to deal in the Materials without restriction, including
  89. ** without limitation the rights to use, copy, modify, merge, publish,
  90. ** distribute, sublicense, and/or sell copies of the Materials, and to
  91. ** permit persons to whom the Materials are furnished to do so, subject to
  92. ** the following conditions:
  93. **
  94. ** The above copyright notice and this permission notice shall be included
  95. ** in all copies or substantial portions of the Materials.
  96. **
  97. ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  98. ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  99. ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  100. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  101. ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  102. ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  103. ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
  104. */
  105. /**
  106. * Types of contexts we have added to map
  107. */
  108. const mappedContextTypes = {};
  109. /**
  110. * Map of numbers to names.
  111. * @type {Object}
  112. */
  113. const glEnums = {};
  114. /**
  115. * Map of names to numbers.
  116. * @type {Object}
  117. */
  118. const enumStringToValue = {};
  119. /**
  120. * Initializes this module. Safe to call more than once.
  121. * @param {!WebGLRenderingContext} ctx A WebGL context. If
  122. * you have more than one context it doesn't matter which one
  123. * you pass in, it is only used to pull out constants.
  124. */
  125. function addEnumsForContext(ctx, type) {
  126. if (!mappedContextTypes[type]) {
  127. mappedContextTypes[type] = true;
  128. for (const propertyName in ctx) {
  129. if (typeof ctx[propertyName] === 'number') {
  130. glEnums[ctx[propertyName]] = propertyName;
  131. enumStringToValue[propertyName] = ctx[propertyName];
  132. }
  133. }
  134. }
  135. }
  136. function enumArrayToString(enums) {
  137. const enumStrings = [];
  138. if (enums.length) {
  139. for (let i = 0; i < enums.length; ++i) {
  140. enums.push(glEnumToString(enums[i])); // eslint-disable-line
  141. }
  142. return '[' + enumStrings.join(', ') + ']';
  143. }
  144. return enumStrings.toString();
  145. }
  146. function makeBitFieldToStringFunc(enums) {
  147. return function(value) {
  148. let orResult = 0;
  149. const orEnums = [];
  150. for (let i = 0; i < enums.length; ++i) {
  151. const enumValue = enumStringToValue[enums[i]];
  152. if ((value & enumValue) !== 0) {
  153. orResult |= enumValue;
  154. orEnums.push(glEnumToString(enumValue)); // eslint-disable-line
  155. }
  156. }
  157. if (orResult === value) {
  158. return orEnums.join(' | ');
  159. } else {
  160. return glEnumToString(value); // eslint-disable-line
  161. }
  162. };
  163. }
  164. const destBufferBitFieldToString = makeBitFieldToStringFunc([
  165. 'COLOR_BUFFER_BIT',
  166. 'DEPTH_BUFFER_BIT',
  167. 'STENCIL_BUFFER_BIT',
  168. ]);
  169. /**
  170. * Which arguments are enums based on the number of arguments to the function.
  171. * So
  172. * 'texImage2D': {
  173. * 9: { 0:true, 2:true, 6:true, 7:true },
  174. * 6: { 0:true, 2:true, 3:true, 4:true },
  175. * },
  176. *
  177. * means if there are 9 arguments then 6 and 7 are enums, if there are 6
  178. * arguments 3 and 4 are enums. Maybe a function as well in which case
  179. * value is passed to function and returns a string
  180. *
  181. * @type {!Object.<number, (!Object.<number, string>|function)}
  182. */
  183. const glValidEnumContexts = {
  184. // Generic setters and getters
  185. 'enable': {1: { 0:true }},
  186. 'disable': {1: { 0:true }},
  187. 'getParameter': {1: { 0:true }},
  188. // Rendering
  189. 'drawArrays': {3:{ 0:true }},
  190. 'drawElements': {4:{ 0:true, 2:true }},
  191. 'drawArraysInstanced': {4: { 0:true }},
  192. 'drawElementsInstanced': {5: {0:true, 2: true }},
  193. 'drawRangeElements': {6: {0:true, 4: true }},
  194. // Shaders
  195. 'createShader': {1: { 0:true }},
  196. 'getShaderParameter': {2: { 1:true }},
  197. 'getProgramParameter': {2: { 1:true }},
  198. 'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
  199. // Vertex attributes
  200. 'getVertexAttrib': {2: { 1:true }},
  201. 'vertexAttribPointer': {6: { 2:true }},
  202. 'vertexAttribIPointer': {5: { 2:true }}, // WebGL2
  203. // Textures
  204. 'bindTexture': {2: { 0:true }},
  205. 'activeTexture': {1: { 0:true }},
  206. 'getTexParameter': {2: { 0:true, 1:true }},
  207. 'texParameterf': {3: { 0:true, 1:true }},
  208. 'texParameteri': {3: { 0:true, 1:true, 2:true }},
  209. 'texImage2D': {
  210. 9: { 0:true, 2:true, 6:true, 7:true },
  211. 6: { 0:true, 2:true, 3:true, 4:true },
  212. 10: { 0:true, 2:true, 6:true, 7:true }, // WebGL2
  213. },
  214. 'texImage3D': {
  215. 10: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  216. 11: { 0:true, 2:true, 7:true, 8:true }, // WebGL2
  217. },
  218. 'texSubImage2D': {
  219. 9: { 0:true, 6:true, 7:true },
  220. 7: { 0:true, 4:true, 5:true },
  221. 10: { 0:true, 6:true, 7:true }, // WebGL2
  222. },
  223. 'texSubImage3D': {
  224. 11: { 0:true, 8:true, 9:true }, // WebGL2
  225. 12: { 0:true, 8:true, 9:true }, // WebGL2
  226. },
  227. 'texStorage2D': { 5: { 0:true, 2:true }}, // WebGL2
  228. 'texStorage3D': { 6: { 0:true, 2:true }}, // WebGL2
  229. 'copyTexImage2D': {8: { 0:true, 2:true }},
  230. 'copyTexSubImage2D': {8: { 0:true }},
  231. 'copyTexSubImage3D': {9: { 0:true }}, // WebGL2
  232. 'generateMipmap': {1: { 0:true }},
  233. 'compressedTexImage2D': {
  234. 7: { 0: true, 2:true },
  235. 8: { 0: true, 2:true }, // WebGL2
  236. },
  237. 'compressedTexSubImage2D': {
  238. 8: { 0: true, 6:true },
  239. 9: { 0: true, 6:true }, // WebGL2
  240. },
  241. 'compressedTexImage3D': {
  242. 8: { 0: true, 2: true, }, // WebGL2
  243. 9: { 0: true, 2: true, }, // WebGL2
  244. },
  245. 'compressedTexSubImage3D': {
  246. 9: { 0: true, 8: true, }, // WebGL2
  247. 10: { 0: true, 8: true, }, // WebGL2
  248. },
  249. // Buffer objects
  250. 'bindBuffer': {2: { 0:true }},
  251. 'bufferData': {
  252. 3: { 0:true, 2:true },
  253. 4: { 0:true, 2:true }, // WebGL2
  254. 5: { 0:true, 2:true }, // WebGL2
  255. },
  256. 'bufferSubData': {
  257. 3: { 0:true },
  258. 4: { 0:true }, // WebGL2
  259. 5: { 0:true }, // WebGL2
  260. },
  261. 'copyBufferSubData': {
  262. 5: { 0:true }, // WeBGL2
  263. },
  264. 'getBufferParameter': {2: { 0:true, 1:true }},
  265. 'getBufferSubData': {
  266. 3: { 0: true, }, // WebGL2
  267. 4: { 0: true, }, // WebGL2
  268. 5: { 0: true, }, // WebGL2
  269. },
  270. // Renderbuffers and framebuffers
  271. 'pixelStorei': {2: { 0:true, 1:true }},
  272. 'readPixels': {
  273. 7: { 4:true, 5:true },
  274. 8: { 4:true, 5:true }, // WebGL2
  275. },
  276. 'bindRenderbuffer': {2: { 0:true }},
  277. 'bindFramebuffer': {2: { 0:true }},
  278. 'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }}, // WebGL2
  279. 'checkFramebufferStatus': {1: { 0:true }},
  280. 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
  281. 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
  282. 'framebufferTextureLayer': {5: {0:true, 1:true }}, // WebGL2
  283. 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
  284. 'getInternalformatParameter': {3: {0:true, 1:true, 2:true }}, // WebGL2
  285. 'getRenderbufferParameter': {2: { 0:true, 1:true }},
  286. 'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }}, // WebGL2
  287. 'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }}, // WebGL2
  288. 'readBuffer': {1: {0: true}}, // WebGL2
  289. 'renderbufferStorage': {4: { 0:true, 1:true }},
  290. 'renderbufferStorageMultisample': {5: { 0: true, 2: true }}, // WebGL2
  291. // Frame buffer operations (clear, blend, depth test, stencil)
  292. 'clear': {1: { 0: destBufferBitFieldToString }},
  293. 'depthFunc': {1: { 0:true }},
  294. 'blendFunc': {2: { 0:true, 1:true }},
  295. 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  296. 'blendEquation': {1: { 0:true }},
  297. 'blendEquationSeparate': {2: { 0:true, 1:true }},
  298. 'stencilFunc': {3: { 0:true }},
  299. 'stencilFuncSeparate': {4: { 0:true, 1:true }},
  300. 'stencilMaskSeparate': {2: { 0:true }},
  301. 'stencilOp': {3: { 0:true, 1:true, 2:true }},
  302. 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
  303. // Culling
  304. 'cullFace': {1: { 0:true }},
  305. 'frontFace': {1: { 0:true }},
  306. // ANGLE_instanced_arrays extension
  307. 'drawArraysInstancedANGLE': {4: { 0:true }},
  308. 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
  309. // EXT_blend_minmax extension
  310. 'blendEquationEXT': {1: { 0:true }},
  311. // Multiple Render Targets
  312. 'drawBuffersWebGL': {1: {0: enumArrayToString, }}, // WEBGL_draw_bufers
  313. 'drawBuffers': {1: {0: enumArrayToString, }}, // WebGL2
  314. 'clearBufferfv': {
  315. 4: {0: true }, // WebGL2
  316. 5: {0: true }, // WebGL2
  317. },
  318. 'clearBufferiv': {
  319. 4: {0: true }, // WebGL2
  320. 5: {0: true }, // WebGL2
  321. },
  322. 'clearBufferuiv': {
  323. 4: {0: true }, // WebGL2
  324. 5: {0: true }, // WebGL2
  325. },
  326. 'clearBufferfi': { 4: {0: true}}, // WebGL2
  327. // QueryObjects
  328. 'beginQuery': { 2: { 0: true }}, // WebGL2
  329. 'endQuery': { 1: { 0: true }}, // WebGL2
  330. 'getQuery': { 2: { 0: true, 1: true }}, // WebGL2
  331. 'getQueryParameter': { 2: { 1: true }}, // WebGL2
  332. // Sampler Objects
  333. 'samplerParameteri': { 3: { 1: true }}, // WebGL2
  334. 'samplerParameterf': { 3: { 1: true }}, // WebGL2
  335. 'getSamplerParameter': { 2: { 1: true }}, // WebGL2
  336. // Sync objects
  337. 'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }}, // WebGL2
  338. 'fenceSync': { 2: { 0: true }}, // WebGL2
  339. 'getSyncParameter': { 2: { 1: true }}, // WebGL2
  340. // Transform Feedback
  341. 'bindTransformFeedback': { 2: { 0: true }}, // WebGL2
  342. 'beginTransformFeedback': { 1: { 0: true }}, // WebGL2
  343. // Uniform Buffer Objects and Transform Feedback Buffers
  344. 'bindBufferBase': { 3: { 0: true }}, // WebGL2
  345. 'bindBufferRange': { 5: { 0: true }}, // WebGL2
  346. 'getIndexedParameter': { 2: { 0: true }}, // WebGL2
  347. 'getActiveUniforms': { 3: { 2: true }}, // WebGL2
  348. 'getActiveUniformBlockParameter': { 3: { 2: true }}, // WebGL2
  349. };
  350. /**
  351. * Gets an string version of an WebGL enum.
  352. *
  353. * Example:
  354. * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
  355. *
  356. * @param {number} value Value to return an enum for
  357. * @return {string} The string version of the enum.
  358. */
  359. function glEnumToString(value) {
  360. const name = glEnums[value];
  361. return (name !== undefined) ? ('gl.' + name) :
  362. ('/*UNKNOWN WebGL ENUM*/ 0x' + value.toString(16) + '');
  363. }
  364. /**
  365. * Returns the string version of a WebGL argument.
  366. * Attempts to convert enum arguments to strings.
  367. * @param {string} functionName the name of the WebGL function.
  368. * @param {number} numArgs the number of arguments passed to the function.
  369. * @param {number} argumentIndx the index of the argument.
  370. * @param {*} value The value of the argument.
  371. * @return {string} The value as a string.
  372. */
  373. function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
  374. const funcInfos = glValidEnumContexts[functionName];
  375. if (funcInfos !== undefined) {
  376. const funcInfo = funcInfos[numArgs];
  377. if (funcInfo !== undefined) {
  378. const argType = funcInfo[argumentIndex];
  379. if (argType) {
  380. if (typeof argType === 'function') {
  381. return argType(value);
  382. } else {
  383. return glEnumToString(value);
  384. }
  385. }
  386. }
  387. }
  388. if (value === null) {
  389. return 'null';
  390. } else if (value === undefined) {
  391. return 'undefined';
  392. } else {
  393. return value.toString();
  394. }
  395. }
  396. /**
  397. * Converts the arguments of a WebGL function to a string.
  398. * Attempts to convert enum arguments to strings.
  399. *
  400. * @param {string} functionName the name of the WebGL function.
  401. * @param {number} args The arguments.
  402. * @return {string} The arguments as a string.
  403. */
  404. function glFunctionArgsToString(functionName, args) {
  405. // apparently we can't do args.join(",");
  406. const argStrs = [];
  407. const numArgs = args.length;
  408. for (let ii = 0; ii < numArgs; ++ii) {
  409. argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
  410. }
  411. return argStrs.join(', ');
  412. }
  413. function makePropertyWrapper(wrapper, original, propertyName) {
  414. wrapper.__defineGetter__(propertyName, function() { // eslint-disable-line
  415. return original[propertyName];
  416. });
  417. // TODO(gmane): this needs to handle properties that take more than
  418. // one value?
  419. wrapper.__defineSetter__(propertyName, function(value) { // eslint-disable-line
  420. original[propertyName] = value;
  421. });
  422. }
  423. /**
  424. * Given a WebGL context returns a wrapped context that calls
  425. * gl.getError after every command and calls a function if the
  426. * result is not gl.NO_ERROR.
  427. *
  428. * @param {!WebGLRenderingContext} ctx The webgl context to
  429. * wrap.
  430. * @param {!function(err, funcName, args): void} opt_onErrorFunc
  431. * The function to call when gl.getError returns an
  432. * error. If not specified the default function calls
  433. * console.log with a message.
  434. * @param {!function(funcName, args): void} opt_onFunc The
  435. * function to call when each webgl function is called.
  436. * You can use this to log all calls for example.
  437. * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
  438. * to call getError on if different than ctx.
  439. */
  440. function makeDebugContext(ctx, options) {
  441. options = options || {};
  442. const errCtx = options.errCtx || ctx;
  443. const onFunc = options.funcFunc;
  444. const sharedState = options.sharedState || {
  445. numDrawCallsRemaining: options.maxDrawCalls || -1,
  446. wrappers: {},
  447. };
  448. options.sharedState = sharedState;
  449. const errorFunc = options.errorFunc || function(err, functionName, args) {
  450. console.error(`WebGL error ${glEnumToString(err)} in ${functionName}(${glFunctionArgsToString(functionName, args)})`); /* eslint-disable-line no-console */
  451. };
  452. // Holds booleans for each GL error so after we get the error ourselves
  453. // we can still return it to the client app.
  454. const glErrorShadow = { };
  455. const wrapper = {};
  456. function removeChecks() {
  457. Object.keys(sharedState.wrappers).forEach(function(name) {
  458. const pair = sharedState.wrappers[name];
  459. const wrapper = pair.wrapper;
  460. const orig = pair.orig;
  461. for (const propertyName in wrapper) {
  462. if (typeof wrapper[propertyName] === 'function') {
  463. wrapper[propertyName] = orig[propertyName].bind(orig);
  464. }
  465. }
  466. });
  467. }
  468. function checkMaxDrawCalls() {
  469. if (sharedState.numDrawCallsRemaining === 0) {
  470. removeChecks();
  471. }
  472. --sharedState.numDrawCallsRemaining;
  473. }
  474. function noop() {
  475. }
  476. // Makes a function that calls a WebGL function and then calls getError.
  477. function makeErrorWrapper(ctx, functionName) {
  478. const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
  479. return function() {
  480. if (onFunc) {
  481. onFunc(functionName, arguments);
  482. }
  483. const result = ctx[functionName].apply(ctx, arguments);
  484. const err = errCtx.getError();
  485. if (err !== 0) {
  486. glErrorShadow[err] = true;
  487. errorFunc(err, functionName, arguments);
  488. }
  489. check();
  490. return result;
  491. };
  492. }
  493. function makeGetExtensionWrapper(ctx, wrapped) {
  494. return function() {
  495. const extensionName = arguments[0];
  496. let ext = sharedState.wrappers[extensionName];
  497. if (!ext) {
  498. ext = wrapped.apply(ctx, arguments);
  499. if (ext) {
  500. const origExt = ext;
  501. ext = makeDebugContext(ext, options);
  502. sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
  503. addEnumsForContext(origExt, extensionName);
  504. }
  505. }
  506. return ext;
  507. };
  508. }
  509. // Make a an object that has a copy of every property of the WebGL context
  510. // but wraps all functions.
  511. for (const propertyName in ctx) {
  512. if (typeof ctx[propertyName] === 'function') {
  513. if (propertyName !== 'getExtension') {
  514. wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
  515. } else {
  516. const wrapped = makeErrorWrapper(ctx, propertyName);
  517. wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
  518. }
  519. } else {
  520. makePropertyWrapper(wrapper, ctx, propertyName);
  521. }
  522. }
  523. // Override the getError function with one that returns our saved results.
  524. if (wrapper.getError) {
  525. wrapper.getError = function() {
  526. for (const err in glErrorShadow) {
  527. if (glErrorShadow.hasOwnProperty(err)) {
  528. if (glErrorShadow[err]) {
  529. glErrorShadow[err] = false;
  530. return err;
  531. }
  532. }
  533. }
  534. return ctx.NO_ERROR;
  535. };
  536. }
  537. if (wrapper.bindBuffer) {
  538. sharedState.wrappers['webgl'] = { wrapper: wrapper, orig: ctx };
  539. addEnumsForContext(ctx, ctx.bindBufferBase ? 'WebGL2' : 'WebGL');
  540. }
  541. return wrapper;
  542. }
  543. //------------
  544. function captureJSErrors() {
  545. // capture JavaScript Errors
  546. self.addEventListener('error', function(e) {
  547. const msg = e.message || e.error;
  548. const url = e.filename;
  549. const lineNo = e.lineno || 1;
  550. const colNo = e.colno || 1;
  551. sendMessage({
  552. type: 'jsError',
  553. lineNo,
  554. colNo,
  555. url,
  556. msg,
  557. });
  558. });
  559. }
  560. const isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
  561. function installWebGLLessonSetup() {
  562. OffscreenCanvas.prototype.getContext = (function(oldFn) {
  563. return function() {
  564. const type = arguments[0];
  565. const isWebGL = isWebGLRE.test(type);
  566. if (isWebGL) {
  567. setupLesson(this);
  568. }
  569. const args = [].slice.apply(arguments);
  570. args[1] = Object.assign({
  571. powerPreference: 'low-power',
  572. }, args[1]);
  573. return oldFn.apply(this, args);
  574. };
  575. }(OffscreenCanvas.prototype.getContext));
  576. }
  577. function installWebGLDebugContextCreator() {
  578. // capture GL errors
  579. OffscreenCanvas.prototype.getContext = (function(oldFn) {
  580. return function() {
  581. let ctx = oldFn.apply(this, arguments);
  582. // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
  583. // but that might fail if wrapped by debugging extension
  584. if (ctx && ctx.bindTexture) {
  585. ctx = makeDebugContext(ctx, {
  586. maxDrawCalls: 100,
  587. errorFunc: function(err, funcName, args) {
  588. const numArgs = args.length;
  589. const enumedArgs = [].map.call(args, function(arg, ndx) {
  590. let str = glFunctionArgToString(funcName, numArgs, ndx, arg);
  591. // shorten because of long arrays
  592. if (str.length > 200) {
  593. str = str.substring(0, 200) + '...';
  594. }
  595. return str;
  596. });
  597. {
  598. const error = new Error();
  599. sendMessage({
  600. type: 'jsErrorWithStack',
  601. stack: error.stack,
  602. msg: `${glEnumToString(err)} in ${funcName}(${enumedArgs.join(', ')})`,
  603. });
  604. }
  605. },
  606. });
  607. }
  608. return ctx;
  609. };
  610. }(OffscreenCanvas.prototype.getContext));
  611. }
  612. installWebGLLessonSetup();
  613. if (isInEditor()) {
  614. setupConsole();
  615. captureJSErrors();
  616. if (lessonSettings.glDebug !== false) {
  617. installWebGLDebugContextCreator();
  618. }
  619. }
  620. }());