start.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. // Includes snippets from https://surma.dev/things/c-to-webassembly/ and https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API -->
  2. let memory = null;
  3. let heapu8 = null;
  4. let heapu16 = null;
  5. let heapu32 = null;
  6. let heapi32 = null;
  7. let heapf32 = null;
  8. let mod = null;
  9. let instance = null;
  10. let audio_thread_started = false;
  11. function create_thread(func) {
  12. console.log('Creating thread');
  13. const thread_starter = new Worker('thread_starter.js');
  14. const arr = new Uint8Array(memory.buffer, func, 256);
  15. let str = '';
  16. for (let i = 0; arr[i] != 0; ++i) {
  17. str += String.fromCharCode(arr[i]);
  18. }
  19. thread_starter.postMessage({ mod, memory, func: str });
  20. }
  21. async function start_audio_thread() {
  22. const audioContext = new AudioContext();
  23. await audioContext.audioWorklet.addModule('audio-thread.js');
  24. const audioThreadNode = new AudioWorkletNode(audioContext, 'audio-thread', { processorOptions: { mod, memory }});
  25. audioThreadNode.port.onmessage = (message) => {
  26. console.log(message.data);
  27. };
  28. audioThreadNode.connect(audioContext.destination);
  29. }
  30. function read_string(ptr) {
  31. let str = '';
  32. for (let i = 0; heapu8[ptr + i] != 0; ++i) {
  33. str += String.fromCharCode(heapu8[ptr + i]);
  34. }
  35. return str;
  36. }
  37. function write_string(ptr, str) {
  38. for (let i = 0; i < str.length; ++i) {
  39. heapu8[ptr + i] = str.charCodeAt(i);
  40. }
  41. heapu8[ptr + str.length] = 0;
  42. }
  43. async function init() {
  44. let wasm_bytes = null;
  45. await fetch("./ShaderTest.wasm").then(res => res.arrayBuffer()).then(buffer => wasm_bytes = new Uint8Array(buffer));
  46. // Read memory size from wasm file
  47. let memory_size = 0;
  48. let i = 8;
  49. while (i < wasm_bytes.length) {
  50. function read_leb() {
  51. let result = 0;
  52. let shift = 0;
  53. while (true) {
  54. let byte = wasm_bytes[i++];
  55. result |= (byte & 0x7f) << shift;
  56. if ((byte & 0x80) == 0) return result;
  57. shift += 7;
  58. }
  59. }
  60. let type = read_leb()
  61. let length = read_leb()
  62. if (type == 6) {
  63. read_leb(); // count
  64. i++; // gtype
  65. i++; // mutable
  66. read_leb(); // opcode
  67. memory_size = read_leb() / 65536 + 1;
  68. break;
  69. }
  70. i += length;
  71. }
  72. memory = new WebAssembly.Memory({ initial: memory_size, maximum: memory_size, shared: true });
  73. heapu8 = new Uint8Array(memory.buffer);
  74. heapu16 = new Uint16Array(memory.buffer);
  75. heapu32 = new Uint32Array(memory.buffer);
  76. heapi32 = new Int32Array(memory.buffer);
  77. heapf32 = new Float32Array(memory.buffer);
  78. const kanvas = document.getElementById('kanvas');
  79. const gl = kanvas.getContext('webgl2', { antialias: false, alpha: false });
  80. // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
  81. gl.getExtension("EXT_color_buffer_float");
  82. gl.getExtension("OES_texture_float_linear");
  83. gl.getExtension("OES_texture_half_float_linear");
  84. gl.getExtension("EXT_texture_filter_anisotropic");
  85. let file_buffer = null;
  86. let file_buffer_pos = 0;
  87. let gl_programs = [null];
  88. let gl_shaders = [null];
  89. let gl_buffers = [null];
  90. let gl_framebuffers = [null];
  91. let gl_renderbuffers = [null];
  92. let gl_textures = [null];
  93. let gl_locations = [null];
  94. const result = await WebAssembly.instantiate(
  95. wasm_bytes, {
  96. env: { memory },
  97. imports: {
  98. create_thread,
  99. glViewport: function(x, y, width, height) {
  100. gl.viewport(x, y, width, height);
  101. },
  102. glScissor: function(x, y, width, height) {
  103. gl.scissor(x, y, width, height);
  104. },
  105. glGetIntegerv: function(pname, data) {
  106. if (pname == 2) { // GL_MAJOR_VERSION
  107. heapu32[data / 4] = 3;
  108. }
  109. else {
  110. heapu32[data / 4] = gl.getParameter(pname);
  111. }
  112. },
  113. glGetFloatv: function(pname, data) {
  114. heapf32[data / 4] = gl.getParameter(pname);
  115. },
  116. glGetString: function(name) {
  117. // return gl.getParameter(name);
  118. },
  119. glDrawElements: function(mode, count, type, offset) {
  120. gl.drawElements(mode, count, type, offset);
  121. },
  122. glDrawElementsInstanced: function(mode, count, type, indices, instancecount) {
  123. gl.drawElementsInstanced(mode, count, type, indices, instancecount);
  124. },
  125. glVertexAttribDivisor: function(index, divisor) {
  126. gl.vertexAttribDivisor(index, divisor);
  127. },
  128. glBindFramebuffer: function(target, framebuffer) {
  129. gl.bindFramebuffer(target, gl_framebuffers[framebuffer]);
  130. },
  131. glFramebufferTexture2D: function(target, attachment, textarget, texture, level) {
  132. gl.framebufferTexture2D(target, attachment, textarget, gl_textures[texture], level);
  133. if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
  134. console.log("Incomplete framebuffer");
  135. }
  136. },
  137. glGenFramebuffers: function(n, framebuffers) {
  138. for (let i = 0; i < n; ++i) {
  139. gl_framebuffers.push(gl.createFramebuffer());
  140. heapu32[framebuffers / 4 + i] = gl_framebuffers.length - 1;
  141. }
  142. },
  143. glGenRenderbuffers: function(n, renderbuffers) {
  144. for (let i = 0; i < n; ++i) {
  145. gl_renderbuffers.push(gl.createRenderbuffer());
  146. heapu32[renderbuffers / 4 + i] = gl_renderbuffers.length - 1;
  147. }
  148. },
  149. glBindRenderbuffer: function(target, renderbuffer) {
  150. gl.bindRenderbuffer(target, gl_renderbuffers[renderbuffer]);
  151. },
  152. glRenderbufferStorage: function(target, internalformat, width, height) {
  153. gl.renderbufferStorage(target, internalformat, width, height)
  154. },
  155. glFramebufferRenderbuffer: function(target, attachment, renderbuffertarget, renderbuffer) {
  156. gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, gl_renderbuffers[renderbuffer]);
  157. },
  158. glReadPixels: function(x, y, width, height, format, type, data) {
  159. let pixels = type == gl.FLOAT ? heapf32.subarray(data / 4) : heapu8.subarray(data);
  160. gl.readPixels(x, y, width, height, format, type, pixels);
  161. },
  162. glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) {
  163. gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, heapu8.subarray(pixels));
  164. },
  165. glEnable: function(cap) {
  166. gl.enable(cap);
  167. },
  168. glDisable: function(cap) {
  169. gl.disable(cap);
  170. },
  171. glColorMask: function(red, green, blue, alpha) {
  172. gl.colorMask(red, green, blue, alpha);
  173. },
  174. glClearColor: function(red, green, blue, alpha) {
  175. gl.clearColor(red, green, blue, alpha);
  176. },
  177. glDepthMask: function(flag) {
  178. gl.depthMask(flag);
  179. },
  180. glClearDepthf: function(depth) {
  181. gl.clearDepth(depth);
  182. },
  183. glStencilMask: function(mask) {
  184. gl.stencilMask(mask);
  185. },
  186. glClearStencil: function(s) {
  187. gl.clearStencil(s);
  188. },
  189. glClear: function(mask) {
  190. gl.clear(mask);
  191. },
  192. glBindBuffer: function(target, buffer) {
  193. gl.bindBuffer(target, gl_buffers[buffer]);
  194. },
  195. glUseProgram: function(program) {
  196. gl.useProgram(gl_programs[program]);
  197. },
  198. glStencilMaskSeparate: function(face, mask) {
  199. gl.stencilMaskSeparate(face, mask);
  200. },
  201. glStencilOpSeparate: function(face, fail, zfail, zpass) {
  202. gl.stencilOpSeparate(face, fail, zfail, zpass);
  203. },
  204. glStencilFuncSeparate: function(face, func, ref, mask) {
  205. gl.stencilFuncSeparate(face, func, ref, mask);
  206. },
  207. glDepthFunc: function(func) {
  208. gl.depthFunc(func);
  209. },
  210. glCullFace: function(mode) {
  211. gl.cullFace(mode);
  212. },
  213. glBlendFuncSeparate: function(src_rgb, dst_rgb, src_alpha, dst_alpha) {
  214. gl.blendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha);
  215. },
  216. glBlendEquationSeparate: function(mode_rgb, mode_alpha) {
  217. gl.blendEquationSeparate(mode_rgb, mode_alpha);
  218. },
  219. glGenBuffers: function(n, buffers) {
  220. for (let i = 0; i < n; ++i) {
  221. gl_buffers.push(gl.createBuffer());
  222. heapu32[buffers / 4 + i] = gl_buffers.length - 1;
  223. }
  224. },
  225. glBufferData: function(target, size, data, usage) {
  226. gl.bufferData(target, heapu8.subarray(data, data + Number(size)), usage);
  227. },
  228. glCreateProgram: function() {
  229. gl_programs.push(gl.createProgram());
  230. return gl_programs.length - 1;
  231. },
  232. glAttachShader: function(program, shader) {
  233. gl.attachShader(gl_programs[program], gl_shaders[shader]);
  234. },
  235. glBindAttribLocation: function(program, index, name) {
  236. gl.bindAttribLocation(gl_programs[program], index, read_string(name));
  237. },
  238. glLinkProgram: function(program) {
  239. gl.linkProgram(gl_programs[program]);
  240. },
  241. glGetProgramiv: function(program, pname, params) {
  242. heapu32[params / 4] = gl.getProgramParameter(gl_programs[program], pname);
  243. },
  244. glGetProgramInfoLog: function(program) {
  245. console.log(gl.getProgramInfoLog(gl_programs[program]));
  246. },
  247. glCreateShader: function(type) {
  248. gl_shaders.push(gl.createShader(type));
  249. return gl_shaders.length - 1;
  250. },
  251. glShaderSource: function(shader, count, source, length) {
  252. gl.shaderSource(gl_shaders[shader], read_string(heapu32[source / 4]));
  253. },
  254. glCompileShader: function(shader) {
  255. gl.compileShader(gl_shaders[shader]);
  256. },
  257. glGetShaderiv: function(shader, pname, params) {
  258. heapu32[params / 4] = gl.getShaderParameter(gl_shaders[shader], pname);
  259. },
  260. glGetShaderInfoLog: function(shader) {
  261. console.log(gl.getShaderInfoLog(gl_shaders[shader]));
  262. },
  263. glBufferSubData: function(target, offset, size, data) {
  264. gl.bufferSubData(target, Number(offset), heapu8.subarray(data, data + Number(size)), 0);
  265. },
  266. glEnableVertexAttribArray: function(index) {
  267. gl.enableVertexAttribArray(index);
  268. },
  269. glVertexAttribPointer: function(index, size, type, normalized, stride, offset) {
  270. gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
  271. },
  272. glDisableVertexAttribArray: function(index) {
  273. gl.disableVertexAttribArray(index);
  274. },
  275. glGetUniformLocation: function(program, name) {
  276. gl_locations.push(gl.getUniformLocation(gl_programs[program], read_string(name)));
  277. return gl_locations.length - 1;
  278. },
  279. glUniform1i: function(location, v0) {
  280. gl.uniform1i(gl_locations[location], v0);
  281. },
  282. glUniform2i: function(location, v0, v1) {
  283. gl.uniform2i(gl_locations[location], v0, v1);
  284. },
  285. glUniform3i: function(location, v0, v1, v2) {
  286. gl.uniform3i(gl_locations[location], v0, v1, v2);
  287. },
  288. glUniform4i: function(location, v0, v1, v2, v3) {
  289. gl.uniform4i(gl_locations[location], v0, v1, v2, v3);
  290. },
  291. glUniform1iv: function(location, count, value) {
  292. gl.uniform1iv(gl_locations[location], count, heapi32.subarray(value / 4));
  293. },
  294. glUniform2iv: function(location, count, value) {
  295. gl.uniform2iv(gl_locations[location], count, heapi32.subarray(value / 4));
  296. },
  297. glUniform3iv: function(location, count, value) {
  298. gl.uniform3iv(gl_locations[location], count, heapi32.subarray(value / 4));
  299. },
  300. glUniform4iv: function(location, count, value) {
  301. gl.uniform4iv(gl_locations[location], count, heapi32.subarray(value / 4));
  302. },
  303. glUniform1f: function(location, v0) {
  304. gl.uniform1f(gl_locations[location], v0);
  305. },
  306. glUniform2f: function(location, v0, v1) {
  307. gl.uniform2f(gl_locations[location], v0, v1);
  308. },
  309. glUniform3f: function(location, v0, v1, v2) {
  310. gl.uniform3f(gl_locations[location], v0, v1, v2);
  311. },
  312. glUniform4f: function(location, v0, v1, v2, v3) {
  313. gl.uniform4f(gl_locations[location], v0, v1, v2, v3);
  314. },
  315. glUniform1fv: function(location, count, value) {
  316. var f32 = new Float32Array(memory.buffer, value, count);
  317. gl.uniform1fv(gl_locations[location], f32);
  318. },
  319. glUniform2fv: function(location, count, value) {
  320. var f32 = new Float32Array(memory.buffer, value, count * 2);
  321. gl.uniform2fv(gl_locations[location], f32);
  322. },
  323. glUniform3fv: function(location, count, value) {
  324. var f32 = new Float32Array(memory.buffer, value, count * 3);
  325. gl.uniform3fv(gl_locations[location], f32);
  326. },
  327. glUniform4fv: function(location, count, value) {
  328. var f32 = new Float32Array(memory.buffer, value, count * 4);
  329. gl.uniform4fv(gl_locations[location], f32);
  330. },
  331. glUniformMatrix3fv: function(location, count, transpose, value) {
  332. var f32 = new Float32Array(memory.buffer, value, 3 * 3);
  333. gl.uniformMatrix3fv(gl_locations[location], transpose, f32);
  334. },
  335. glUniformMatrix4fv: function(location, count, transpose, value) {
  336. var f32 = new Float32Array(memory.buffer, value, 4 * 4);
  337. gl.uniformMatrix4fv(gl_locations[location], transpose, f32);
  338. },
  339. glTexParameterf: function(target, pname, param) {
  340. gl.texParameterf(target, pname, param);
  341. },
  342. glActiveTexture: function(texture) {
  343. gl.activeTexture(texture);
  344. },
  345. glBindTexture: function(target, texture) {
  346. gl.bindTexture(target, gl_textures[texture]);
  347. },
  348. glTexParameteri: function(target, pname, param) {
  349. gl.texParameteri(target, pname, param);
  350. },
  351. glGetActiveUniform: function(program, index, bufSize, length, size, type, name) {
  352. let u = gl.getActiveUniform(gl_programs[program], index);
  353. heapu32[size / 4] = u.size;
  354. heapu32[type / 4] = u.type;
  355. write_string(name, u.name);
  356. },
  357. glGenTextures: function(n, textures) {
  358. for (let i = 0; i < n; ++i) {
  359. gl_textures.push(gl.createTexture());
  360. heapu32[textures / 4 + i] = gl_textures.length - 1;
  361. }
  362. },
  363. glTexImage2D: function(target, level, internalformat, width, height, border, format, type, data) {
  364. let pixels = type == gl.FLOAT ? heapf32.subarray(data / 4) :
  365. type == gl.UNSIGNED_INT ? heapu32.subarray(data / 4) :
  366. type == gl.UNSIGNED_SHORT ? heapu16.subarray(data / 2) :
  367. type == gl.HALF_FLOAT ? heapu16.subarray(data / 2) : heapu8.subarray(data);
  368. gl.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
  369. },
  370. glPixelStorei: function(pname, param) {
  371. gl.pixelStorei(pname, param);
  372. },
  373. glCompressedTexImage2D: function(target, level, internalformat, width, height, border, imageSize, data) {
  374. gl.compressedTexImage2D(target, level, internalformat, width, height, border, imageSize, heapu8.subarray(data));
  375. },
  376. glDrawBuffers: function(n, bufs) {
  377. let ar = [];
  378. for (let i = 0; i < n; ++i) {
  379. ar.push(gl.COLOR_ATTACHMENT0 + i);
  380. }
  381. gl.drawBuffers(ar);
  382. },
  383. glGenerateMipmap: function(target) {
  384. gl.generateMipmap(target);
  385. },
  386. glFlush: function() {
  387. gl.flush();
  388. },
  389. glDeleteBuffers: function(n, buffers) {
  390. for (let i = 0; i < n; ++i) {
  391. gl.deleteBuffer(gl_buffers[heapu32[buffers / 4 + i]]);
  392. }
  393. },
  394. glDeleteTextures: function(n, textures) {
  395. for (let i = 0; i < n; ++i) {
  396. gl.deleteTexture(gl_textures[heapu32[textures / 4 + i]]);
  397. }
  398. },
  399. glDeleteFramebuffers: function(n, framebuffers) {
  400. for (let i = 0; i < n; ++i) {
  401. gl.deleteFramebuffer(gl_framebuffers[heapu32[framebuffers / 4 + i]]);
  402. }
  403. },
  404. glDeleteProgram: function(program) {
  405. gl.deleteProgram(gl_programs[program]);
  406. },
  407. glDeleteShader: function(shader) {
  408. gl.deleteShader(gl_shaders[shader]);
  409. },
  410. js_fprintf: function(format) {
  411. console.log(read_string(format));
  412. },
  413. js_fopen: function(filename) {
  414. const req = new XMLHttpRequest();
  415. req.open("GET", read_string(filename), false);
  416. req.overrideMimeType("text/plain; charset=x-user-defined");
  417. req.send();
  418. let str = req.response;
  419. file_buffer_pos = 0;
  420. file_buffer = new ArrayBuffer(str.length);
  421. let buf_view = new Uint8Array(file_buffer);
  422. for (let i = 0; i < str.length; ++i) {
  423. buf_view[i] = str.charCodeAt(i);
  424. }
  425. return 1;
  426. },
  427. js_ftell: function(stream) {
  428. return file_buffer_pos;
  429. },
  430. js_fseek: function(stream, offset, origin) {
  431. file_buffer_pos = offset;
  432. if (origin == 1) file_buffer_pos += file_buffer.byteLength; // SEEK_END
  433. return 0;
  434. },
  435. js_fread: function(ptr, size, count, stream) {
  436. let buf_view = new Uint8Array(file_buffer);
  437. for (let i = 0; i < count; ++i) {
  438. heapu8[ptr + i] = buf_view[file_buffer_pos++];
  439. }
  440. return count;
  441. },
  442. js_time: function() {
  443. return window.performance.now();
  444. },
  445. js_pow: function(x) {
  446. return Math.pow(x);
  447. },
  448. js_floor: function(x) {
  449. return Math.floor(x);
  450. },
  451. js_sin: function(x) {
  452. return Math.sin(x);
  453. },
  454. js_cos: function(x) {
  455. return Math.cos(x);
  456. },
  457. js_tan: function(x) {
  458. return Math.tan(x);
  459. },
  460. js_log: function(base, exponent) {
  461. return Math.log(base, exponent);
  462. },
  463. js_exp: function(x) {
  464. return Math.exp(x);
  465. },
  466. js_sqrt: function(x) {
  467. return Math.sqrt(x);
  468. },
  469. js_eval: function(str) {
  470. (1, eval)(read_string(str));
  471. }
  472. }
  473. }
  474. );
  475. mod = result.module;
  476. instance = result.instance;
  477. instance.exports._start();
  478. function update() {
  479. instance.exports._update();
  480. window.requestAnimationFrame(update);
  481. }
  482. window.requestAnimationFrame(update);
  483. kanvas.addEventListener('click', (event) => {
  484. if (!audio_thread_started) {
  485. start_audio_thread();
  486. audio_thread_started = true;
  487. }
  488. });
  489. kanvas.addEventListener('contextmenu', (event) => {
  490. event.preventDefault();
  491. });
  492. kanvas.addEventListener('mousedown', (event) => {
  493. instance.exports._mousedown(event.button, event.clientX, event.clientY);
  494. });
  495. kanvas.addEventListener('mouseup', (event) => {
  496. instance.exports._mouseup(event.button, event.clientX, event.clientY);
  497. });
  498. kanvas.addEventListener('mousemove', (event) => {
  499. instance.exports._mousemove(event.clientX, event.clientY);
  500. });
  501. kanvas.addEventListener('wheel', (event) => {
  502. instance.exports._wheel(event.deltaY);
  503. });
  504. kanvas.addEventListener('keydown', (event) => {
  505. if (event.repeat) {
  506. event.preventDefault();
  507. return;
  508. }
  509. instance.exports._keydown(event.keyCode);
  510. });
  511. kanvas.addEventListener('keyup', (event) => {
  512. if (event.repeat) {
  513. event.preventDefault();
  514. return;
  515. }
  516. instance.exports._keyup(event.keyCode);
  517. });
  518. }
  519. init();