├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── demo-hello-triangle.png ├── examples ├── README.md ├── common.js ├── extern │ ├── README.md │ ├── deno.png │ ├── gmath.bundle.js │ ├── gpu_err.bundle.js │ └── pngs.bundle.js ├── hello_camera.js ├── hello_texture.js ├── hello_triangle.js └── shaders │ ├── hello_camera.wgsl │ ├── hello_texture.wgsl │ └── hello_triangle.wgsl └── src ├── core.js ├── event_loop.rs ├── extra.rs ├── main.rs ├── runtime.rs ├── surface.rs ├── util.rs └── window.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .test/ 3 | .vscode/ 4 | deno/ 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deno_desktop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | deno_runtime = { git = "https://github.com/DjDeveloperr/deno.git", rev = "961851925567152fe98f08ca6b842f8f7c05b679" } 9 | # deno_runtime = { path = "./deno/runtime" } 10 | winit-main = { version = "0.1.3", features = ["proc"] } 11 | tokio = { version = "1", features = ["full"] } 12 | lazy_static = "1.4.0" 13 | serde = { version = "1", features = ["derive"] } 14 | # wgpu-core = { version = "0.11.0", features = ["raw-window-handle"] } 15 | # wgpu-types = { version = "0.11.0", features = ["serde"] } 16 | raw-window-handle = "0.3" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 DjDeveloperr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno_desktop 2 | 3 | Windowing support for Deno WebGPU. In very early stages at the moment. 4 | 5 | ![demo](./assets/demo-hello-triangle.png) 6 | 7 | ## Usage 8 | 9 | ```js 10 | const win = Deno.createWindow({ 11 | title: "Deno Desktop", 12 | width: 800, 13 | height: 600, 14 | resizable: false, 15 | // ... 16 | }); 17 | 18 | const surface = win.createSurface(device); 19 | ``` 20 | 21 | The surface object maps to the WebGPU `GPUCanvasContext` object. So you first `configure` it 22 | then render to the texture returned for each frame by `getCurrentTexture`. Do note that unlike 23 | web implementations, you need to call `surface.present()` after rendering it. 24 | 25 | You also need to go through the event loop in order to render. `Deno.eventLoop()` returns Async 26 | Iterator yielding events. Mostly you'd do something like this: 27 | 28 | ```ts 29 | for await (const event of Deno.eventLoop()) { 30 | if (event.type === "windowEvent" && event.windowID === win.id) { 31 | if (event.event.type === "closeRequested") { 32 | // Do any cleanup stuff before this. 33 | // There is an unverified bug that causes panic if we do not 34 | // exit manually. 35 | Deno.exit(0); 36 | } 37 | } else if (event.type === "redrawRequested" && event.windowID === win.id) { 38 | // Render things and then present them on Window. 39 | renderStuff(); 40 | surface.present(); 41 | } 42 | } 43 | ``` 44 | 45 | And finally, in order to receive `redrawRequested` events, call `win.requestRedraw` 46 | in your program's main loop. 47 | 48 | ## TODO 49 | 50 | - Try to match Web API. Such as not having to call `present`. 51 | - Complete the `winit` bindings. 52 | 53 | ## Notes 54 | 55 | - I'm currently using a fork of Deno in this. It is to export some private structs in order to 56 | extend WebGPU API. Also, the fork uses an updated version of `wgpu` as of now. 57 | - Debug builds seem to be locked at 30 fps usually, while release builds are 60 fps in same test. 58 | 59 | ## License 60 | 61 | Check [LICENSE](LICENSE) for more info. 62 | 63 | Copyright 2021 @ DjDeveloperr 64 | -------------------------------------------------------------------------------- /assets/demo-hello-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DjDeveloperr/deno_desktop/034df2af6111869d5687dbd18460dba25315d7ba/assets/demo-hello-triangle.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | These are based on [learn-wgpu](https://sotrh.github.io/learn-wgpu) as mainly I made this project to learn WebGPU but using JS. 4 | -------------------------------------------------------------------------------- /examples/common.js: -------------------------------------------------------------------------------- 1 | import { enableValidationErrors } from "./extern/gpu_err.bundle.js"; 2 | import { Matrix4 } from "./extern/gmath.bundle.js"; 3 | 4 | export const OPENGL_TO_WGPU_MATRIX = Matrix4.from( 5 | 1.0, 0.0, 0.0, 0.0, 6 | 0.0, 1.0, 0.0, 0.0, 7 | 0.0, 0.0, 0.5, 0.0, 8 | 0.0, 0.0, 0.5, 1.0, 9 | ); 10 | 11 | /** Base Application used by all examples */ 12 | export class App { 13 | constructor(title, width = 800, height = 600) { 14 | this.title = title; 15 | this.width = width; 16 | this.height = height; 17 | } 18 | 19 | // To be extended by subclasses 20 | async init() {} 21 | async render(_encoder, _view) {} 22 | async onEvent(_event) {} 23 | 24 | mainLoop() { 25 | this.window.requestRedraw(); 26 | } 27 | 28 | async run() { 29 | this.adapter = await navigator.gpu.requestAdapter(); 30 | if (!this.adapter) { 31 | throw new Error("No GPU adapter available!"); 32 | } 33 | 34 | this.device = await this.adapter.requestDevice(); 35 | enableValidationErrors(this.device, true); 36 | 37 | this.window = Deno.createWindow({ 38 | title: this.title, 39 | width: this.width, 40 | height: this.height, 41 | resizable: true, 42 | }); 43 | 44 | this.surface = this.window.createSurface(this.device); 45 | 46 | this.format = this.surface.getPreferredFormat(); 47 | this.surface.configure({ 48 | format: this.format, 49 | width: this.width, 50 | height: this.height, 51 | }); 52 | 53 | await this.init(); 54 | 55 | this.mainLoopInterval = setInterval(() => this.mainLoop(), 1000 / 60); 56 | 57 | for await (const event of Deno.eventLoop()) { 58 | await this.onEvent(event); 59 | if (event.type === "windowEvent" && event.windowID === this.window.id) { 60 | if (event.event.type === "closeRequested") { 61 | this.cleanup(); 62 | Deno.exit(0); 63 | } else if (event.event.type === "resized") { 64 | this.width = event.event.width; 65 | this.height = event.event.height; 66 | this.surface.configure({ 67 | format: this.format, 68 | width: this.width, 69 | height: this.height, 70 | }); 71 | } 72 | } else if (event.type === "redrawRequested" && event.windowID === this.window.id) { 73 | const texture = this.surface.getCurrentTexture(); 74 | const view = texture.createView(); 75 | const encoder = this.device.createCommandEncoder(); 76 | this.render(encoder, view); 77 | this.device.queue.submit([encoder.finish()]); 78 | this.surface.present(); 79 | } 80 | } 81 | } 82 | 83 | cleanup() { 84 | if (this.mainLoopInterval) { 85 | clearInterval(this.mainLoopInterval); 86 | this.mainLoopInterval = null; 87 | } 88 | 89 | this.surface.destroy(); 90 | this.window.close(); 91 | this.device.destroy(); 92 | } 93 | 94 | async loadShader(name) { 95 | const code = await Deno.readTextFile(new URL(`./shaders/${name}.wgsl`, import.meta.url)); 96 | return this.device.createShaderModule({ 97 | label: name, 98 | code, 99 | }); 100 | } 101 | 102 | createBuffer({ label, usage, data, size }) { 103 | const buffer = this.device.createBuffer({ 104 | label, 105 | usage, 106 | size: ((data ? data.byteLength : size) + 3) & ~3, 107 | mappedAtCreation: data ? true : false, 108 | }); 109 | 110 | if (data) { 111 | new data.constructor(buffer.getMappedRange()).set(data); 112 | buffer.unmap(); 113 | } 114 | 115 | return buffer; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/extern/README.md: -------------------------------------------------------------------------------- 1 | # extern 2 | 3 | contains stuff not by me, such as assets or bundled third party dependencies. 4 | -------------------------------------------------------------------------------- /examples/extern/deno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DjDeveloperr/deno_desktop/034df2af6111869d5687dbd18460dba25315d7ba/examples/extern/deno.png -------------------------------------------------------------------------------- /examples/extern/gmath.bundle.js: -------------------------------------------------------------------------------- 1 | class Angle { 2 | static turn; 3 | value; 4 | constructor(value = 0){ 5 | this.value = value; 6 | } 7 | } 8 | class Rad extends Angle { 9 | static turn = 2 * Math.PI; 10 | sin() { 11 | return Math.sin(this.value); 12 | } 13 | cos() { 14 | return Math.cos(this.value); 15 | } 16 | tan() { 17 | return Math.tan(this.value); 18 | } 19 | sincos() { 20 | return [ 21 | Math.sin(this.value), 22 | Math.cos(this.value) 23 | ]; 24 | } 25 | csc() { 26 | return 1 / this.sin(); 27 | } 28 | cot() { 29 | return 1 / this.tan(); 30 | } 31 | sec() { 32 | return 1 / this.cos(); 33 | } 34 | asin() { 35 | return Math.asin(this.value); 36 | } 37 | acos() { 38 | return Math.acos(this.value); 39 | } 40 | atan() { 41 | return Math.atan(this.value); 42 | } 43 | add(other) { 44 | const value = other instanceof Angle ? other.toRad().value : other; 45 | return new Rad(this.value + value); 46 | } 47 | sub(other) { 48 | const value = other instanceof Angle ? other.toRad().value : other; 49 | return new Rad(this.value - value); 50 | } 51 | mul(other) { 52 | const value = other instanceof Angle ? other.toRad().value : other; 53 | return new Rad(this.value * value); 54 | } 55 | div(other) { 56 | const value = other instanceof Angle ? other.toRad().value : other; 57 | return new Rad(this.value / value); 58 | } 59 | neg() { 60 | return new Rad(-this.value); 61 | } 62 | eq(other) { 63 | const value = other instanceof Angle ? other.toRad().value : other; 64 | return this.value === value; 65 | } 66 | normal() { 67 | const rem = this.value % Rad.turn; 68 | return new Rad(rem < 0 ? rem + Rad.turn : rem); 69 | } 70 | normalize() { 71 | const rem = this.value % Rad.turn; 72 | this.value = rem < 0 ? rem + Rad.turn : rem; 73 | return this; 74 | } 75 | toRad() { 76 | return this; 77 | } 78 | toDeg() { 79 | return new Deg(this.value * (180 / Math.PI)); 80 | } 81 | toString() { 82 | return `${this.value} rad`; 83 | } 84 | } 85 | class Deg extends Angle { 86 | static turn = 360; 87 | sin() { 88 | return this.toRad().sin(); 89 | } 90 | cos() { 91 | return this.toRad().cos(); 92 | } 93 | tan() { 94 | return this.toRad().tan(); 95 | } 96 | sincos() { 97 | return this.toRad().sincos(); 98 | } 99 | csc() { 100 | return this.toRad().csc(); 101 | } 102 | cot() { 103 | return this.toRad().cot(); 104 | } 105 | sec() { 106 | return this.toRad().sec(); 107 | } 108 | asin() { 109 | return this.toRad().asin(); 110 | } 111 | acos() { 112 | return this.toRad().acos(); 113 | } 114 | atan() { 115 | return this.toRad().atan(); 116 | } 117 | add(other) { 118 | const value = other instanceof Angle ? other.toDeg().value : other; 119 | return new Deg(this.value + value); 120 | } 121 | sub(other) { 122 | const value = other instanceof Angle ? other.toDeg().value : other; 123 | return new Deg(this.value - value); 124 | } 125 | mul(other) { 126 | const value = other instanceof Angle ? other.toDeg().value : other; 127 | return new Deg(this.value * value); 128 | } 129 | div(other) { 130 | const value = other instanceof Angle ? other.toDeg().value : other; 131 | return new Deg(this.value / value); 132 | } 133 | neg() { 134 | return new Deg(-this.value); 135 | } 136 | eq(other) { 137 | const value = other instanceof Angle ? other.toDeg().value : other; 138 | return this.value === value; 139 | } 140 | normal() { 141 | const rem = this.value % Deg.turn; 142 | return new Deg(rem < 0 ? rem + Deg.turn : rem); 143 | } 144 | normalize() { 145 | const rem = this.value % Deg.turn; 146 | this.value = rem < 0 ? rem + Deg.turn : rem; 147 | return this; 148 | } 149 | toRad() { 150 | return new Rad(this.value * (Math.PI / 180)); 151 | } 152 | toDeg() { 153 | return this; 154 | } 155 | toString() { 156 | return `${this.value} deg`; 157 | } 158 | } 159 | export { Angle as Angle }; 160 | export { Rad as Rad }; 161 | export { Deg as Deg }; 162 | function decode(b64) { 163 | const binString = atob(b64); 164 | const size = binString.length; 165 | const bytes = new Uint8Array(size); 166 | for(let i = 0; i < size; i++){ 167 | bytes[i] = binString.charCodeAt(i); 168 | } 169 | return bytes; 170 | } 171 | const importMeta = { 172 | url: "https://deno.land/x/lz4@v0.1.2/wasm.js", 173 | main: false 174 | }; 175 | const source = Uint8Array.from(atob("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QcCjwAAgAjYCAEHIo8AAIAMgBmoiADYCACAAIAJBAXI2AgQgASADaiACNgIAIANBBGohACAGQQNyCyEGIAAgBjYCAAwHC0Hso8AAKAIAIgBBACAAIANNG0UEQEHso8AAIAM2AgALQfCjwABB/x82AgBB3KPAACAFNgIAQdijwAAgAzYCAEHEoMAAQbigwAA2AgBBzKDAAEHAoMAANgIAQcCgwABBuKDAADYCAEHUoMAAQcigwAA2AgBByKDAAEHAoMAANgIAQdygwABB0KDAADYCAEHQoMAAQcigwAA2AgBB5KDAAEHYoMAANgIAQdigwABB0KDAADYCAEHsoMAAQeCgwAA2AgBB4KDAAEHYoMAANgIAQfSgwABB6KDAADYCAEHooMAAQeCgwAA2AgBB/KDAAEHwoMAANgIAQfCgwABB6KDAADYCAEHko8AAQQA2AgBBhKHAAEH4oMAANgIAQfigwABB8KDAADYCAEGAocAAQfigwAA2AgBBjKHAAEGAocAANgIAQYihwABBgKHAADYCAEGUocAAQYihwAA2AgBBkKHAAEGIocAANgIAQZyhwABBkKHAADYCAEGYocAAQZChwAA2AgBBpKHAAEGYocAANgIAQaChwABBmKHAADYCAEGsocAAQaChwAA2AgBBqKHAAEGgocAANgIAQbShwABBqKHAADYCAEGwocAAQaihwAA2AgBBvKHAAEGwocAANgIAQbihwABBsKHAADYCAEHEocAAQbihwAA2AgBBzKHAAEHAocAANgIAQcChwABBuKHAADYCAEHUocAAQcihwAA2AgBByKHAAEHAocAANgIAQdyhwABB0KHAADYCAEHQocAAQcihwAA2AgBB5KHAAEHYocAANgIAQdihwABB0KHAADYCAEHsocAAQeChwAA2AgBB4KHAAEHYocAANgIAQfShwABB6KHAADYCAEHoocAAQeChwAA2AgBB/KHAAEHwocAANgIAQfChwABB6KHAADYCAEGEosAAQfihwAA2AgBB+KHAAEHwocAANgIAQYyiwABBgKLAADYCAEGAosAAQfihwAA2AgBBlKLAAEGIosAANgIAQYiiwABBgKLAADYCAEGcosAAQZCiwAA2AgBBkKLAAEGIosAANgIAQaSiwABBmKLAADYCAEGYosAAQZCiwAA2AgBBrKLAAEGgosAANgIAQaCi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AEsbIgIgAkEEaiICIAJBAnRBmJHAAGooAgBBC3QgAEsbIgIgAkECaiICIAJBAnRBmJHAAGooAgBBC3QgAEsbIgIgAkEBaiICIAJBAnRBmJHAAGooAgBBC3QgAEsbIgJBAnRBmJHAAGooAgBBC3QiBCAARiAEIABJaiACaiIEQR5NBEAgBEECdCEGQbEFIQACQCAEQR5GDQAgBkGckcAAaiICRQ0AIAIoAgBBFXYhAAtBACECIARBf2oiBSAETQRAIAVBH08NBiAFQQJ0QZiRwABqKAIAQf///wBxIQILAkAgACAGQZiRwABqKAIAQRV2IgRBAWpGDQAgAyACayECIABBf2ohBUEAIQADQCAEQbAFSw0DIAAgBEGkksAAai0AAGoiACACSw0BIAUgBEEBaiIERw0ACwsgBEEBcQ0GIANBgIAESQ0CIANBgIAISQ0DIANBkPxHakGQ/AtJIANBtdlzakG12ytJciADQeKLdGpB4gtJIANBn6h0akGfGElyciADQf7//wBxQZ7wCkYgA0He4nRqQQ5JciADQaKydWpBIklycg0EIANBy5F1akEKSw0IDAQLIARBH0HYl8AAEBwACyAEQbEFQeiXwAAQHAALIANBkIbAAEEpQeKGwABBogJBhInAAEG1AhAPRQ0BDAULIANBuYvAAEEmQYWMwABBrwFBtI3AAEGjAxAPDQQLIANBAXJnQQJ2QQdzrUKAgICA0ACEDAILIAVBH0GUksAAEBwACyADQQFyZ0ECdkEHc61CgICAgNAAhAshB0EDIQQMAQtBASEECyADIQILA0AgBCEDQdwAIQBBASEFQQEhBAJAAn4CQAJAAkACQCADQQFrDgMBBQACCwJAAkACQAJAIAdCIIinQf8BcUEBaw4FAwIBAAYFC0H1ACEAIAdC/////49gg0KAgICAMIQMBgtB+wAhACAHQv////+PYINCgICAgCCEDAULIAIgB6ciA0ECdEEccXZBD3EiAEEwciAAQdcAaiAAQQpJGyEAIAdCf3xC/////w+DIAdCgICAgHCDhCADDQQaIAdC/////49gg0KAgICAEIQMBAtB/QAhACAHQv////+PYIMMAwtBACEEIAIhAAwDCyABKAIYQScgASgCHCgCEBEAAA8LIAdC/////49gg0KAgICAwACECyEHQQMhBAsgASgCGCAAIAEoAhwo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dCADakHQAGooAgAiB0F/Rg0BIANBQGsgBxAjIANBQGsQPEcNASABIAdrIgZBgIAETw0BIANBGGogAygCQCIFIAQgCkHMnMAAEC0gAygCGCEJIAMoAhwhASADQRBqIAUgBCAHQQRqQdycwAAQLSADKAIUIgUgASABIAVLGyEFQQAhASADKAIQIQQDQCABIAVPDQQgASAJai0AACABIARqLQAARw0EIAFBAWohAQwACwALIAZBgCBB7JzAABAcAAsgA0FAa0EBEBsEQCACQQFqIQIgAygCRCEEIAMoAkwhAQwBCwsgAkEEdEFwIAJBD0kbIQpBACEBIAUhBkEAIQUMAQsgA0FAayABQQRqEBsaIAJBBHRBcCACQQ9JGyEKIAZBgP4DcUEIdiEJQQEhBUEPIAFBDksNARoLIAELIQQgAygCSCIHIAQgCnIQKiACQQ9PBEAgA0FAayACQXFqEC8LIANBCGogCCACIAhqIAMoAkAgAygCRCIEQfycwAAQKCAHIAMoAgggAygCDBAxIAUEQCAHIAYQKiAHIAkQKiABQQ9PBEAgA0FAayABQXFqEC8LIAMoAkwhCCAGIQUMAQsLIANB2IABaiIBIANBOGooAgA2AgAgAyADKQMwNwPQgAEgA0EoahA0IANByABqIAEoAgA2AgAgAyADKQPQgAE3A0AgAyADQUBrECYgACADKQMANwIAIANB0IACaiQAC+MFAgN/AX4jAEHgAGsiAyQAIANBEGogASACECkgAyADKAIUIgE2AhwgAyADKAIQIgI2AhggA0EwakGAIBAhIANBADoATCADIAE2AkQgAyACNgJAIAMgA0EwajYCSANAAkACQAJAAkACQAJAAkACQAJAIAFFDQAgA0HQAGogA0FAa0EBEBggAy0AUEEBRg0BIAMoAlgEQCADIAMoAlQtAAAiAToATCABQQR2IgFBD0YEQCADQdAAaiADQUBrEBYgAykDUCIGp0H/AXFBAUYNBCAGQiCIp0EPaiEBCyADKAJIIANB0ABqIANBQGsgARAYIAMtAFBBAUYEQCADLQBRQQBHIQEMCQsgA0EIaiADKAJUIAMoAlgiASABQbiawAAQLiADKAIIIAMoAgwQMSADKAJERQ0BIANB0ABqIANBQGtBAhAYIAMtAFBBAUYNBSADKAJYQQJHDQQgAygCVC8AACEBIAMtAExB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QQBBAEGcnMAAEBwAC30BAX8jAEEQayICJAACQCAALQAAQQFGBEAgAiABKAIYQZCewABBGiABQRxqKAIAKAIMEQEAIgA6AAgMAQsgAiABKAIYQaqewABBDSABQRxqKAIAKAIMEQEAIgA6AAgLIAIgATYCACACQQA6AAkgAkEANgIEIAJBEGokACAAC4EBAgJ/An4jAEEQayIDJAAgAAJ/IAEoAgQiBCACTwRAIANBCGogASgCACAEIAJBrJzAABAuIAMpAwghBSADIAEoAgAgASgCBCACQbycwAAQLSADKQMAIQYgAEEEaiAFNwIAIAEgBjcCAEEADAELIABBADoAAUEBCzoAACADQRBqJAALdwEDfyMAQSBrIgIkAAJAIAAoAgAgARARRQRAIAFBHGooAgAhAyABKAIYIAJBHGpBADYCACACQeSewAA2AhggAkIBNwIMIAJBqIXAADYCCCADIAJBCGoQBkUNAQsgAkEgaiQAQQEPCyAAKAIEIAEQESACQSBqJAALgAEBAX8jAEFAaiIDJAAgA0ErNgIMIANByJrAADYCCCADIAE2AhQgAyAANgIQIANBLGpBAjYCACADQTxqQQM2AgAgA0ICNwIcIANB7JjAADYCGCADQQQ2AjQgAyADQTBqNgIoIAMgA0EQajYCOCADIANBCGo2AjAgA0EYaiACEDAAC3ABAn8CQANAIAEEQCAAIAAoAgwiAkEEaiAAKAIESQR/IAAQMiIDQf8fSw0DIAAgA0ECdGpBEGogAjYCACAAKAIMBSACC0EBajYCDCABQX9qIQEMAQsLIAAoAgwgACgCBE0PCyADQYAgQfCdwAAQHAALbAEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBHGpBAjYCACADQSxqQQI2AgAgA0ICNwIMIANB6IDAADYCCCADQQI2AiQgAyADQSBqNgIYIAMgAzYCKCADIANBBGo2AiAgA0EIaiACEDAAC2wBAX8jAEEwayIDJAAgAyABNgIEIAMgADYCACADQRxqQQI2AgAgA0EsakECNgIAIANCAjcCDCADQYSDwAA2AgggA0ECNgIkIAMgA0EgajYCGCADIANBBGo2AiggAyADNgIgIANBCGogAhAwAAtsAQF/IwBBMGsiAyQAIAMgATYCBCADIAA2AgAgA0EcakECNgIAIANBLGpBAjYCACADQgI3AgwgA0G8g8AANgIIIANBAjYCJCADIANBIGo2AhggAyADQQRqNgIoIAMgAzYCICADQQhqIAIQMAALWQEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakGImsAAIAJBCGoQBiACQSBqJAALVgEBfyMAQSBrIgIkACACIAA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakGImsAAIAJBCGoQBiACQSBqJAALaQECfyMAQRBrIgIkAAJAIAFBf0oEQCACQQhqIAEQMyACKAIIIgNFDQEgAigCDCEBIABBADYCCCAAIAM2AgAgACABNgIEIAJBEGokAA8LEDgACyABQQFBhKTAACgCACIAQQEgABsRAgAAC2oBAn9BASEAAkACQEH4o8AAKAIAQQFHBEBB+KPAAEKBgICAEDcDAAwBC0H8o8AAQfyjwAAoAgBBAWoiADYCACAAQQJLDQELQYCkwAAoAgAiAUF/TA0AQYCkwAAgATYCACAAQQFLDQAACwALUwEBfyMAQRBrIgIkACACIAEgAUEEaiAAKAIAIAAoAgRBgJ7AABAoIAIoAgRBBEcEQCACQQhqQfSawABBgJ7AABAaAAsgAigCACgAACACQRBqJAALQwEDfwJAIAJFDQADQCAALQAAIgQgAS0AACIFRgRAIABBAWohACABQQFqIQEgAkF/aiICDQEMAgsLIAQgBWshAwsgAwtKAAJ/IAFBgIDEAEcEQEEBIAAoAhggASAAQRxqKAIAKAIQEQAADQEaCyACRQRAQQAPCyAAKAIYIAIgAyAAQRxqKAIAKAIMEQEACwtEAQF/IwBBIGsiAiQAIAJBGGogAUEIaigCADYCACACIAEpAgA3AxAgAkEIaiACQRBqEBUgACACKQMINwIAIAJBIGokAAtHAQF/IwBBIGsiAyQAIANBFGpBADYCACADQeSewAA2AhAgA0IBNwIEIAMgATYCHCADIAA2AhggAyADQRhqNgIAIAMgAhAwAAs6AAJAIAIgAU8EQCAEIAJPDQEgAiAEIAUQHQALIAEgAiAFEB4ACyAAIAIgAWs2AgQgACABIANqNgIACz8BAX8jAEEgayIDJAAgAyACNgIYIAMg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XHNyY1xnaXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjNcbHo0LWNvbXByZXNzaW9uLTAuNy4wXHNyY1xjb21wcmVzcy5yc4wOEABkAAAAWAAAAA0AAACMDhAAZAAAAHYAAAAcAAAASW52YWxpZERlZHVwbGljYXRpb25PZmZzZXRVbmV4cGVjdGVkRW5kY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZQAAdA8QABcAAACiAQAADwAAAHNyYy9saWJzdGQvcGFuaWNraW5nLnJzVHJpZWQgdG8gc2hyaW5rIHRvIGEgbGFyZ2VyIGNhcGFjaXR5AMAPEABuAAAAIgAAAAkAAABDOlxVc2Vyc1xlbGlhc1wucnVzdHVwXHRvb2xjaGFpbnNcc3RhYmxlLXg4Nl82NC1wYy13aW5kb3dzLW1zdmNcbGliL3J1c3RsaWIvc3JjL3J1c3Rcc3JjL2xpYmNvcmUvbWFjcm9zL21vZC5ycwB7CXByb2R1Y2VycwIIbGFuZ3VhZ2UBBFJ1c3QADHByb2Nlc3NlZC1ieQMFcnVzdGMdMS40NC4xIChjNzA4N2ZlMDAgMjAyMC0wNi0xNykGd2FscnVzBjAuMTcuMAx3YXNtLWJpbmRnZW4SMC4yLjY0ICgzMWMyZDZmYmUp"), (A1)=>A1.charCodeAt(0) 176 | ); 177 | let A, I = null; 178 | function g() { 179 | return null !== I && I.buffer === A.memory.buffer || (I = new Uint8Array(A.memory.buffer)), I; 180 | } 181 | let B = 0; 182 | function Q(A2, I1) { 183 | const Q1 = I1(1 * A2.length); 184 | return g().set(A2, Q1 / 1), B = A2.length, Q1; 185 | } 186 | let C = null; 187 | function E() { 188 | return null !== C && C.buffer === A.memory.buffer || (C = new Int32Array(A.memory.buffer)), C; 189 | } 190 | function D(A3, I2) { 191 | return g().subarray(A3 / 1, A3 / 1 + I2); 192 | } 193 | function lz4_decompress(I4) { 194 | var g2 = Q(I4, A.__wbindgen_malloc), C2 = B; 195 | A.lz4_decompress(8, g2, C2); 196 | var w2 = E()[2], o2 = E()[3], i = D(w2, o2).slice(); 197 | return A.__wbindgen_free(w2, 1 * o2), i; 198 | } 199 | async function w(A4, I5) { 200 | if ("function" == typeof Response && A4 instanceof Response) { 201 | if ("function" == typeof WebAssembly.instantiateStreaming) try { 202 | return await WebAssembly.instantiateStreaming(A4, I5); 203 | } catch (I6) { 204 | if ("application/wasm" == A4.headers.get("Content-Type")) throw I6; 205 | console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", I6); 206 | } 207 | const g3 = await A4.arrayBuffer(); 208 | return await WebAssembly.instantiate(g3, I5); 209 | } 210 | { 211 | const g4 = await WebAssembly.instantiate(A4, I5); 212 | return g4 instanceof WebAssembly.Instance ? { 213 | instance: g4, 214 | module: A4 215 | } : g4; 216 | } 217 | } 218 | async function o(I7) { 219 | void 0 === I7 && (I7 = importMeta.url.replace(/\.js$/, "_bg.wasm")); 220 | ("string" == typeof I7 || "function" == typeof Request && I7 instanceof Request || "function" == typeof URL && I7 instanceof URL) && (I7 = fetch(I7)); 221 | const { instance: g5 , module: B1 } = await w(await I7, { 222 | }); 223 | return A = g5.exports, o.__wbindgen_wasm_module = B1, A; 224 | } 225 | await o(source); 226 | function decompress(input) { 227 | return lz4_decompress(input); 228 | } 229 | const source1 = decompress(decode("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JQgQDLECABAAEgjNBhAYLwEA+gAAWAAB0AAQCZoBEAC9ADIKlJJdBjAcIgslAAAkAgElADMIIgwlADQUIgMlABAgXQYA7AAiAiA2AgApAQE0AwASCiGSIDADIRQiNAYAlgQQB8cAEA0oBjEgDiBgAAC8BBAHuQERDUoGEA5KBgCLAQNRAAHoAQFRABEcWAcQBQsAECCBAAAtAQA7ABAJmAsAUQAArAYD7gUBUQARDFEAEgNRAGAYIAILngEbAwYwARAAsQIANQESkgwBAAoBABAAD2oDFAAQABAQEAAPlAY0D6AABw+7AyMAEAACoAAPIwYzIQtuog8AqxIAORKwA0EDdEGAgAFqIgMjAhNLZRIDxRXwBANBf0cNAEEBIQJBACEDDAELIANwEhIDBBIQA2gDCHcSQ0EAIQIhDyEEIBAS4AALBQBBgAQLBABBAQsCGg9wIAELC1EBAIwWgwALSAEAAAAEBAARArsYAgwAABgAAAEAAAgAkwUAAAAGAAAABxAAAAEAAAgACBgA8w4ADw4uZGVidWdfYXJhbmdlcwCoCQRuYW1lAYAJGdgYoQEyX1pOOXdlZV8PACExNQcA9RxfZmlyc3RfZml0MTdoNGY0OWRlMzkyMWQ2M2Q2YkUCiQFfWk44OF8kTFQkOwDwBi4uc2l6ZV9jbGFzc2VzLi5TaXplQw0AEEEeANFQb2xpY3kkdTIwJGFzBwAHOQAIIgDwC0dUJDIybmV3X2NlbGxfZm9yX2ZyZWVfbGlzjAD0A2RkYTk4MjhkNTNiMmY0MWRFA5cZLwSTlQBL8ggzMnNob3VsZF9tZXJnZV9hZGphY2VudJ4AAKwA8wdzMTdoZGI4ODdhZDQwZmEwMDc5MUUFlBk1NGRllBkjBg0UABFpkxkFHRpEbXVsCAwARGFkZAkMAENzdWIKRwAIJxoTC0cAAyYaBRgaNW11bBcaRGFkZA4MAENzdWIPRwAXM0cAExBHAAMhGgUTGkRtdWwSDABEYWRkEwwArHN1YhR3X1pONzBqAVhMYXJnZTYBAFEBD1gBDg/tAQj/BGM4OWMyZGQ0ZDc2NDVkZWRFFW55ADhSMTNtaW55AAAkAvAHMTdoZjVlOGU0MWE0ZWZiM2YwOUUWgVQCD3EANQ9CAhL/H2I2OTdhNGE3NDgxOGE4MzJFF1JfWk40Y29yZTNwdHI0OGRyb3BfaW5fcGxhY2WYAA0A5wDwBjdoMTkzNWJiNjI3MDA3ZGE0ZEUYgNgADywDRw5bAfB5MDJjM2U1ZGY0NTAxM2QwMkUHEgEAD19fc3RhY2tfcG9pbnRlcgkKAQAHLnJvZGF0YQBVCXByb2R1Y2VycwIIbGFuZ3VhZ2UBBFJ1c3QADHByb2Nlc3NlZC1ieQEFcnVzdGMlMS41NC4wLW5pZ2h0bHkgKDNlOTk0MzlmNCAyMDIxLTA1LTE3KQ==")); 230 | const { instance } = await WebAssembly.instantiate(source1, { 231 | env: { 232 | panic: (ptr, len)=>{ 233 | const msg = new TextDecoder().decode(new Uint8Array(memory.buffer, ptr, len)); 234 | throw new Error(msg); 235 | } 236 | } 237 | }); 238 | const memory = instance.exports.memory; 239 | const alloc = instance.exports.alloc; 240 | instance.exports.dealloc; 241 | const matrix2determinant = instance.exports.matrix2determinant; 242 | const matrix2invert = instance.exports.matrix2invert; 243 | const matrix2mul = instance.exports.matrix2mul; 244 | const matrix2add = instance.exports.matrix2add; 245 | const matrix2sub = instance.exports.matrix2sub; 246 | const matrix3determinant = instance.exports.matrix3determinant; 247 | const matrix3invert = instance.exports.matrix3invert; 248 | const matrix3mul = instance.exports.matrix3mul; 249 | const matrix3add = instance.exports.matrix3add; 250 | const matrix3sub = instance.exports.matrix3sub; 251 | const matrix4determinant = instance.exports.matrix4determinant; 252 | const matrix4invert = instance.exports.matrix4invert; 253 | const matrix4mul = instance.exports.matrix4mul; 254 | const matrix4add = instance.exports.matrix4add; 255 | const matrix4sub = instance.exports.matrix4sub; 256 | class Vector3 { 257 | #internal = new Float32Array(3); 258 | get [0]() { 259 | return this.#internal[0]; 260 | } 261 | set [0](val) { 262 | this.#internal[0] = val; 263 | } 264 | get [1]() { 265 | return this.#internal[1]; 266 | } 267 | set [1](val) { 268 | this.#internal[1] = val; 269 | } 270 | get [2]() { 271 | return this.#internal[2]; 272 | } 273 | set [2](val) { 274 | this.#internal[2] = val; 275 | } 276 | get x() { 277 | return this.#internal[0]; 278 | } 279 | set x(val) { 280 | this.#internal[0] = val; 281 | } 282 | get y() { 283 | return this.#internal[1]; 284 | } 285 | set y(val) { 286 | this.#internal[1] = val; 287 | } 288 | get z() { 289 | return this.#internal[2]; 290 | } 291 | set z(val) { 292 | this.#internal[2] = val; 293 | } 294 | static negativeInfinity() { 295 | return new Vector3(Number.NEGATIVE_INFINITY); 296 | } 297 | static positiveInfinity() { 298 | return new Vector3(Number.POSITIVE_INFINITY); 299 | } 300 | static zero() { 301 | return new Vector3(0); 302 | } 303 | static one() { 304 | return new Vector3(1); 305 | } 306 | static up() { 307 | return new Vector3(0, 1, 0); 308 | } 309 | static down() { 310 | return new Vector3(0, -1, 0); 311 | } 312 | static left() { 313 | return new Vector3(-1, 0, 0); 314 | } 315 | static right() { 316 | return new Vector3(1, 0, 0); 317 | } 318 | static back() { 319 | return new Vector3(0, 0, -1); 320 | } 321 | static forward() { 322 | return new Vector3(0, 0, 1); 323 | } 324 | static fromHomogeneous(vector) { 325 | return vector.trunc().mul(1 / vector.w); 326 | } 327 | constructor(x, y, z){ 328 | if (x !== undefined) { 329 | this.x = x; 330 | if (y !== undefined && z !== undefined) { 331 | this.y = y; 332 | this.z = z; 333 | } else { 334 | this.y = x; 335 | this.z = x; 336 | } 337 | } 338 | } 339 | clone() { 340 | return new Vector3(this.x, this.y, this.z); 341 | } 342 | mag() { 343 | return Math.hypot(this.x, this.y, this.z); 344 | } 345 | mag2() { 346 | return this.x ** 2 + this.y ** 2 + this.z ** 2; 347 | } 348 | normal() { 349 | return this.div(this.mag()); 350 | } 351 | truncN(n) { 352 | switch(n){ 353 | case 0: 354 | return new Vector2(this.y, this.z); 355 | case 1: 356 | return new Vector2(this.x, this.z); 357 | case 2: 358 | return new Vector2(this.x, this.y); 359 | } 360 | } 361 | trunc() { 362 | return new Vector2(this.x, this.y); 363 | } 364 | clamp(length) { 365 | return this.normal().mul(length); 366 | } 367 | dot(other) { 368 | const { x , y , z } = this.mul(other); 369 | return x + y + z; 370 | } 371 | cross(other) { 372 | return new Vector3(this.y * other.z - this.z * other.y, this.z * other.x - this.x * other.z, this.x * other.y - this.y * other.x); 373 | } 374 | lerp(other, alpha) { 375 | return this.add(other.sub(this).mul(alpha)); 376 | } 377 | set(other) { 378 | this.x = other.x; 379 | this.y = other.y; 380 | this.z = other.z; 381 | return this; 382 | } 383 | add(other) { 384 | const { x , y , z } = typeof other === "number" ? { 385 | x: other, 386 | y: other, 387 | z: other 388 | } : other; 389 | return new Vector3(this.x + x, this.y + y, this.z + z); 390 | } 391 | sub(other) { 392 | const { x , y , z } = typeof other === "number" ? { 393 | x: other, 394 | y: other, 395 | z: other 396 | } : other; 397 | return new Vector3(this.x - x, this.y - y, this.z - z); 398 | } 399 | mul(other) { 400 | const { x , y , z } = typeof other === "number" ? { 401 | x: other, 402 | y: other, 403 | z: other 404 | } : other; 405 | return new Vector3(this.x * x, this.y * y, this.z * z); 406 | } 407 | div(other) { 408 | const { x , y , z } = typeof other === "number" ? { 409 | x: other, 410 | y: other, 411 | z: other 412 | } : other; 413 | return new Vector3(this.x / x, this.y / y, this.z / z); 414 | } 415 | neg() { 416 | return new Vector3(-this.x, -this.y, -this.z); 417 | } 418 | midpoint(other) { 419 | return other.sub(this).div(2).add(this); 420 | } 421 | eq(other) { 422 | return this.x === other.x && this.y === other.y && this.z === other.z; 423 | } 424 | isFinite() { 425 | return isFinite(this.x) && isFinite(this.y) && isFinite(this.z); 426 | } 427 | extend(w1) { 428 | return new Vector4(this.x, this.y, this.z, w1); 429 | } 430 | toHomogeneous() { 431 | return this.extend(1); 432 | } 433 | toString() { 434 | return `Vector3 { x: ${this[0]}, y: ${this[1]}, z: ${this[2]} }`; 435 | } 436 | toArray() { 437 | return [ 438 | this[0], 439 | this[1], 440 | this[2] 441 | ]; 442 | } 443 | toFloat32Array() { 444 | return this.#internal; 445 | } 446 | } 447 | class Vector4 { 448 | #internal = new Float32Array(4); 449 | get [0]() { 450 | return this.#internal[0]; 451 | } 452 | set [0](val) { 453 | this.#internal[0] = val; 454 | } 455 | get [1]() { 456 | return this.#internal[1]; 457 | } 458 | set [1](val) { 459 | this.#internal[1] = val; 460 | } 461 | get [2]() { 462 | return this.#internal[2]; 463 | } 464 | set [2](val) { 465 | this.#internal[2] = val; 466 | } 467 | get [3]() { 468 | return this.#internal[3]; 469 | } 470 | set [3](val) { 471 | this.#internal[3] = val; 472 | } 473 | get x() { 474 | return this.#internal[0]; 475 | } 476 | set x(val) { 477 | this.#internal[0] = val; 478 | } 479 | get y() { 480 | return this.#internal[1]; 481 | } 482 | set y(val) { 483 | this.#internal[1] = val; 484 | } 485 | get z() { 486 | return this.#internal[2]; 487 | } 488 | set z(val) { 489 | this.#internal[2] = val; 490 | } 491 | get w() { 492 | return this.#internal[3]; 493 | } 494 | set w(val) { 495 | this.#internal[3] = val; 496 | } 497 | static negativeInfinity() { 498 | return new Vector4(Number.NEGATIVE_INFINITY); 499 | } 500 | static positiveInfinity() { 501 | return new Vector4(Number.POSITIVE_INFINITY); 502 | } 503 | static zero() { 504 | return new Vector4(0); 505 | } 506 | static one() { 507 | return new Vector4(1); 508 | } 509 | constructor(x, y, z, w2){ 510 | if (x !== undefined) { 511 | this.x = x; 512 | this.y = y ?? x; 513 | this.z = z ?? x; 514 | this.w = w2 ?? x; 515 | } 516 | } 517 | clone() { 518 | return new Vector4(this.x, this.y, this.z, this.w); 519 | } 520 | mag() { 521 | return Math.hypot(this.x, this.y, this.z, this.w); 522 | } 523 | mag2() { 524 | return this.x ** 2 + this.y ** 2 + this.z ** 2 + this.w ** 2; 525 | } 526 | normal() { 527 | return this.div(this.mag()); 528 | } 529 | truncN(n) { 530 | switch(n){ 531 | case 0: 532 | return new Vector3(this.y, this.z, this.w); 533 | case 1: 534 | return new Vector3(this.x, this.z, this.w); 535 | case 2: 536 | return new Vector3(this.x, this.y, this.w); 537 | case 3: 538 | return new Vector3(this.x, this.y, this.z); 539 | } 540 | } 541 | trunc() { 542 | return new Vector3(this.x, this.y, this.z); 543 | } 544 | clamp(length) { 545 | return this.normal().mul(length); 546 | } 547 | dot(other) { 548 | const { x , y , z , w: w3 } = this.mul(other); 549 | return x + y + z + w3; 550 | } 551 | lerp(other, alpha) { 552 | return this.add(other.sub(this).mul(alpha)); 553 | } 554 | set(other) { 555 | this.x = other.x; 556 | this.y = other.y; 557 | this.z = other.z; 558 | this.w = other.w; 559 | return this; 560 | } 561 | add(other) { 562 | const { x , y , z , w: w4 } = typeof other === "number" ? { 563 | x: other, 564 | y: other, 565 | z: other, 566 | w: other 567 | } : other; 568 | return new Vector4(this.x + x, this.y + y, this.z + z, this.w + w4); 569 | } 570 | sub(other) { 571 | const { x , y , z , w: w5 } = typeof other === "number" ? { 572 | x: other, 573 | y: other, 574 | z: other, 575 | w: other 576 | } : other; 577 | return new Vector4(this.x - x, this.y - y, this.z - z, this.w - w5); 578 | } 579 | mul(other) { 580 | const { x , y , z , w: w6 } = typeof other === "number" ? { 581 | x: other, 582 | y: other, 583 | z: other, 584 | w: other 585 | } : other; 586 | return new Vector4(this.x * x, this.y * y, this.z * z, this.w * w6); 587 | } 588 | div(other) { 589 | const { x , y , z , w: w7 } = typeof other === "number" ? { 590 | x: other, 591 | y: other, 592 | z: other, 593 | w: other 594 | } : other; 595 | return new Vector4(this.x / x, this.y / y, this.z / z, this.w / w7); 596 | } 597 | neg() { 598 | return new Vector4(-this.x, -this.y, -this.z, -this.w); 599 | } 600 | midpoint(other) { 601 | return other.sub(this).div(2).add(this); 602 | } 603 | eq(other) { 604 | return this.x === other.x && this.y === other.y && this.z === other.z && this.w === other.w; 605 | } 606 | isFinite() { 607 | return isFinite(this.x) && isFinite(this.y) && isFinite(this.z) && isFinite(this.w); 608 | } 609 | toString() { 610 | return `Vector4 { x: ${this[0]}, y: ${this[1]}, z: ${this[2]}, w: ${this[3]} }`; 611 | } 612 | toArray() { 613 | return [ 614 | this[0], 615 | this[1], 616 | this[2], 617 | this[3] 618 | ]; 619 | } 620 | toFloat32Array() { 621 | return this.#internal; 622 | } 623 | } 624 | class Vector2 { 625 | #internal = new Float32Array(2); 626 | get [0]() { 627 | return this.#internal[0]; 628 | } 629 | set [0](val) { 630 | this.#internal[0] = val; 631 | } 632 | get [1]() { 633 | return this.#internal[1]; 634 | } 635 | set [1](val) { 636 | this.#internal[1] = val; 637 | } 638 | get x() { 639 | return this.#internal[0]; 640 | } 641 | set x(val) { 642 | this.#internal[0] = val; 643 | } 644 | get y() { 645 | return this.#internal[1]; 646 | } 647 | set y(val) { 648 | this.#internal[1] = val; 649 | } 650 | static negativeInfinity() { 651 | return new Vector2(Number.NEGATIVE_INFINITY); 652 | } 653 | static positiveInfinity() { 654 | return new Vector2(Number.POSITIVE_INFINITY); 655 | } 656 | static zero() { 657 | return new Vector2(0); 658 | } 659 | static one() { 660 | return new Vector2(1); 661 | } 662 | static up() { 663 | return new Vector2(0, 1); 664 | } 665 | static down() { 666 | return new Vector2(0, -1); 667 | } 668 | static left() { 669 | return new Vector2(-1, 0); 670 | } 671 | static right() { 672 | return new Vector2(1, 0); 673 | } 674 | constructor(x, y){ 675 | if (x !== undefined) { 676 | this.x = x; 677 | if (y !== undefined) { 678 | this.y = y; 679 | } else { 680 | this.y = x; 681 | } 682 | } 683 | } 684 | clone() { 685 | return new Vector2(this.x, this.y); 686 | } 687 | mag() { 688 | return Math.hypot(this.x, this.y); 689 | } 690 | mag2() { 691 | return this.x ** 2 + this.y ** 2; 692 | } 693 | normal() { 694 | return this.div(this.mag()); 695 | } 696 | angle() { 697 | return new Rad(Math.atan2(this.y, this.x)); 698 | } 699 | clamp(length) { 700 | return this.normal().mul(length); 701 | } 702 | dot(other) { 703 | const { x , y } = this.mul(other); 704 | return x + y; 705 | } 706 | lerp(other, alpha) { 707 | return this.add(other.sub(this).mul(alpha)); 708 | } 709 | set(other) { 710 | this.x = other.x; 711 | this.y = other.y; 712 | return this; 713 | } 714 | add(other) { 715 | const { x , y } = typeof other === "number" ? { 716 | x: other, 717 | y: other 718 | } : other; 719 | return new Vector2(this.x + x, this.y + y); 720 | } 721 | sub(other) { 722 | const { x , y } = typeof other === "number" ? { 723 | x: other, 724 | y: other 725 | } : other; 726 | return new Vector2(this.x - x, this.y - y); 727 | } 728 | mul(other) { 729 | const { x , y } = typeof other === "number" ? { 730 | x: other, 731 | y: other 732 | } : other; 733 | return new Vector2(this.x * x, this.y * y); 734 | } 735 | div(other) { 736 | const { x , y } = typeof other === "number" ? { 737 | x: other, 738 | y: other 739 | } : other; 740 | return new Vector2(this.x / x, this.y / y); 741 | } 742 | neg() { 743 | return new Vector2(-this.x, -this.y); 744 | } 745 | midpoint(other) { 746 | return other.sub(this).div(2).add(this); 747 | } 748 | eq(other) { 749 | return this.x === other.x && this.y === other.y; 750 | } 751 | isFinite() { 752 | return isFinite(this.x) && isFinite(this.y); 753 | } 754 | extend3(z) { 755 | return new Vector3(this.x, this.y, z); 756 | } 757 | extend4(z, w8) { 758 | return new Vector4(this.x, this.y, z, w8); 759 | } 760 | toString() { 761 | return `Vector2 { x: ${this[0]}, y: ${this[1]} }`; 762 | } 763 | toArray() { 764 | return [ 765 | this[0], 766 | this[1] 767 | ]; 768 | } 769 | toFloat32Array() { 770 | return this.#internal; 771 | } 772 | } 773 | export { Vector3 as Vector3 }; 774 | export { Vector4 as Vector4 }; 775 | export { Vector2 as Vector2 }; 776 | class Matrix2 { 777 | ptr; 778 | #internal; 779 | get [0]() { 780 | return new Proxy([ 781 | this.#internal[0], 782 | this.#internal[1] 783 | ], { 784 | set: (_target, prop, value)=>{ 785 | if (prop === "0" || prop === "1") { 786 | this.#internal[prop] = value; 787 | return true; 788 | } 789 | return false; 790 | } 791 | }); 792 | } 793 | set [0](val) { 794 | this.#internal[0] = val[0]; 795 | this.#internal[1] = val[1]; 796 | } 797 | get [1]() { 798 | return new Proxy([ 799 | this.#internal[2], 800 | this.#internal[3] 801 | ], { 802 | set: (_target, prop, value)=>{ 803 | if (prop === "2" || prop === "3") { 804 | this.#internal[2 + prop] = value; 805 | return true; 806 | } 807 | return false; 808 | } 809 | }); 810 | } 811 | set [1](val) { 812 | this.#internal[3] = val[0]; 813 | this.#internal[4] = val[1]; 814 | } 815 | get x() { 816 | return new Vector2(...this[0]); 817 | } 818 | set x(val) { 819 | this.#internal[0] = val.x; 820 | this.#internal[1] = val.y; 821 | } 822 | get y() { 823 | return new Vector2(...this[1]); 824 | } 825 | set y(val) { 826 | this.#internal[2] = val.x; 827 | this.#internal[3] = val.y; 828 | } 829 | static from(c0r0, c0r1, c1r0, c1r1) { 830 | return new Matrix2(new Vector2(c0r0, c0r1), new Vector2(c1r0, c1r1)); 831 | } 832 | static fromAngle(theta) { 833 | const [s, c] = theta.sincos(); 834 | return Matrix2.from(c, s, -s, c); 835 | } 836 | static identity() { 837 | return Matrix2.from(1, 0, 0, 1); 838 | } 839 | static lookAt(dir, up) { 840 | const basis1 = dir.normal(); 841 | const basis2 = up.x * dir.y >= up.y * dir.x ? new Vector2(basis1.y, -basis1.x) : new Vector2(-basis1.y, basis1.x); 842 | return new Matrix2(basis1, basis2); 843 | } 844 | constructor(x, y){ 845 | this.ptr = typeof x === "number" ? x : alloc(16); 846 | this.#internal = new Float32Array(memory.buffer, this.ptr, 4); 847 | if (typeof x !== "number" && x !== undefined) { 848 | this.x = x ?? Vector2.zero(); 849 | this.y = y ?? Vector2.zero(); 850 | } 851 | } 852 | clone() { 853 | return new Matrix2(this.x, this.y); 854 | } 855 | transpose() { 856 | return Matrix2.from(this[0][0], this[1][0], this[0][1], this[1][1]); 857 | } 858 | eq(other) { 859 | return this[0][0] === other[0][0] && this[0][1] === other[0][1] && this[1][0] === other[1][0] && this[1][1] === other[1][1]; 860 | } 861 | isFinite() { 862 | return this.x.isFinite() && this.y.isFinite(); 863 | } 864 | row(n) { 865 | return [ 866 | this[0][n], 867 | this[1][n] 868 | ]; 869 | } 870 | col(n) { 871 | return this[n]; 872 | } 873 | diag() { 874 | return [ 875 | this[0][0], 876 | this[1][1] 877 | ]; 878 | } 879 | trace() { 880 | return this[0][0] + this[1][1]; 881 | } 882 | determinant() { 883 | return matrix2determinant(this.ptr); 884 | } 885 | invert() { 886 | const ptr = matrix2invert(this.ptr); 887 | if (ptr !== 0) { 888 | return new Matrix2(ptr); 889 | } 890 | } 891 | add(other) { 892 | if (typeof other === "number") { 893 | return new Matrix2(this.x.add(other), this.y.add(other)); 894 | } 895 | return new Matrix2(matrix2add(this.ptr, other.ptr)); 896 | } 897 | sub(other) { 898 | if (typeof other === "number") { 899 | return new Matrix2(this.x.sub(other), this.y.sub(other)); 900 | } 901 | return new Matrix2(matrix2sub(this.ptr, other.ptr)); 902 | } 903 | mul(other) { 904 | if (typeof other === "number") { 905 | return new Matrix2(this.x.mul(other), this.y.mul(other)); 906 | } 907 | return new Matrix2(matrix2mul(this.ptr, other.ptr)); 908 | } 909 | toMatrix3() { 910 | return Matrix3.from(this[0][0], this[0][1], 0, this[1][0], this[1][1], 0, 0, 0, 1); 911 | } 912 | toMatrix4() { 913 | return Matrix4.from(this[0][0], this[0][1], 0, 0, this[1][0], this[1][1], 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 914 | } 915 | toArray() { 916 | return [ 917 | this[0], 918 | this[1] 919 | ]; 920 | } 921 | toFloat32Array() { 922 | return new Float32Array(this.#internal); 923 | } 924 | } 925 | class Matrix3 { 926 | ptr; 927 | #internal; 928 | get [0]() { 929 | return new Proxy([ 930 | this.#internal[0], 931 | this.#internal[1], 932 | this.#internal[2] 933 | ], { 934 | set: (_target, prop, value)=>{ 935 | if (prop === "0" || prop === "1" || prop === "2") { 936 | this.#internal[prop] = value; 937 | return true; 938 | } 939 | return false; 940 | } 941 | }); 942 | } 943 | set [0](val) { 944 | this.#internal[0] = val[0]; 945 | this.#internal[1] = val[1]; 946 | this.#internal[2] = val[2]; 947 | } 948 | get [1]() { 949 | return new Proxy([ 950 | this.#internal[3], 951 | this.#internal[4], 952 | this.#internal[5] 953 | ], { 954 | set: (_target, prop, value)=>{ 955 | if (prop === "0" || prop === "1" || prop === "2") { 956 | this.#internal[3 + prop] = value; 957 | return true; 958 | } 959 | return false; 960 | } 961 | }); 962 | } 963 | set [1](val) { 964 | this.#internal[3] = val[0]; 965 | this.#internal[4] = val[1]; 966 | this.#internal[5] = val[2]; 967 | } 968 | get [2]() { 969 | return new Proxy([ 970 | this.#internal[6], 971 | this.#internal[7], 972 | this.#internal[8] 973 | ], { 974 | set: (_target, prop, value)=>{ 975 | if (prop === "0" || prop === "1" || prop === "2") { 976 | this.#internal[6 + prop] = value; 977 | return true; 978 | } 979 | return false; 980 | } 981 | }); 982 | } 983 | set [2](val) { 984 | this.#internal[6] = val[0]; 985 | this.#internal[7] = val[1]; 986 | this.#internal[8] = val[2]; 987 | } 988 | get x() { 989 | return new Vector3(...this[0]); 990 | } 991 | set x(val) { 992 | this.#internal[0] = val.x; 993 | this.#internal[1] = val.y; 994 | this.#internal[2] = val.z; 995 | } 996 | get y() { 997 | return new Vector3(...this[1]); 998 | } 999 | set y(val) { 1000 | this.#internal[3] = val.x; 1001 | this.#internal[4] = val.y; 1002 | this.#internal[5] = val.z; 1003 | } 1004 | get z() { 1005 | return new Vector3(...this[2]); 1006 | } 1007 | set z(val) { 1008 | this.#internal[6] = val.x; 1009 | this.#internal[7] = val.y; 1010 | this.#internal[8] = val.z; 1011 | } 1012 | static from(c0r0, c0r1, c0r2, c1r0, c1r1, c1r2, c2r0, c2r1, c2r2) { 1013 | return new Matrix3(new Vector3(c0r0, c0r1, c0r2), new Vector3(c1r0, c1r1, c1r2), new Vector3(c2r0, c2r1, c2r2)); 1014 | } 1015 | static identity() { 1016 | return Matrix3.from(1, 0, 0, 0, 1, 0, 0, 0, 1); 1017 | } 1018 | static lookToLh(dir, up) { 1019 | dir = dir.normal(); 1020 | const side = up.cross(dir).normal(); 1021 | up = dir.cross(side).normal(); 1022 | return new Matrix3(side, up, dir).transpose(); 1023 | } 1024 | static lookToRh(dir, up) { 1025 | return Matrix3.lookToLh(dir.neg(), up); 1026 | } 1027 | static lookAtLh(eye, center, up) { 1028 | const dir = center.sub(eye); 1029 | return Matrix2.lookAt(dir, up).toMatrix3(); 1030 | } 1031 | static lookAtRh(eye, center, up) { 1032 | const dir = eye.sub(center); 1033 | return Matrix2.lookAt(dir, up).toMatrix3(); 1034 | } 1035 | static fromAngleX(theta) { 1036 | const [s, c] = theta.sincos(); 1037 | return Matrix3.from(1, 0, 0, 0, c, s, 0, -s, c); 1038 | } 1039 | static fromAngleY(theta) { 1040 | const [s, c] = theta.sincos(); 1041 | return Matrix3.from(c, 0, -s, 0, 1, 0, s, 0, c); 1042 | } 1043 | static fromAngleZ(theta) { 1044 | const [s, c] = theta.sincos(); 1045 | return Matrix3.from(c, s, 0, -s, c, 0, 0, 0, 1); 1046 | } 1047 | static fromAxisAngle(axis, angle) { 1048 | const [s, c] = angle.sincos(); 1049 | const c1 = 1 - c; 1050 | return Matrix3.from(c1 * axis.x * axis.x + c, c1 * axis.x * axis.y + s * axis.z, c1 * axis.x * axis.z - s * axis.y, c1 * axis.x * axis.y - s * axis.z, c1 * axis.y * axis.y + c, c1 * axis.y * axis.z + s * axis.x, c1 * axis.x * axis.z + s * axis.y, c1 * axis.y * axis.z - s * axis.x, c1 * axis.z * axis.z + c); 1051 | } 1052 | static fromTranslation(translation) { 1053 | return Matrix3.from(1, 0, 0, 0, 1, 0, translation.x, translation.y, 1); 1054 | } 1055 | static fromScale(scale) { 1056 | return this.fromNonuniformScale(scale, scale); 1057 | } 1058 | static fromNonuniformScale(x, y) { 1059 | return Matrix3.from(x, 0, 0, 0, y, 0, 0, 0, 1); 1060 | } 1061 | static fromQuaternion(quaternion) { 1062 | const x2 = quaternion.vector.x * 2; 1063 | const y2 = quaternion.vector.y * 2; 1064 | const z2 = quaternion.vector.z * 2; 1065 | const xx2 = x2 * quaternion.vector.x; 1066 | const xy2 = x2 * quaternion.vector.y; 1067 | const xz2 = x2 * quaternion.vector.z; 1068 | const yy2 = y2 * quaternion.vector.y; 1069 | const yz2 = y2 * quaternion.vector.z; 1070 | const zz2 = z2 * quaternion.vector.z; 1071 | const sy2 = y2 * quaternion.scalar; 1072 | const sz2 = z2 * quaternion.scalar; 1073 | const sx2 = x2 * quaternion.scalar; 1074 | return Matrix3.from(1 - yy2 - zz2, xy2 + sz2, xz2 - sy2, xy2 - sz2, 1 - xx2 - zz2, yz2 + sx2, xz2 + sy2, yz2 - sx2, 1 - xx2 - yy2); 1075 | } 1076 | static fromDecomposed(decomposed) { 1077 | const m = Matrix2.fromAngle(decomposed.rot).mul(decomposed.scale).toMatrix3(); 1078 | m.z = decomposed.disp.extend3(1); 1079 | return m; 1080 | } 1081 | constructor(x, y, z){ 1082 | this.ptr = typeof x === "number" ? x : alloc(36); 1083 | this.#internal = new Float32Array(memory.buffer, this.ptr, 9); 1084 | if (typeof x !== "number" && x !== undefined) { 1085 | this.x = x ?? Vector3.zero(); 1086 | this.y = y ?? Vector3.zero(); 1087 | this.z = z ?? Vector3.zero(); 1088 | } 1089 | } 1090 | clone() { 1091 | return new Matrix3(this.x, this.y, this.z); 1092 | } 1093 | transpose() { 1094 | return Matrix3.from(this[0][0], this[1][0], this[2][0], this[0][1], this[1][1], this[2][1], this[0][2], this[1][2], this[2][2]); 1095 | } 1096 | eq(other) { 1097 | return this[0][0] === other[0][0] && this[0][1] === other[0][1] && this[0][2] === other[0][2] && this[1][0] === other[1][0] && this[1][1] === other[1][1] && this[1][2] === other[1][2] && this[2][0] === other[2][0] && this[2][1] === other[2][1] && this[2][2] === other[2][2]; 1098 | } 1099 | isFinite() { 1100 | return this.x.isFinite() && this.y.isFinite() && this.z.isFinite(); 1101 | } 1102 | row(n) { 1103 | return [ 1104 | this[0][n], 1105 | this[1][n], 1106 | this[2][n] 1107 | ]; 1108 | } 1109 | col(n) { 1110 | return this[n]; 1111 | } 1112 | diag() { 1113 | return [ 1114 | this[0][0], 1115 | this[1][1], 1116 | this[2][2] 1117 | ]; 1118 | } 1119 | trace() { 1120 | return this[0][0] + this[1][1] + this[2][2]; 1121 | } 1122 | determinant() { 1123 | return matrix3determinant(this.ptr); 1124 | } 1125 | invert() { 1126 | const ptr = matrix3invert(this.ptr); 1127 | if (ptr !== 0) { 1128 | return new Matrix3(ptr); 1129 | } 1130 | } 1131 | add(other) { 1132 | if (typeof other === "number") { 1133 | return new Matrix3(this.x.add(other), this.y.add(other), this.z.add(other)); 1134 | } 1135 | return new Matrix3(matrix3add(this.ptr, other.ptr)); 1136 | } 1137 | sub(other) { 1138 | if (typeof other === "number") { 1139 | return new Matrix3(this.x.sub(other), this.y.sub(other), this.z.sub(other)); 1140 | } 1141 | return new Matrix3(matrix3sub(this.ptr, other.ptr)); 1142 | } 1143 | mul(other) { 1144 | if (typeof other === "number") { 1145 | return new Matrix3(this.x.mul(other), this.y.mul(other), this.z.mul(other)); 1146 | } 1147 | return new Matrix3(matrix3mul(this.ptr, other.ptr)); 1148 | } 1149 | toMatrix4() { 1150 | return Matrix4.from(this[0][0], this[0][1], this[0][2], 0, this[1][0], this[1][1], this[1][2], 0, this[2][0], this[2][1], this[2][2], 0, 0, 0, 0, 1); 1151 | } 1152 | toArray() { 1153 | return [ 1154 | this[0], 1155 | this[1], 1156 | this[2] 1157 | ]; 1158 | } 1159 | toFloat32Array() { 1160 | return new Float32Array(this.#internal); 1161 | } 1162 | } 1163 | class Matrix4 { 1164 | ptr; 1165 | #internal; 1166 | get [0]() { 1167 | return new Proxy([ 1168 | this.#internal[0], 1169 | this.#internal[1], 1170 | this.#internal[2], 1171 | this.#internal[3], 1172 | ], { 1173 | set: (_target, prop, value)=>{ 1174 | if (prop === "0" || prop === "1" || prop === "2" || prop === "3") { 1175 | this.#internal[prop] = value; 1176 | return true; 1177 | } 1178 | return false; 1179 | } 1180 | }); 1181 | } 1182 | set [0](val) { 1183 | this.#internal[0] = val[0]; 1184 | this.#internal[1] = val[1]; 1185 | this.#internal[2] = val[2]; 1186 | this.#internal[3] = val[3]; 1187 | } 1188 | get [1]() { 1189 | return new Proxy([ 1190 | this.#internal[4], 1191 | this.#internal[5], 1192 | this.#internal[6], 1193 | this.#internal[7], 1194 | ], { 1195 | set: (_target, prop, value)=>{ 1196 | if (prop === "0" || prop === "1" || prop === "2" || prop === "3") { 1197 | this.#internal[4 + prop] = value; 1198 | return true; 1199 | } 1200 | return false; 1201 | } 1202 | }); 1203 | } 1204 | set [1](val) { 1205 | this.#internal[4] = val[0]; 1206 | this.#internal[5] = val[1]; 1207 | this.#internal[6] = val[2]; 1208 | this.#internal[7] = val[3]; 1209 | } 1210 | get [2]() { 1211 | return new Proxy([ 1212 | this.#internal[8], 1213 | this.#internal[9], 1214 | this.#internal[10], 1215 | this.#internal[11], 1216 | ], { 1217 | set: (_target, prop, value)=>{ 1218 | if (prop === "0" || prop === "1" || prop === "2" || prop === "3") { 1219 | this.#internal[8 + prop] = value; 1220 | return true; 1221 | } 1222 | return false; 1223 | } 1224 | }); 1225 | } 1226 | set [2](val) { 1227 | this.#internal[8] = val[0]; 1228 | this.#internal[9] = val[1]; 1229 | this.#internal[10] = val[2]; 1230 | this.#internal[11] = val[3]; 1231 | } 1232 | get [3]() { 1233 | return new Proxy([ 1234 | this.#internal[12], 1235 | this.#internal[13], 1236 | this.#internal[14], 1237 | this.#internal[15], 1238 | ], { 1239 | set: (_target, prop, value)=>{ 1240 | if (prop === "0" || prop === "1" || prop === "2" || prop === "3") { 1241 | this.#internal[12 + prop] = value; 1242 | return true; 1243 | } 1244 | return false; 1245 | } 1246 | }); 1247 | } 1248 | set [3](val) { 1249 | this.#internal[12] = val[0]; 1250 | this.#internal[13] = val[1]; 1251 | this.#internal[14] = val[2]; 1252 | this.#internal[15] = val[3]; 1253 | } 1254 | get x() { 1255 | return new Vector4(...this[0]); 1256 | } 1257 | set x(val) { 1258 | this.#internal[0] = val.x; 1259 | this.#internal[1] = val.y; 1260 | this.#internal[2] = val.z; 1261 | this.#internal[3] = val.w; 1262 | } 1263 | get y() { 1264 | return new Vector4(...this[1]); 1265 | } 1266 | set y(val) { 1267 | this.#internal[4] = val.x; 1268 | this.#internal[5] = val.y; 1269 | this.#internal[6] = val.z; 1270 | this.#internal[7] = val.w; 1271 | } 1272 | get z() { 1273 | return new Vector4(...this[2]); 1274 | } 1275 | set z(val) { 1276 | this.#internal[8] = val.x; 1277 | this.#internal[9] = val.y; 1278 | this.#internal[10] = val.z; 1279 | this.#internal[11] = val.w; 1280 | } 1281 | get w() { 1282 | return new Vector4(...this[3]); 1283 | } 1284 | set w(val) { 1285 | this.#internal[12] = val.x; 1286 | this.#internal[13] = val.y; 1287 | this.#internal[14] = val.z; 1288 | this.#internal[15] = val.w; 1289 | } 1290 | static from(c0r0, c0r1, c0r2, c0r3, c1r0, c1r1, c1r2, c1r3, c2r0, c2r1, c2r2, c2r3, c3r0, c3r1, c3r2, c3r3) { 1291 | return new Matrix4(new Vector4(c0r0, c0r1, c0r2, c0r3), new Vector4(c1r0, c1r1, c1r2, c1r3), new Vector4(c2r0, c2r1, c2r2, c2r3), new Vector4(c3r0, c3r1, c3r2, c3r3)); 1292 | } 1293 | static fromPerspective(perspective) { 1294 | if (perspective.left <= perspective.right) { 1295 | throw new RangeError(`perspective.left (${perspective.right}) cannot be greater than perspective.right (${perspective.right})`); 1296 | } 1297 | if (perspective.bottom <= perspective.top) { 1298 | throw new RangeError(`perspective.bottom (${perspective.bottom}) cannot be greater than perspective.top (${perspective.top})`); 1299 | } 1300 | if (perspective.near <= perspective.far) { 1301 | throw new RangeError(`perspective.near (${perspective.near}) cannot be greater than perspective.far (${perspective.far})`); 1302 | } 1303 | const c0r0 = 2 * perspective.near / (perspective.right - perspective.left); 1304 | const c1r1 = 2 * perspective.near / (perspective.top - perspective.bottom); 1305 | const c2r0 = (perspective.right + perspective.left) / (perspective.right - perspective.left); 1306 | const c2r1 = (perspective.top + perspective.bottom) / (perspective.top - perspective.bottom); 1307 | const c2r2 = -(perspective.far + perspective.near) / (perspective.far - perspective.near); 1308 | const c2r3 = -1; 1309 | const c3r2 = -(2 * perspective.far * perspective.near) / (perspective.far - perspective.near); 1310 | return Matrix4.from(c0r0, 0, 0, 0, 0, c1r1, 0, 0, c2r0, c2r1, c2r2, c2r3, 0, 0, c3r2, 0); 1311 | } 1312 | static identity() { 1313 | return Matrix4.from(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 1314 | } 1315 | static fromTranslation(translation) { 1316 | return Matrix4.from(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, translation.x, translation.y, translation.z, 1); 1317 | } 1318 | static fromScale(scale) { 1319 | return this.fromNonuniformScale(scale, scale); 1320 | } 1321 | static fromNonuniformScale(x, y) { 1322 | return Matrix4.from(x, 0, 0, 0, 0, y, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 1323 | } 1324 | static lookToRh(eye, dir, up) { 1325 | const f = dir.normal(); 1326 | const s = f.cross(up).normal(); 1327 | const u = s.cross(f); 1328 | return Matrix4.from(s.x, u.x, -f.x, 0, s.y, u.y, -f.y, 0, s.z, u.z, -f.z, 0, -eye.dot(s), -eye.dot(u), eye.dot(f), 1); 1329 | } 1330 | static lookToLh(eye, dir, up) { 1331 | return Matrix4.lookToRh(eye, dir.neg(), up); 1332 | } 1333 | static lookAtRh(eye, center, up) { 1334 | return Matrix4.lookToRh(eye, center.sub(eye), up); 1335 | } 1336 | static lookAtLh(eye, center, up) { 1337 | return Matrix4.lookToLh(eye, center.sub(eye), up); 1338 | } 1339 | static fromAngleX(theta) { 1340 | const [s, c] = theta.sincos(); 1341 | return Matrix4.from(1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1); 1342 | } 1343 | static fromAngleY(theta) { 1344 | const [s, c] = theta.sincos(); 1345 | return Matrix4.from(c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1); 1346 | } 1347 | static fromAngleZ(theta) { 1348 | const [s, c] = theta.sincos(); 1349 | return Matrix4.from(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 1350 | } 1351 | static fromAxisAngle(axis, angle) { 1352 | const [s, c] = angle.sincos(); 1353 | const c1 = 1 - c; 1354 | return Matrix4.from(c1 * axis.x * axis.x + c, c1 * axis.x * axis.y + s * axis.z, c1 * axis.x * axis.z - s * axis.y, 0, c1 * axis.x * axis.y - s * axis.z, c1 * axis.y * axis.y + c, c1 * axis.y * axis.z + s * axis.x, 0, c1 * axis.x * axis.z + s * axis.y, c1 * axis.y * axis.z - s * axis.x, c1 * axis.z * axis.z + c, 0, 0, 0, 0, 1); 1355 | } 1356 | static fromQuaternion(quaternion) { 1357 | const x2 = quaternion.vector.x + quaternion.vector.x; 1358 | const y2 = quaternion.vector.y + quaternion.vector.y; 1359 | const z2 = quaternion.vector.z + quaternion.vector.z; 1360 | const xx2 = x2 * quaternion.vector.x; 1361 | const xy2 = x2 * quaternion.vector.y; 1362 | const xz2 = x2 * quaternion.vector.z; 1363 | const yy2 = y2 * quaternion.vector.y; 1364 | const yz2 = y2 * quaternion.vector.z; 1365 | const zz2 = z2 * quaternion.vector.z; 1366 | const sy2 = y2 * quaternion.scalar; 1367 | const sz2 = z2 * quaternion.scalar; 1368 | const sx2 = x2 * quaternion.scalar; 1369 | return Matrix4.from(1 - yy2 - zz2, xy2 + sz2, xz2 - sy2, 0, xy2 - sz2, 1 - xx2 - zz2, yz2 + sx2, 0, xz2 + sy2, yz2 - sx2, 1 - xx2 - yy2, 0, 0, 0, 0, 1); 1370 | } 1371 | static fromDecomposed(decomposed) { 1372 | const m = Matrix3.fromQuaternion(decomposed.rot).mul(decomposed.scale).toMatrix4(); 1373 | m.w = decomposed.disp.extend(1); 1374 | return m; 1375 | } 1376 | constructor(x, y, z, w9){ 1377 | this.ptr = typeof x === "number" ? x : alloc(64); 1378 | this.#internal = new Float32Array(memory.buffer, this.ptr, 16); 1379 | if (typeof x !== "number" && x !== undefined) { 1380 | this.x = x ?? Vector4.zero(); 1381 | this.y = y ?? Vector4.zero(); 1382 | this.z = z ?? Vector4.zero(); 1383 | this.w = w9 ?? Vector4.zero(); 1384 | } 1385 | } 1386 | clone() { 1387 | return new Matrix4(this.x, this.y, this.z, this.w); 1388 | } 1389 | transpose() { 1390 | return Matrix4.from(this[0][0], this[1][0], this[2][0], this[3][0], this[0][1], this[1][1], this[2][1], this[3][1], this[0][2], this[1][2], this[2][2], this[3][2], this[0][3], this[1][3], this[2][3], this[3][3]); 1391 | } 1392 | eq(other) { 1393 | return this[0][0] === other[0][0] && this[0][1] === other[0][1] && this[0][2] === other[0][2] && this[0][3] === other[0][3] && this[1][0] === other[1][0] && this[1][1] === other[1][1] && this[1][2] === other[1][2] && this[1][3] === other[1][3] && this[2][0] === other[2][0] && this[2][1] === other[2][1] && this[2][2] === other[2][2] && this[2][3] === other[2][3] && this[3][0] === other[3][0] && this[3][1] === other[3][1] && this[3][2] === other[3][2] && this[3][3] === other[3][3]; 1394 | } 1395 | isFinite() { 1396 | return this.x.isFinite() && this.y.isFinite() && this.z.isFinite() && this.w.isFinite(); 1397 | } 1398 | row(n) { 1399 | return [ 1400 | this[0][n], 1401 | this[1][n], 1402 | this[2][n], 1403 | this[3][n] 1404 | ]; 1405 | } 1406 | col(n) { 1407 | return this[n]; 1408 | } 1409 | diag() { 1410 | return [ 1411 | this[0][0], 1412 | this[1][1], 1413 | this[2][2], 1414 | this[3][3] 1415 | ]; 1416 | } 1417 | trace() { 1418 | return this[0][0] + this[1][1] + this[2][2] + this[3][3]; 1419 | } 1420 | determinant() { 1421 | return matrix4determinant(this.ptr); 1422 | } 1423 | invert() { 1424 | const ptr = matrix4invert(this.ptr); 1425 | if (ptr !== 0) { 1426 | return new Matrix4(ptr); 1427 | } 1428 | } 1429 | add(other) { 1430 | if (typeof other === "number") { 1431 | return new Matrix4(this.x.add(other), this.y.add(other), this.z.add(other), this.w.add(other)); 1432 | } 1433 | return new Matrix4(matrix4add(this.ptr, other.ptr)); 1434 | } 1435 | sub(other) { 1436 | if (typeof other === "number") { 1437 | return new Matrix4(this.x.sub(other), this.y.sub(other), this.z.sub(other), this.w.sub(other)); 1438 | } 1439 | return new Matrix4(matrix4sub(this.ptr, other.ptr)); 1440 | } 1441 | mul(other) { 1442 | if (typeof other === "number") { 1443 | return new Matrix4(this.x.mul(other), this.y.mul(other), this.z.mul(other), this.w.mul(other)); 1444 | } 1445 | return new Matrix4(matrix4mul(this.ptr, other.ptr)); 1446 | } 1447 | toArray() { 1448 | return [ 1449 | this[0], 1450 | this[1], 1451 | this[2], 1452 | this[3], 1453 | ]; 1454 | } 1455 | toFloat32Array() { 1456 | return new Float32Array(this.#internal); 1457 | } 1458 | } 1459 | export { Matrix2 as Matrix2 }; 1460 | export { Matrix3 as Matrix3 }; 1461 | export { Matrix4 as Matrix4 }; 1462 | const epsilon = 3.4028235 * 10 ** 38; 1463 | function absDiffEq(x, y) { 1464 | return (x > y ? x - y : x - y) <= epsilon; 1465 | } 1466 | class Perspective { 1467 | left; 1468 | right; 1469 | bottom; 1470 | top; 1471 | near; 1472 | far; 1473 | constructor(left, right, bottom, top, near, far){ 1474 | this.left = left; 1475 | this.right = right; 1476 | this.bottom = bottom; 1477 | this.top = top; 1478 | this.near = near; 1479 | this.far = far; 1480 | } 1481 | toMatrix4() { 1482 | if (this.left > this.right) { 1483 | throw new RangeError(`perspective.left (${this.right}) cannot be greater than perspective.right (${this.right})`); 1484 | } 1485 | if (this.bottom > this.top) { 1486 | throw new RangeError(`perspective.bottom (${this.bottom}) cannot be greater than perspective.top (${this.top})`); 1487 | } 1488 | if (this.near > this.far) { 1489 | throw new RangeError(`perspective.near (${this.near}) cannot be greater than perspective.far (${this.far})`); 1490 | } 1491 | const c0r0 = 2 * this.near / (this.right - this.left); 1492 | const c1r1 = 2 * this.near / (this.top - this.bottom); 1493 | const c2r0 = (this.right + this.left) / (this.right - this.left); 1494 | const c2r1 = (this.top + this.bottom) / (this.top - this.bottom); 1495 | const c2r2 = -(this.far + this.near) / (this.far - this.near); 1496 | const c2r3 = -1; 1497 | const c3r2 = -(2 * this.far * this.near) / (this.far - this.near); 1498 | return Matrix4.from(c0r0, 0, 0, 0, 0, c1r1, 0, 0, c2r0, c2r1, c2r2, c2r3, 0, 0, c3r2, 0); 1499 | } 1500 | } 1501 | class PerspectiveFov { 1502 | fovy; 1503 | aspect; 1504 | near; 1505 | far; 1506 | constructor(fovy, aspect, near, far){ 1507 | fovy = fovy.toRad(); 1508 | this.fovy = fovy; 1509 | this.aspect = aspect; 1510 | this.near = near; 1511 | this.far = far; 1512 | } 1513 | toPerspective() { 1514 | const angle = this.fovy.div(2); 1515 | const ymax = this.near * angle.tan(); 1516 | const xmax = ymax * this.aspect; 1517 | return new Perspective(-xmax, xmax, -ymax, ymax, this.near, this.far); 1518 | } 1519 | toMatrix4() { 1520 | if (this.fovy.value < 0) { 1521 | throw new RangeError(`The vertical field of view cannot be below zero, found ${this.fovy.toString()}`); 1522 | } 1523 | if (this.fovy.value > Rad.turn / 2) { 1524 | throw new RangeError(`The vertical field of view cannot be greater than a half turn, found ${this.fovy.toString()}`); 1525 | } 1526 | if (absDiffEq(Math.abs(this.aspect), 0)) { 1527 | throw new RangeError(`The absolute aspect ratio cannot be zero, found ${Math.abs(this.aspect)}`); 1528 | } 1529 | if (this.near < 0) { 1530 | throw new RangeError(`The near plane distance cannot be below zero, found ${this.near}`); 1531 | } 1532 | if (this.far < 0) { 1533 | throw new RangeError(`The far plane distance cannot be below zero, found ${this.far}`); 1534 | } 1535 | if (absDiffEq(this.far, this.near)) { 1536 | throw new RangeError(`The far plane (${this.far}) and near plane (${this.near}) are too close`); 1537 | } 1538 | const f = this.fovy.div(2).cot(); 1539 | const c0r0 = f / this.aspect; 1540 | const c1r1 = f; 1541 | const c2r2 = (this.far + this.near) / (this.far - this.near); 1542 | const c2r3 = -1; 1543 | const c3r2 = 2 * this.far * this.near / (this.near - this.far); 1544 | return Matrix4.from(c0r0, 0, 0, 0, 0, c1r1, 0, 0, 0, 0, c2r2, c2r3, 0, 0, c3r2, 0); 1545 | } 1546 | } 1547 | class Orthographic { 1548 | left; 1549 | right; 1550 | bottom; 1551 | top; 1552 | near; 1553 | far; 1554 | constructor(left, right, bottom, top, near, far){ 1555 | this.left = left; 1556 | this.right = right; 1557 | this.bottom = bottom; 1558 | this.top = top; 1559 | this.near = near; 1560 | this.far = far; 1561 | } 1562 | toMatrix4() { 1563 | const c0r0 = 2 / (this.right - this.left); 1564 | const c1r1 = 2 / (this.top - this.bottom); 1565 | const c2r2 = -2 / (this.far - this.near); 1566 | const c3r0 = -(this.right + this.left) / (this.right - this.left); 1567 | const c3r1 = -(this.top + this.bottom) / (this.top - this.bottom); 1568 | const c3r2 = -(this.far + this.near) / (this.far - this.near); 1569 | return Matrix4.from(c0r0, 0, 0, 0, 0, c1r1, 0, 0, 0, 0, c2r2, 0, c3r0, c3r1, c3r2, 1); 1570 | } 1571 | } 1572 | export { Perspective as Perspective }; 1573 | export { PerspectiveFov as PerspectiveFov }; 1574 | export { Orthographic as Orthographic }; 1575 | class Quaternion { 1576 | scalar; 1577 | vector; 1578 | static zero() { 1579 | return new Quaternion(0, Vector3.zero()); 1580 | } 1581 | static one() { 1582 | return new Quaternion(1, Vector3.one()); 1583 | } 1584 | static fromArc(src, dst, fallback) { 1585 | const avgMag = Math.sqrt(src.mag2() * dst.mag2()); 1586 | const dot = src.dot(dst); 1587 | if (dot === avgMag) { 1588 | return Quaternion.one(); 1589 | } 1590 | if (dot === -avgMag) { 1591 | let axis = fallback; 1592 | if (axis === undefined) { 1593 | let vector = Vector3.up().cross(src); 1594 | if (vector.eq(Vector3.zero())) { 1595 | vector = Vector3.right().cross(src); 1596 | } 1597 | axis = vector.normal(); 1598 | } 1599 | return Quaternion.fromAxisAngle(axis, new Rad(Rad.turn / 2)); 1600 | } 1601 | return new Quaternion(avgMag + dot, src.cross(dst).normal()); 1602 | } 1603 | static fromAxisAngle(axis, angle) { 1604 | const [s, c] = angle.div(2).sincos(); 1605 | return new Quaternion(c, axis.mul(s)); 1606 | } 1607 | static fromMatrix3(matrix) { 1608 | const trace = matrix.trace(); 1609 | if (trace >= 0) { 1610 | let s = Math.sqrt(1 + trace); 1611 | const w10 = 0.5 * s; 1612 | s = 0.5 / s; 1613 | const x = (matrix[1][2] - matrix[2][1]) * s; 1614 | const y = (matrix[2][0] - matrix[0][2]) * s; 1615 | const z = (matrix[0][1] - matrix[1][0]) * s; 1616 | return new Quaternion(w10, new Vector3(x, y, z)); 1617 | } 1618 | if (matrix[0][0] > matrix[1][1] && matrix[0][0] > matrix[2][2]) { 1619 | let s = Math.sqrt(matrix[0][0] - matrix[1][1] - matrix[2][2] + 1); 1620 | const x = 0.5 * s; 1621 | s = 0.5 / s; 1622 | const y = (matrix[1][0] + matrix[0][1]) * s; 1623 | const z = (matrix[0][2] + matrix[2][0]) * s; 1624 | const w11 = (matrix[1][2] - matrix[2][1]) * s; 1625 | return new Quaternion(w11, new Vector3(x, y, z)); 1626 | } 1627 | if (matrix[1][1] > matrix[2][2]) { 1628 | let s = Math.sqrt(matrix[1][1] - matrix[0][0] - matrix[2][2] + 1); 1629 | const y = 0.5 * s; 1630 | s = 0.5 / s; 1631 | const z = (matrix[2][1] + matrix[1][2]) * s; 1632 | const x = (matrix[1][0] + matrix[0][1]) * s; 1633 | const w12 = (matrix[2][0] - matrix[0][2]) * s; 1634 | return new Quaternion(w12, new Vector3(x, y, z)); 1635 | } 1636 | let s = Math.sqrt(matrix[2][2] - matrix[0][0] - matrix[1][1] + 1); 1637 | const z = 0.5 * s; 1638 | s = 0.5 / s; 1639 | const x = (matrix[0][2] + matrix[2][0]) * s; 1640 | const y = (matrix[2][1] + matrix[1][2]) * s; 1641 | const w13 = (matrix[0][1] - matrix[1][0]) * s; 1642 | return new Quaternion(w13, new Vector3(x, y, z)); 1643 | } 1644 | static lookAt(dir, up) { 1645 | return Quaternion.fromMatrix3(Matrix3.lookToLh(dir, up)); 1646 | } 1647 | static between(a, b) { 1648 | const kCosTheta = a.dot(b); 1649 | if (kCosTheta === 1) { 1650 | return Quaternion.one(); 1651 | } 1652 | const k = Math.sqrt(a.mag2() * b.mag2()); 1653 | if (kCosTheta / k === -1) { 1654 | let orthogonal = a.cross(Vector3.right()); 1655 | if (orthogonal.mag2() === 0) { 1656 | orthogonal = a.cross(Vector3.up()); 1657 | } 1658 | return new Quaternion(0, orthogonal.normal()); 1659 | } 1660 | return new Quaternion(k + kCosTheta, a.cross(b)).normal(); 1661 | } 1662 | constructor(scalar, vector){ 1663 | this.scalar = scalar ?? 0; 1664 | this.vector = vector ?? Vector3.zero(); 1665 | } 1666 | clone() { 1667 | return new Quaternion(this.scalar, this.vector); 1668 | } 1669 | mag() { 1670 | return Math.hypot(this.scalar, this.vector.x, this.vector.y, this.vector.z); 1671 | } 1672 | mag2() { 1673 | return this.scalar ** 2 + this.vector.x ** 2 + this.vector.y ** 2 + this.vector.z ** 2; 1674 | } 1675 | normal() { 1676 | return this.div(this.mag()); 1677 | } 1678 | dot(other) { 1679 | return this.scalar * other.scalar + this.vector.dot(other.vector); 1680 | } 1681 | conjugate() { 1682 | return new Quaternion(this.scalar, this.vector.neg()); 1683 | } 1684 | invert() { 1685 | return this.conjugate().div(this.mag2()); 1686 | } 1687 | rot(vector) { 1688 | const tmp = this.vector.cross(vector).add(vector.mul(this.scalar)); 1689 | return this.vector.cross(tmp).mul(2).add(vector); 1690 | } 1691 | nlerp(other, alpha) { 1692 | if (this.dot(other) < 0) { 1693 | other = other.neg(); 1694 | } 1695 | return this.mul(1 - alpha).add(other.mul(alpha)).normal(); 1696 | } 1697 | slerp(other, alpha) { 1698 | let dot = this.dot(other); 1699 | if (dot < 0) { 1700 | other = other.neg(); 1701 | dot = -dot; 1702 | } 1703 | if (dot > 0.9995) { 1704 | return this.nlerp(other, alpha); 1705 | } 1706 | const robustDot = Math.max(Math.min(dot, 1), -1); 1707 | const theta = new Rad(robustDot).acos(); 1708 | const scale1 = new Rad(theta * (1 - alpha)).sin(); 1709 | const scale2 = new Rad(theta * alpha).sin(); 1710 | return this.mul(scale1).add(other.mul(scale2)).normal(); 1711 | } 1712 | set(scalar, vector) { 1713 | this.scalar = scalar; 1714 | this.vector = vector; 1715 | return this; 1716 | } 1717 | add(other) { 1718 | const { vector , scalar } = typeof other === "number" ? { 1719 | vector: new Vector3(other), 1720 | scalar: other 1721 | } : other; 1722 | return new Quaternion(this.scalar + scalar, this.vector.add(vector)); 1723 | } 1724 | sub(other) { 1725 | const { vector , scalar } = typeof other === "number" ? { 1726 | vector: new Vector3(other), 1727 | scalar: other 1728 | } : other; 1729 | return new Quaternion(this.scalar - scalar, this.vector.sub(vector)); 1730 | } 1731 | mul(other) { 1732 | if (typeof other === "number") { 1733 | return new Quaternion(this.scalar * other, this.vector.mul(other)); 1734 | } 1735 | return new Quaternion(this.scalar * other.scalar - this.vector.x * other.vector.x - this.vector.y * other.vector.y - this.vector.z * other.vector.z, new Vector3(this.scalar * other.vector.x + this.vector.x * other.scalar + this.vector.y * other.vector.z - this.vector.z * other.vector.y, this.scalar * other.vector.y + this.vector.y * other.scalar + this.vector.z * other.vector.x - this.vector.x * other.vector.z, this.scalar * other.vector.z + this.vector.z * other.scalar + this.vector.x * other.vector.y - this.vector.y * other.vector.x)); 1736 | } 1737 | div(other) { 1738 | return new Quaternion(this.scalar / other, this.vector.div(other)); 1739 | } 1740 | neg() { 1741 | return new Quaternion(-this.scalar, this.vector.neg()); 1742 | } 1743 | eq(other) { 1744 | return this.scalar === other.scalar && this.vector.eq(other.vector); 1745 | } 1746 | is_finite() { 1747 | return isFinite(this.scalar) && this.vector.isFinite(); 1748 | } 1749 | toString() { 1750 | return `Quaternion { scalar: ${this.scalar}, vector: ${this.vector.toString()} }`; 1751 | } 1752 | } 1753 | export { Quaternion as Quaternion }; 1754 | -------------------------------------------------------------------------------- /examples/extern/gpu_err.bundle.js: -------------------------------------------------------------------------------- 1 | function patchPushError(proto, throwError) { 2 | const _pushError = proto.pushError; 3 | proto.pushError = function(error) { 4 | if (error !== null && error.type === "validation") { 5 | const err = new Error(error.value ?? "unknown"); 6 | err.name = "WebGPUValidationError"; 7 | if (throwError) throw err; 8 | else console.error(err); 9 | } 10 | _pushError.call(proto, error); 11 | }; 12 | } 13 | function enableValidationErrors(device, throwError = false) { 14 | const _InnerDevice = Object.getOwnPropertySymbols(device).find((e)=>e.description === "[[device]]" 15 | ); 16 | const innerDevice = device[_InnerDevice]; 17 | patchPushError(innerDevice, throwError); 18 | } 19 | export { enableValidationErrors as enableValidationErrors }; 20 | -------------------------------------------------------------------------------- /examples/hello_camera.js: -------------------------------------------------------------------------------- 1 | import { App, OPENGL_TO_WGPU_MATRIX } from "./common.js"; 2 | import { decode } from "./extern/pngs.bundle.js"; 3 | import { Vector3, Matrix4, PerspectiveFov, Deg } from "./extern/gmath.bundle.js"; 4 | 5 | export class CameraController { 6 | speed = 0; 7 | isLeftPressed = false; 8 | isRightPressed = false; 9 | isForwardPressed = false; 10 | isBackwardPressed = false; 11 | 12 | constructor(speed) { 13 | this.speed = speed; 14 | } 15 | 16 | onEvent(event) { 17 | if (event.type === "windowEvent") { 18 | event = event.event; 19 | if (event.type === "keyboardInput") { 20 | const pressed = event.input.state === "pressed"; 21 | 22 | switch (event.input.keyCode) { 23 | case 57416: // up 24 | this.isForwardPressed = pressed; 25 | break; 26 | 27 | case 57424: // down 28 | this.isBackwardPressed = pressed; 29 | break; 30 | 31 | case 57419: // left 32 | this.isLeftPressed = pressed; 33 | break; 34 | 35 | case 57421: // right 36 | this.isRightPressed = pressed; 37 | break; 38 | } 39 | } 40 | } 41 | } 42 | 43 | /** @param {Camera} camera */ 44 | updateCamera(camera) { 45 | let forward = camera.target.sub(camera.eye); 46 | const forwardNorm = forward.normal(); 47 | let forwardMag = forward.mag(); 48 | 49 | if (this.isForwardPressed && forwardMag > this.speed) { 50 | camera.eye = camera.eye.add(forwardNorm.mul(this.speed)); 51 | } 52 | 53 | if (this.isBackwardPressed) { 54 | camera.eye = camera.eye.sub(forwardNorm.mul(this.speed)); 55 | } 56 | 57 | const right = forwardNorm.cross(camera.up); 58 | 59 | forward = camera.target.sub(camera.eye); 60 | forwardMag = forward.mag(); 61 | 62 | if (this.isRightPressed) { 63 | camera.eye = camera.target.sub(forward.add(right.mul(this.speed)).normal().mul(forwardMag)); 64 | } 65 | 66 | if (this.isLeftPressed) { 67 | camera.eye = camera.target.sub(forward.sub(right.mul(this.speed)).normal().mul(forwardMag)); 68 | } 69 | } 70 | } 71 | 72 | export class Camera { 73 | eye = new Vector3(0, 1, 2); 74 | target = new Vector3(0, 0, 0); 75 | up = Vector3.up(); 76 | aspect = 0; 77 | fovy = 45.0; 78 | znear = 0.1; 79 | zfar = 100.0; 80 | 81 | constructor(width, height) { 82 | this.aspect = width / height; 83 | } 84 | 85 | buildViewProjMatrix() { 86 | const view = Matrix4.lookAtRh(this.eye, this.target, this.up); 87 | const proj = new PerspectiveFov(new Deg(this.fovy), this.aspect, this.znear, this.zfar) 88 | .toPerspective() 89 | .toMatrix4(); 90 | return OPENGL_TO_WGPU_MATRIX.mul(proj.mul(view)).toFloat32Array(); 91 | } 92 | } 93 | 94 | export class HelloCameraApp extends App { 95 | constructor() { 96 | super("Hello Camera"); 97 | 98 | const vertices = [ 99 | [-0.0868241, 0.49240386, 0], 100 | [-0.49513406, 0.06958647, 0], 101 | [-0.21918549, -0.44939706, 0], 102 | [0.35966998, -0.3473291, 0], 103 | [0.44147372, 0.2347359, 0], 104 | ]; 105 | 106 | const texCoords = [ 107 | [0.4131759, 0.00759614], 108 | [0.0048659444, 0.43041354], 109 | [0.28081453, 0.949397], 110 | [0.85967, 0.84732914], 111 | [0.9414737, 0.2652641], 112 | ]; 113 | 114 | const indices = [ 115 | 0, 1, 4, 116 | 1, 2, 4, 117 | 2, 3, 4, 118 | 0, 119 | ]; 120 | 121 | this.vertices = new Float32Array(vertices.length * vertices[0].length + texCoords.length * texCoords[0].length); 122 | for (let i = 0; i < vertices.length; i++) { 123 | const vertex = vertices[i], texCoord = texCoords[i]; 124 | const offset = i * vertex.length + i * texCoord.length; 125 | this.vertices.set(vertex, offset); 126 | this.vertices.set(texCoord, offset + vertex.length); 127 | } 128 | 129 | this.indices = new Uint16Array(indices); 130 | 131 | this.textureImage = decode(Deno.readFileSync(new URL("./extern/deno.png", import.meta.url))); 132 | 133 | this.camera = new Camera(this.width, this.height); 134 | this.cameraUniform = new Float32Array(4 * 4); 135 | this.updateViewProjMatrix(); 136 | this.cameraController = new CameraController(0.2); 137 | } 138 | 139 | onEvent(event) { 140 | this.cameraController.onEvent(event); 141 | } 142 | 143 | updateViewProjMatrix() { 144 | const viewProjMatrix = this.camera.buildViewProjMatrix(); 145 | this.cameraUniform.set(viewProjMatrix, 0); 146 | } 147 | 148 | async init() { 149 | const shaderModule = await this.loadShader("hello_camera"); 150 | 151 | this.vertexBuffer = this.createBuffer({ 152 | label: "Vertex Buffer", 153 | data: this.vertices, 154 | usage: GPUBufferUsage.VERTEX, 155 | }); 156 | 157 | this.indexBuffer = this.createBuffer({ 158 | label: "Index Buffer", 159 | data: this.indices, 160 | usage: GPUBufferUsage.INDEX, 161 | }); 162 | 163 | this.cameraBuffer = this.createBuffer({ 164 | label: "Camera Buffer", 165 | data: this.cameraUniform, 166 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 167 | }); 168 | 169 | this.texture = this.device.createTexture({ 170 | label: "Tree Texture", 171 | size: { 172 | width: this.textureImage.width, 173 | height: this.textureImage.height, 174 | depthOrArrayLayers: 1, 175 | }, 176 | mipLevelCount: 1, 177 | sampleCount: 1, 178 | dimension: "2d", 179 | format: "rgba8unorm-srgb", 180 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, 181 | }); 182 | 183 | this.device.queue.writeTexture({ 184 | texture: this.texture, 185 | }, this.textureImage.image, { 186 | offset: 0, 187 | bytesPerRow: this.textureImage.width * 4, 188 | rowsPerImage: this.textureImage.height, 189 | }, { 190 | width: this.textureImage.width, 191 | height: this.textureImage.height, 192 | depthOrArrayLayers: 1, 193 | }); 194 | 195 | this.textureView = this.texture.createView(); 196 | this.textureSampler = this.device.createSampler({ 197 | label: "Tree Texture Sampler", 198 | addressModeU: "clamp-to-edge", 199 | addressModeV: "clamp-to-edge", 200 | addressModeW: "clamp-to-edge", 201 | magFilter: "linear", 202 | minFilter: "nearest", 203 | mipmapFilter: "nearest", 204 | }); 205 | 206 | this.texBindGroupLayout = this.device.createBindGroupLayout({ 207 | label: "Texture Bind Group Layout", 208 | entries: [ 209 | { 210 | binding: 0, 211 | visibility: GPUShaderStage.FRAGMENT, 212 | texture: { 213 | multisampled: false, 214 | viewDimension: "2d", 215 | sampleType: "float", 216 | }, 217 | }, 218 | { 219 | binding: 1, 220 | visibility: GPUShaderStage.FRAGMENT, 221 | sampler: { 222 | type: "filtering", 223 | }, 224 | }, 225 | ], 226 | }); 227 | 228 | this.texBindGroup = this.device.createBindGroup({ 229 | label: "Texture Bind Group", 230 | layout: this.texBindGroupLayout, 231 | entries: [ 232 | { 233 | binding: 0, 234 | resource: this.textureView, 235 | }, 236 | { 237 | binding: 1, 238 | resource: this.textureSampler, 239 | }, 240 | ], 241 | }); 242 | 243 | this.cameraBindGroupLayout = this.device.createBindGroupLayout({ 244 | entries: [ 245 | { 246 | binding: 0, 247 | buffer: { 248 | type: "uniform", 249 | }, 250 | visibility: GPUShaderStage.VERTEX, 251 | }, 252 | ], 253 | }); 254 | 255 | this.cameraBindGroup = this.device.createBindGroup({ 256 | layout: this.cameraBindGroupLayout, 257 | entries: [ 258 | { 259 | binding: 0, 260 | resource: { 261 | buffer: this.cameraBuffer, 262 | }, 263 | }, 264 | ], 265 | }); 266 | 267 | this.pipelineLayout = this.device.createPipelineLayout({ 268 | label: "Pipeline Layout", 269 | bindGroupLayouts: [this.texBindGroupLayout, this.cameraBindGroupLayout], 270 | }); 271 | 272 | this.renderPipeline = this.device.createRenderPipeline({ 273 | layout: this.pipelineLayout, 274 | vertex: { 275 | module: shaderModule, 276 | entryPoint: "vs_main", 277 | buffers: [ 278 | { 279 | arrayStride: 4 * 3 + 4 * 2, 280 | stepMode: "vertex", 281 | attributes: [ 282 | { 283 | offset: 0, 284 | shaderLocation: 0, 285 | format: "float32x3", 286 | }, 287 | { 288 | offset: 4 * 3, 289 | shaderLocation: 1, 290 | format: "float32x2", 291 | }, 292 | ], 293 | }, 294 | ], 295 | }, 296 | fragment: { 297 | module: shaderModule, 298 | entryPoint: "fs_main", 299 | targets: [ 300 | { 301 | format: this.format, 302 | }, 303 | ], 304 | }, 305 | }); 306 | } 307 | 308 | render(encoder, view) { 309 | this.cameraController.updateCamera(this.camera); 310 | this.updateViewProjMatrix(); 311 | this.device.queue.writeBuffer(this.cameraBuffer, 0, this.cameraUniform); 312 | 313 | const renderPass = encoder.beginRenderPass({ 314 | colorAttachments: [ 315 | { 316 | view, 317 | storeOp: "store", 318 | loadValue: [0.1, 0.2, 0.3, 1], 319 | }, 320 | ], 321 | }); 322 | 323 | renderPass.setPipeline(this.renderPipeline); 324 | renderPass.setBindGroup(0, this.texBindGroup); 325 | renderPass.setBindGroup(1, this.cameraBindGroup); 326 | renderPass.setVertexBuffer(0, this.vertexBuffer); 327 | renderPass.setIndexBuffer(this.indexBuffer, "uint16"); 328 | renderPass.drawIndexed(this.indices.length, 1); 329 | renderPass.endPass(); 330 | } 331 | } 332 | 333 | if (import.meta.main) { 334 | const app = new HelloCameraApp(); 335 | await app.run(); 336 | } 337 | -------------------------------------------------------------------------------- /examples/hello_texture.js: -------------------------------------------------------------------------------- 1 | import { App } from "./common.js"; 2 | import { decode } from "./extern/pngs.bundle.js"; 3 | 4 | export class HelloTextureApp extends App { 5 | constructor() { 6 | super("Hello Texture"); 7 | 8 | const vertices = [ 9 | [-0.0868241, 0.49240386, 0], 10 | [-0.49513406, 0.06958647, 0], 11 | [-0.21918549, -0.44939706, 0], 12 | [0.35966998, -0.3473291, 0], 13 | [0.44147372, 0.2347359, 0], 14 | ]; 15 | 16 | const texCoords = [ 17 | [0.4131759, 0.00759614], 18 | [0.0048659444, 0.43041354], 19 | [0.28081453, 0.949397], 20 | [0.85967, 0.84732914], 21 | [0.9414737, 0.2652641], 22 | ]; 23 | 24 | const indices = [ 25 | 0, 1, 4, 26 | 1, 2, 4, 27 | 2, 3, 4, 28 | 0, 29 | ]; 30 | 31 | this.vertices = new Float32Array(vertices.length * vertices[0].length + texCoords.length * texCoords[0].length); 32 | for (let i = 0; i < vertices.length; i++) { 33 | const vertex = vertices[i], texCoord = texCoords[i]; 34 | const offset = i * vertex.length + i * texCoord.length; 35 | this.vertices.set(vertex, offset); 36 | this.vertices.set(texCoord, offset + vertex.length); 37 | } 38 | 39 | this.indices = new Uint16Array(indices); 40 | 41 | this.textureImage = decode(Deno.readFileSync(new URL("./extern/tree.png", import.meta.url))); 42 | } 43 | 44 | async init() { 45 | const shaderModule = await this.loadShader("hello_texture"); 46 | 47 | this.vertexBuffer = this.createBuffer({ 48 | label: "Vertex Buffer", 49 | data: this.vertices, 50 | usage: GPUBufferUsage.VERTEX, 51 | }); 52 | 53 | this.indexBuffer = this.createBuffer({ 54 | label: "Index Buffer", 55 | data: this.indices, 56 | usage: GPUBufferUsage.INDEX, 57 | }); 58 | 59 | this.texture = this.device.createTexture({ 60 | label: "Tree Texture", 61 | size: { 62 | width: this.textureImage.width, 63 | height: this.textureImage.height, 64 | depthOrArrayLayers: 1, 65 | }, 66 | mipLevelCount: 1, 67 | sampleCount: 1, 68 | dimension: "2d", 69 | format: "rgba8unorm-srgb", 70 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, 71 | }); 72 | 73 | this.device.queue.writeTexture({ 74 | texture: this.texture, 75 | }, this.textureImage.image, { 76 | offset: 0, 77 | bytesPerRow: this.textureImage.width * 4, 78 | rowsPerImage: this.textureImage.height, 79 | }, { 80 | width: this.textureImage.width, 81 | height: this.textureImage.height, 82 | depthOrArrayLayers: 1, 83 | }); 84 | 85 | this.textureView = this.texture.createView(); 86 | this.textureSampler = this.device.createSampler({ 87 | label: "Tree Texture Sampler", 88 | addressModeU: "clamp-to-edge", 89 | addressModeV: "clamp-to-edge", 90 | addressModeW: "clamp-to-edge", 91 | magFilter: "linear", 92 | minFilter: "nearest", 93 | mipmapFilter: "nearest", 94 | }); 95 | 96 | this.texBindGroupLayout = this.device.createBindGroupLayout({ 97 | label: "Texture Bind Group Layout", 98 | entries: [ 99 | { 100 | binding: 0, 101 | visibility: GPUShaderStage.FRAGMENT, 102 | texture: { 103 | multisampled: false, 104 | viewDimension: "2d", 105 | sampleType: "float", 106 | }, 107 | }, 108 | { 109 | binding: 1, 110 | visibility: GPUShaderStage.FRAGMENT, 111 | sampler: { 112 | type: "filtering", 113 | }, 114 | }, 115 | ], 116 | }); 117 | 118 | this.texBindGroup = this.device.createBindGroup({ 119 | label: "Texture Bind Group", 120 | layout: this.texBindGroupLayout, 121 | entries: [ 122 | { 123 | binding: 0, 124 | resource: this.textureView, 125 | }, 126 | { 127 | binding: 1, 128 | resource: this.textureSampler, 129 | }, 130 | ], 131 | }); 132 | 133 | this.pipelineLayout = this.device.createPipelineLayout({ 134 | label: "Pipeline Layout", 135 | bindGroupLayouts: [this.texBindGroupLayout], 136 | }); 137 | 138 | this.renderPipeline = this.device.createRenderPipeline({ 139 | layout: this.pipelineLayout, 140 | vertex: { 141 | module: shaderModule, 142 | entryPoint: "vs_main", 143 | buffers: [ 144 | { 145 | arrayStride: 4 * 3 + 4 * 2, 146 | stepMode: "vertex", 147 | attributes: [ 148 | { 149 | offset: 0, 150 | shaderLocation: 0, 151 | format: "float32x3", 152 | }, 153 | { 154 | offset: 4 * 3, 155 | shaderLocation: 1, 156 | format: "float32x2", 157 | }, 158 | ], 159 | }, 160 | ], 161 | }, 162 | fragment: { 163 | module: shaderModule, 164 | entryPoint: "fs_main", 165 | targets: [ 166 | { 167 | format: this.format, 168 | }, 169 | ], 170 | }, 171 | }); 172 | } 173 | 174 | render(encoder, view) { 175 | const renderPass = encoder.beginRenderPass({ 176 | colorAttachments: [ 177 | { 178 | view, 179 | storeOp: "store", 180 | loadValue: [0, 0, 0, 1], 181 | }, 182 | ], 183 | }); 184 | 185 | renderPass.setPipeline(this.renderPipeline); 186 | renderPass.setBindGroup(0, this.texBindGroup); 187 | renderPass.setVertexBuffer(0, this.vertexBuffer); 188 | renderPass.setIndexBuffer(this.indexBuffer, "uint16"); 189 | renderPass.drawIndexed(this.indices.length, 1); 190 | renderPass.endPass(); 191 | } 192 | } 193 | 194 | if (import.meta.main) { 195 | const app = new HelloTextureApp(); 196 | await app.run(); 197 | } 198 | -------------------------------------------------------------------------------- /examples/hello_triangle.js: -------------------------------------------------------------------------------- 1 | import { App } from "./common.js"; 2 | 3 | export class HelloTriangleApp extends App { 4 | constructor() { 5 | super("Hello Triangle"); 6 | 7 | const vertices = [ 8 | [ 0, 0.5, 0], 9 | [ 0.5, -0.5, 0], 10 | [-0.5, -0.5, 0], 11 | ]; 12 | 13 | const colors = [ 14 | [1, 0, 0, 1], 15 | [0, 1, 0, 1], 16 | [0, 0, 1, 1], 17 | ]; 18 | 19 | const indices = [ 20 | 0, 1, 2, 21 | ]; 22 | 23 | this.vertices = new Float32Array(vertices.length * 3 + colors.length * 4); 24 | for (let i = 0; i < vertices.length; i++) { 25 | const vertex = vertices[i], color = colors[i]; 26 | const offset = i * vertex.length + i * color.length; 27 | this.vertices.set(vertex, offset); 28 | this.vertices.set(color, offset + vertex.length); 29 | } 30 | 31 | this.indices = new Uint16Array(indices); 32 | } 33 | 34 | async init() { 35 | const shaderModule = await this.loadShader("hello_triangle"); 36 | 37 | this.vertexBuffer = this.createBuffer({ 38 | label: "Vertex Buffer", 39 | data: this.vertices, 40 | usage: GPUBufferUsage.VERTEX, 41 | }); 42 | 43 | this.indexBuffer = this.createBuffer({ 44 | label: "Index Buffer", 45 | data: this.indices, 46 | usage: GPUBufferUsage.INDEX, 47 | }); 48 | 49 | this.renderPipeline = this.device.createRenderPipeline({ 50 | vertex: { 51 | module: shaderModule, 52 | entryPoint: "vs_main", 53 | buffers: [ 54 | { 55 | arrayStride: 4 * 3 + 4 * 4, 56 | stepMode: "vertex", 57 | attributes: [ 58 | { 59 | offset: 0, 60 | shaderLocation: 0, 61 | format: "float32x3", 62 | }, 63 | { 64 | offset: 4 * 3, 65 | shaderLocation: 1, 66 | format: "float32x4", 67 | }, 68 | ], 69 | }, 70 | ], 71 | }, 72 | fragment: { 73 | module: shaderModule, 74 | entryPoint: "fs_main", 75 | targets: [ 76 | { 77 | format: this.format, 78 | }, 79 | ], 80 | }, 81 | }); 82 | } 83 | 84 | render(encoder, view) { 85 | const renderPass = encoder.beginRenderPass({ 86 | colorAttachments: [ 87 | { 88 | view, 89 | storeOp: "store", 90 | loadValue: [0, 0, 0, 1], 91 | }, 92 | ], 93 | }); 94 | 95 | renderPass.setPipeline(this.renderPipeline); 96 | renderPass.setVertexBuffer(0, this.vertexBuffer); 97 | renderPass.setIndexBuffer(this.indexBuffer, "uint16"); 98 | renderPass.drawIndexed(this.indices.length, 1); 99 | renderPass.endPass(); 100 | } 101 | } 102 | 103 | if (import.meta.main) { 104 | const app = new HelloTriangleApp(); 105 | await app.run(); 106 | } 107 | -------------------------------------------------------------------------------- /examples/shaders/hello_camera.wgsl: -------------------------------------------------------------------------------- 1 | [[block]] 2 | struct CameraUniform { 3 | view_proj: mat4x4; 4 | }; 5 | 6 | [[group(1), binding(0)]] 7 | var camera: CameraUniform; 8 | 9 | struct VertexInput { 10 | [[location(0)]] position: vec3; 11 | [[location(1)]] tex_coords: vec2; 12 | }; 13 | 14 | struct VertexOutput { 15 | [[builtin(position)]] clip_position: vec4; 16 | [[location(0)]] tex_coords: vec2; 17 | }; 18 | 19 | [[stage(vertex)]] 20 | fn vs_main(in: VertexInput) -> VertexOutput { 21 | var out: VertexOutput; 22 | out.tex_coords = in.tex_coords; 23 | out.clip_position = camera.view_proj * vec4(in.position, 1.0); 24 | return out; 25 | } 26 | 27 | [[group(0), binding(0)]] 28 | var t_diffuse: texture_2d; 29 | [[group(0), binding(1)]] 30 | var s_diffuse: sampler; 31 | 32 | [[stage(fragment)]] 33 | fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { 34 | return textureSample(t_diffuse, s_diffuse, in.tex_coords); 35 | } 36 | -------------------------------------------------------------------------------- /examples/shaders/hello_texture.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | [[location(0)]] position: vec3; 3 | [[location(1)]] tex_coords: vec2; 4 | }; 5 | 6 | struct VertexOutput { 7 | [[builtin(position)]] clip_position: vec4; 8 | [[location(0)]] tex_coords: vec2; 9 | }; 10 | 11 | [[stage(vertex)]] 12 | fn vs_main(in: VertexInput) -> VertexOutput { 13 | var out: VertexOutput; 14 | out.tex_coords = in.tex_coords; 15 | out.clip_position = vec4(in.position, 1.0); 16 | return out; 17 | } 18 | 19 | [[group(0), binding(0)]] 20 | var t_diffuse: texture_2d; 21 | [[group(0), binding(1)]] 22 | var s_diffuse: sampler; 23 | 24 | [[stage(fragment)]] 25 | fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { 26 | return textureSample(t_diffuse, s_diffuse, in.tex_coords); 27 | } 28 | -------------------------------------------------------------------------------- /examples/shaders/hello_triangle.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | [[location(0)]] pos: vec3; 3 | [[location(1)]] color: vec4; 4 | }; 5 | 6 | struct VertexOutput { 7 | [[builtin(position)]] pos: vec4; 8 | [[location(0)]] color: vec4; 9 | }; 10 | 11 | [[stage(vertex)]] 12 | fn vs_main(input: VertexInput) -> VertexOutput { 13 | var out: VertexOutput; 14 | out.color = input.color; 15 | out.pos = vec4(input.pos, 1.0); 16 | return out; 17 | } 18 | 19 | [[stage(fragment)]] 20 | fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { 21 | return in.color; 22 | } 23 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | Deno.nextEvent = function nextEvent() { 2 | const promise = Deno.core.opAsync("op_next_event"); 3 | // Unref the op so that it does not keep event loop alive. 4 | Deno.core.unrefOp(promise[Symbol.for("Deno.core.internalPromiseId")]); 5 | return promise; 6 | } 7 | 8 | Deno.eventLoop = async function* eventLoop() { 9 | let event; 10 | while ((event = (await Deno.nextEvent()))) { 11 | yield event; 12 | } 13 | }; 14 | 15 | Deno.createWindow = function createWindow(options) { 16 | return new WinitWindow(Deno.core.opSync("op_create_window", options)); 17 | }; 18 | 19 | let symbolCache = {}; 20 | 21 | function getSymbolOf(obj, name) { 22 | if (symbolCache[name]) { 23 | return symbolCache[name]; 24 | } 25 | const symbol = Object.getOwnPropertySymbols(obj).find((symbol) => symbol.description === name); 26 | if (!symbol) { 27 | throw new Error(`No symbol ${name} found`); 28 | } 29 | symbolCache[name] = symbol; 30 | return symbol; 31 | } 32 | 33 | function getRidOf(obj) { 34 | return obj[getSymbolOf(obj, "[[rid]]")]; 35 | } 36 | 37 | class GPUCanvasContext { 38 | #window; 39 | #rid; 40 | #rids; 41 | #baseTex; 42 | #currentTexture; 43 | 44 | constructor(window, rid, rids, device) { 45 | this.#window = window; 46 | this.#rid = rid; 47 | this.#rids = rids; 48 | 49 | if (!this.#baseTex) { 50 | this.#baseTex = device.createTexture({ 51 | size: { 52 | width: 1, 53 | height: 1, 54 | }, 55 | format: "bgra8unorm-srgb", 56 | usage: GPUTextureUsage.RENDER_ATTACHMENT, 57 | }); 58 | this.#baseTex.destroy(); 59 | } 60 | } 61 | 62 | getPreferredFormat() { 63 | return Deno.core.opSync("op_webgpu_surface_get_preferred_format", { 64 | surfaceRid: this.#rid, 65 | adapterRid: this.#rids.adapter, 66 | }); 67 | } 68 | 69 | configure(options = {}) { 70 | Deno.core.opSync("op_webgpu_configure_surface", { 71 | surfaceRid: this.#rid, 72 | deviceRid: this.#rids.device, 73 | format: options.format ?? this.getPreferredFormat(), 74 | width: options.width ?? this.#window.width, 75 | height: options.height ?? this.#window.height, 76 | usage: options.usage ?? GPUTextureUsage.RENDER_ATTACHMENT, 77 | }); 78 | this.#currentTexture = null; 79 | } 80 | 81 | present() { 82 | const status = Deno.core.opSync("op_webgpu_surface_present", { 83 | surfaceRid: this.#rid, 84 | adapterRid: this.#rids.adapter, 85 | }); 86 | this.#currentTexture = null; 87 | return status; 88 | } 89 | 90 | getCurrentTexture() { 91 | if (this.#currentTexture) return this.#currentTexture; 92 | const currentTextureRid = Deno.core.opSync( 93 | "op_webgpu_surface_get_current_texture", 94 | { 95 | surfaceRid: this.#rid, 96 | adapterRid: this.#rids.adapter, 97 | } 98 | ); 99 | this.#baseTex[getSymbolOf(this.#baseTex, "[[rid]]")] = currentTextureRid; 100 | this.#currentTexture = this.#baseTex; 101 | return this.#baseTex; 102 | } 103 | 104 | destroy() { 105 | Deno.core.opSync("op_webgpu_surface_drop", this.#rid); 106 | } 107 | } 108 | 109 | globalThis.GPUCanvasContext = GPUCanvasContext; 110 | 111 | class WinitWindow { 112 | #rid; 113 | #id; 114 | 115 | constructor([id, rid]) { 116 | this.#id = id; 117 | this.#rid = rid; 118 | } 119 | 120 | get rid() { 121 | return this.#rid; 122 | } 123 | 124 | get id() { 125 | return this.#id; 126 | } 127 | 128 | get width() { 129 | return this.getSize().width; 130 | } 131 | 132 | get height() { 133 | return this.getSize().height; 134 | } 135 | 136 | requestRedraw() { 137 | Deno.core.opSync("op_window_request_redraw", this.#rid); 138 | } 139 | 140 | requestUserAttention(type) { 141 | Deno.core.opSync("op_window_request_user_attention", [this.#rid, type ?? null]); 142 | } 143 | 144 | getFullscreen() { 145 | return Deno.core.opSync("op_window_fullscreen", this.#rid); 146 | } 147 | 148 | getInnerPosition() { 149 | return Deno.core.opSync("op_window_inner_position", this.#rid); 150 | } 151 | 152 | getSize() { 153 | return Deno.core.opSync("op_window_inner_size", this.#rid); 154 | } 155 | 156 | getOuterSize() { 157 | return Deno.core.opSync("op_window_outer_size", this.#rid); 158 | } 159 | 160 | getScaleFactor() { 161 | return Deno.core.opSync("op_window_scale_factor", this.#rid); 162 | } 163 | 164 | setAlwaysOnTop(value) { 165 | Deno.core.opSync("op_window_set_always_on_top", [this.#rid, value]); 166 | } 167 | 168 | setCursorGrab(value) { 169 | Deno.core.opSync("op_window_set_cursor_grab", [this.#rid, value]); 170 | } 171 | 172 | setCursorIcon(icon) { 173 | Deno.core.opSync("op_window_set_cursor_icon", [this.#rid, icon]); 174 | } 175 | 176 | setCursorPosition(pos) { 177 | Deno.core.opSync("op_window_set_cursor_position", [this.#rid, pos]); 178 | } 179 | 180 | setCursorVisible(value) { 181 | Deno.core.opSync("op_window_set_cursor_visible", [this.#rid, value]); 182 | } 183 | 184 | setDecorations(value) { 185 | Deno.core.opSync("op_window_set_decorations", [this.#rid, value]); 186 | } 187 | 188 | setFullscreen(fullscreen) { 189 | Deno.core.opSync("op_window_set_fullscreen", [this.#rid, fullscreen]); 190 | } 191 | 192 | setImePosition(pos) { 193 | Deno.core.opSync("op_window_set_ime_position", [this.#rid, pos]); 194 | } 195 | 196 | setSize(size) { 197 | Deno.core.opSync("op_window_set_inner_size", [this.#rid, size]); 198 | } 199 | 200 | setMaxSize({ width, height }) { 201 | Deno.core.opSync("op_window_set_max_inner_size", [this.#rid, width, height]); 202 | } 203 | 204 | setMaximized(value) { 205 | Deno.core.opSync("op_window_set_maximized", [this.#rid, value]); 206 | } 207 | 208 | setMinSize({ width, height }) { 209 | Deno.core.opSync("op_window_set_min_inner_size", [this.#rid, width, height]); 210 | } 211 | 212 | setMinimized(value) { 213 | Deno.core.opSync("op_window_set_minimized", [this.#rid, value]); 214 | } 215 | 216 | setPosition(pos) { 217 | Deno.core.opSync("op_window_set_outer_position", [this.#rid, pos]); 218 | } 219 | 220 | setResizable(value) { 221 | Deno.core.opSync("op_window_set_resizable", [this.#rid, value]); 222 | } 223 | 224 | setTitle(title) { 225 | Deno.core.opSync("op_window_set_title", [this.#rid, title]); 226 | } 227 | 228 | setVisible(value) { 229 | Deno.core.opSync("op_window_set_visible", [this.#rid, value]); 230 | } 231 | 232 | setIcon({ data, width, height }) { 233 | Deno.core.opSync("op_window_set_window_icon", [this.#rid, width, height], data); 234 | } 235 | 236 | createSurface(device) { 237 | const rid = Deno.core.opSync("op_webgpu_create_surface", { windowRid: this.#rid }); 238 | const adapter = device[getSymbolOf(device, "[[device]]")].adapter; 239 | return new GPUCanvasContext(this, rid, { 240 | device: device[getSymbolOf(device, "[[device]]")].rid, 241 | adapter: adapter[getSymbolOf(adapter, "[[adapter]]")].rid, 242 | }, device); 243 | } 244 | 245 | close() { 246 | Deno.close(this.rid); 247 | } 248 | } 249 | 250 | Deno.WinitWindow = WinitWindow; 251 | -------------------------------------------------------------------------------- /src/event_loop.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use deno_runtime::deno_core::Extension; 5 | use deno_runtime::deno_core::op_async; 6 | use deno_runtime::deno_core::OpState; 7 | use deno_runtime::deno_core::error::AnyError; 8 | use serde::Serialize; 9 | use deno_runtime::deno_core::serde_json::Value; 10 | use deno_runtime::deno_core::serde_json::json; 11 | use winit_main::Blocker; 12 | use winit_main::reexports::dpi::PhysicalPosition; 13 | use winit_main::reexports::dpi::PhysicalSize; 14 | use winit_main::reexports::event::DeviceEvent; 15 | use winit_main::reexports::event::ElementState; 16 | use winit_main::reexports::event::Event; 17 | use winit_main::reexports::event::KeyboardInput; 18 | use winit_main::reexports::event::MouseButton; 19 | use winit_main::reexports::event::MouseScrollDelta; 20 | use winit_main::reexports::event::StartCause; 21 | use winit_main::reexports::event::TouchPhase; 22 | use winit_main::reexports::event::WindowEvent; 23 | use winit_main::reexports::window::Theme; 24 | use crate::util::hash; 25 | use crate::EVENT_RECEIVER; 26 | 27 | pub fn serialize_physical_size(size: PhysicalSize) -> Value { 28 | json!({ "width": size.width, "height": size.height }) 29 | } 30 | 31 | pub fn serialize_physical_position(pos: PhysicalPosition) -> Value { 32 | json!({ "x": pos.x, "y": pos.y }) 33 | } 34 | 35 | fn serialize_keyboard_input(input: KeyboardInput) -> Value { 36 | json!({ 37 | "keyCode": input.scancode, 38 | "state": match input.state { 39 | ElementState::Pressed => "pressed", 40 | ElementState::Released => "released" 41 | }, 42 | }) 43 | } 44 | 45 | fn serialize_mouse_scroll_delta(delta: MouseScrollDelta) -> Value { 46 | match delta { 47 | MouseScrollDelta::LineDelta(x, y) => json!({ "type": "lineDelta", "x": x, "y": y }), 48 | MouseScrollDelta::PixelDelta(pos) => json!({ "type": "pixelDelta", "position": serialize_physical_position(pos) }), 49 | } 50 | } 51 | 52 | fn serialize_touch_phase(phase: TouchPhase) -> Value { 53 | match phase { 54 | TouchPhase::Started => json!("started"), 55 | TouchPhase::Moved => json!("moved"), 56 | TouchPhase::Ended => json!("ended"), 57 | TouchPhase::Cancelled => json!("cancelled"), 58 | } 59 | } 60 | 61 | pub fn serialize_event<'a>(event: Event<'a, Blocker>) -> Value { 62 | match event { 63 | Event::NewEvents(cause) => { 64 | let cause = match cause { 65 | StartCause::ResumeTimeReached { start, requested_resume } => json!({ "type": "resumeTimeReached", "start": start.elapsed().as_millis().to_string(), "requestedResume": requested_resume.elapsed().as_millis().to_string() }), 66 | StartCause::WaitCancelled { start, requested_resume } => json!({ "type": "waitCancelled", "start": start.elapsed().as_millis().to_string(), "requestedResume": if let Some(requested_resume) = requested_resume { Some(requested_resume.elapsed().as_millis().to_string()) } else { None }}), 67 | StartCause::Poll => json!({ "type": "poll" }), 68 | StartCause::Init => json!({ "type": "init" }), 69 | }; 70 | json!({ "type": "newEvents", "cause": cause }) 71 | }, 72 | Event::WindowEvent { window_id, event } => { 73 | let event = match event { 74 | WindowEvent::Resized(size) => json!({ "type": "resized", "size": serialize_physical_size(size) }), 75 | WindowEvent::Moved(pos) => json!({ "type": "moved", "position": serialize_physical_position(pos) }), 76 | WindowEvent::CloseRequested => json!({ "type": "closeRequested" }), 77 | WindowEvent::Destroyed => json!({ "type": "destroyed" }), 78 | WindowEvent::DroppedFile(path) => json!({ "type": "droppedFile", "path": path.into_os_string().into_string() }), 79 | WindowEvent::HoveredFile(path) => json!({ "type": "hoveredFile", "path": path.into_os_string().into_string() }), 80 | WindowEvent::HoveredFileCancelled => json!({ "type": "hoveredFileCancelled" }), 81 | WindowEvent::ReceivedCharacter(ch) => json!({ "type": "receivedCharacter", "char": ch }), 82 | WindowEvent::Focused(focused) => json!({ "type": "focused", "focused": focused }), 83 | WindowEvent::KeyboardInput { device_id, input, is_synthetic } => json!({ 84 | "type": "keyboardInput", 85 | "deviceID": hash(device_id), 86 | "isSynthetic": is_synthetic, 87 | "input": serialize_keyboard_input(input) 88 | }), 89 | WindowEvent::ModifiersChanged(state) => json!({ "type": "modifiersChanged", "state": state.bits() }), 90 | WindowEvent::CursorMoved { device_id, position, .. } => json!({ 91 | "type": "cursorMoved", 92 | "deviceID": hash(device_id), 93 | "position": serialize_physical_position(position) 94 | }), 95 | WindowEvent::CursorEntered { device_id } => json!({ 96 | "type": "cursorEntered", 97 | "deviceID": hash(device_id) 98 | }), 99 | WindowEvent::CursorLeft { device_id } => json!({ "type": "cursorLeft", "deviceID": hash(device_id) }), 100 | WindowEvent::MouseWheel { device_id, delta, phase, .. } => json!({ 101 | "type": "mouseWheel", 102 | "deviceID": hash(device_id), 103 | "delta": serialize_mouse_scroll_delta(delta), 104 | "phase": serialize_touch_phase(phase), 105 | }), 106 | WindowEvent::MouseInput { device_id, state, button, .. } => json!({ 107 | "type": "mouseInput", 108 | "deviceID": hash(device_id), 109 | "state": match state { 110 | ElementState::Pressed => "pressed", 111 | ElementState::Released => "released" 112 | }, 113 | "button": match button { 114 | MouseButton::Left => json!("left"), 115 | MouseButton::Right => json!("right"), 116 | MouseButton::Middle => json!("middle"), 117 | MouseButton::Other(n) => json!(n), 118 | }, 119 | }), 120 | WindowEvent::TouchpadPressure { device_id, pressure, stage } => json!({ 121 | "type": "touchpadPressure", 122 | "deviceID": hash(device_id), 123 | "pressure": pressure, 124 | "stage": stage, 125 | }), 126 | WindowEvent::AxisMotion { device_id, axis, value } => json!({ 127 | "type": "axisMotion", 128 | "deviceID": hash(device_id), 129 | "axis": axis, 130 | "value": value, 131 | }), 132 | WindowEvent::Touch(input) => json!({ 133 | "type": "touch", 134 | "device_id": hash(input.device_id), 135 | "phase": serialize_touch_phase(input.phase), 136 | "location": serialize_physical_position(input.location), 137 | // leaving out input.force as that's ios specific for now 138 | "id": input.id, 139 | }), 140 | WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size } => json!({ 141 | "type": "scaleFactorChanged", 142 | "scaleFactor": scale_factor, 143 | "newInnerSize": serialize_physical_size(*new_inner_size) 144 | }), 145 | WindowEvent::ThemeChanged(theme) => json!({ 146 | "type": "themeChanged", 147 | "theme": match theme { 148 | Theme::Light => "light", 149 | Theme::Dark => "dark" 150 | } 151 | }), 152 | }; 153 | json!({ "type": "windowEvent", "windowID": hash(window_id), "event": event }) 154 | }, 155 | Event::DeviceEvent { device_id, event } => { 156 | let event = match event { 157 | DeviceEvent::Added => json!({ "type": "added" }), 158 | DeviceEvent::Removed => json!({ "type": "removed" }), 159 | DeviceEvent::MouseMotion { delta } => json!({ "type": "mouseMotion", "delta": delta }), 160 | DeviceEvent::MouseWheel { delta } => json!({ "type": "mouseWheel", "delta": serialize_mouse_scroll_delta(delta) }), 161 | DeviceEvent::Motion { axis, value } => json!({ "type": "motion", "axis": axis, "value": value }), 162 | DeviceEvent::Button { button, state } => json!({ "type": "button", "button": button, "state": match state { ElementState::Pressed => "pressed", ElementState::Released => "released" } }), 163 | DeviceEvent::Key(input) => json!({ "type": "key", "input": serialize_keyboard_input(input) }), 164 | DeviceEvent::Text { codepoint } => json!({ "type": "text", "codepoint": codepoint }), 165 | }; 166 | json!({ "type": "deviceEvent", "deviceID": hash(device_id), "event": event }) 167 | }, 168 | Event::UserEvent(_) => json!({ "type": "blocker" }), 169 | Event::Suspended => json!({ "type": "suspended" }), 170 | Event::Resumed => json!({ "type": "resumed" }), 171 | Event::MainEventsCleared => json!({ "type": "mainEventsCleared" }), 172 | Event::RedrawRequested(wid) => json!({ "type": "redrawRequested", "windowID": hash(wid) }), 173 | Event::RedrawEventsCleared => json!({ "type": "redrawEventsCleared" }), 174 | Event::LoopDestroyed => json!({ "type": "loopDestroyed" }), 175 | } 176 | } 177 | 178 | pub async fn op_next_event(_: Rc>, _: (), _: ()) -> Result { 179 | Ok(tokio::task::spawn_blocking(|| { 180 | let arc = EVENT_RECEIVER.lock().unwrap(); 181 | let asref = arc.as_ref(); 182 | let arc = asref.unwrap(); 183 | let er = arc.lock().unwrap(); 184 | let next = er.recv(); 185 | 186 | serialize_event(next) 187 | }) 188 | .await 189 | .unwrap()) 190 | } 191 | 192 | pub fn init() -> Extension { 193 | Extension::builder() 194 | .ops(vec![ 195 | ("op_next_event", op_async(op_next_event)), 196 | ]) 197 | .build() 198 | } 199 | -------------------------------------------------------------------------------- /src/extra.rs: -------------------------------------------------------------------------------- 1 | use deno_runtime::deno_core::OpState; 2 | use deno_runtime::deno_core::error::AnyError; 3 | use deno_runtime::deno_core::op_sync; 4 | use deno_runtime::deno_core::Extension; 5 | use serde::Serialize; 6 | use serde::Deserialize; 7 | 8 | #[derive(Deserialize)] 9 | #[serde(rename_all = "camelCase")] 10 | struct ApplySourceMap { 11 | file_name: String, 12 | line_number: i32, 13 | column_number: i32, 14 | } 15 | 16 | #[derive(Serialize)] 17 | #[serde(rename_all = "camelCase")] 18 | struct AppliedSourceMap { 19 | file_name: String, 20 | line_number: u32, 21 | column_number: u32, 22 | } 23 | 24 | fn op_apply_source_map( 25 | _state: &mut OpState, 26 | args: ApplySourceMap, 27 | _: (), 28 | ) -> Result { 29 | Ok(AppliedSourceMap { 30 | file_name: args.file_name, 31 | line_number: args.line_number as u32, 32 | column_number: args.column_number as u32 33 | }) 34 | } 35 | 36 | fn op_format_diagnostic( 37 | _state: &mut OpState, 38 | _: (), 39 | _: (), 40 | ) -> Result { 41 | Ok(String::from("")) 42 | } 43 | 44 | fn op_format_file_name( 45 | _state: &mut OpState, 46 | file_name: String, 47 | _: (), 48 | ) -> Result { 49 | Ok(file_name) 50 | } 51 | 52 | pub fn init() -> Extension { 53 | Extension::builder() 54 | .ops(vec![ 55 | ("op_apply_source_map", op_sync(op_apply_source_map)), 56 | ("op_format_diagnostic", op_sync(op_format_diagnostic)), 57 | ("op_format_file_name", op_sync(op_format_file_name)), 58 | ]) 59 | .build() 60 | } 61 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | mod runtime; 5 | mod event_loop; 6 | mod surface; 7 | mod util; 8 | mod window; 9 | mod extra; 10 | 11 | use winit_main::EventLoopHandle; 12 | use winit_main::EventReceiver; 13 | 14 | use std::sync::Arc; 15 | use std::sync::Mutex; 16 | 17 | lazy_static! { 18 | pub static ref EVENT_LOOP: Mutex>>> = Mutex::new(None); 19 | pub static ref EVENT_RECEIVER: Mutex>>> = Mutex::new(None); 20 | } 21 | 22 | #[winit_main::main] 23 | fn main(event_loop: EventLoopHandle, events: EventReceiver) { 24 | let rt = tokio::runtime::Runtime::new().unwrap(); 25 | let arc_events = Arc::new(Mutex::new(events)); 26 | 27 | rt.block_on(async { 28 | *EVENT_LOOP.lock().unwrap() = Some(Arc::new(Mutex::new(event_loop))); 29 | *EVENT_RECEIVER.lock().unwrap() = Some(arc_events); 30 | 31 | match runtime::start().await { 32 | Err(err) => eprintln!("{}", err.to_string()), 33 | _ => {}, 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use deno_runtime::deno_core::FsModuleLoader; 2 | use deno_runtime::deno_core::ModuleSpecifier; 3 | use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; 4 | use deno_runtime::deno_web::BlobStore; 5 | use deno_runtime::permissions::Permissions; 6 | use deno_runtime::worker::MainWorker; 7 | use deno_runtime::worker::WorkerOptions; 8 | use deno_runtime::deno_core::error::AnyError; 9 | use deno_runtime::BootstrapOptions; 10 | use std::path::Path; 11 | use std::rc::Rc; 12 | use std::sync::Arc; 13 | 14 | use crate::event_loop; 15 | use crate::surface; 16 | use crate::window; 17 | use crate::extra; 18 | 19 | fn get_error_class_name(e: &AnyError) -> &'static str { 20 | deno_runtime::errors::get_error_class_name(e).unwrap_or("Error") 21 | } 22 | 23 | pub async fn start() -> Result<(), AnyError> { 24 | let module_loader = Rc::new(FsModuleLoader); 25 | let create_web_worker_cb = Arc::new(|_| { 26 | todo!("Web workers are not supported"); 27 | }); 28 | 29 | let options = WorkerOptions { 30 | bootstrap: BootstrapOptions { 31 | apply_source_maps: false, 32 | args: vec![], 33 | cpu_count: 1, 34 | debug_flag: false, 35 | enable_testing_features: false, 36 | location: Some(ModuleSpecifier::parse("https://desktop.deno.land").unwrap()), 37 | no_color: false, 38 | runtime_version: "0.0.1".to_string(), 39 | ts_version: "4.4.3".to_string(), 40 | unstable: true, 41 | }, 42 | extensions: vec![ 43 | event_loop::init(), 44 | window::init(), 45 | // Will be integrated into deno_webgpu later. 46 | // https://github.com/gfx-rs/wgpu/pull/2279 47 | surface::init(), 48 | // Some ops deno_runtime depends on 49 | // But aren't there (implemented in CLI) 50 | // https://github.com/denoland/deno/issues/12918 51 | extra::init(), 52 | ], 53 | unsafely_ignore_certificate_errors: None, 54 | root_cert_store: None, 55 | user_agent: "deno_desktop".to_string(), 56 | seed: None, 57 | js_error_create_fn: None, 58 | create_web_worker_cb, 59 | maybe_inspector_server: None, 60 | should_break_on_first_statement: false, 61 | module_loader, 62 | get_error_class_fn: Some(&get_error_class_name), 63 | origin_storage_dir: None, 64 | blob_store: BlobStore::default(), 65 | broadcast_channel: InMemoryBroadcastChannel::default(), 66 | shared_array_buffer_store: None, 67 | compiled_wasm_module_store: None, 68 | }; 69 | 70 | let path = std::env::args().nth(1).unwrap_or(String::from("test.js")); 71 | let js_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(path); 72 | let main_module = deno_runtime::deno_core::resolve_path(&js_path.to_string_lossy())?; 73 | let permissions = Permissions::allow_all(); 74 | 75 | let mut worker = MainWorker::bootstrap_from_options(main_module.clone(), permissions, options); 76 | 77 | worker 78 | .js_runtime 79 | .execute_script("deno_desktop:core.js", include_str!("core.js"))?; 80 | 81 | worker.execute_main_module(&main_module).await?; 82 | worker.run_event_loop(false).await?; 83 | 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /src/surface.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_macros)] 2 | 3 | use std::borrow::Cow; 4 | use std::rc::Rc; 5 | 6 | use deno_runtime::deno_core::Extension; 7 | use deno_runtime::deno_core::op_sync; 8 | use deno_runtime::deno_core::ResourceTable; 9 | use deno_runtime::deno_core::error::bad_resource_id; 10 | use deno_runtime::deno_core::error::AnyError; 11 | use deno_runtime::deno_core::OpState; 12 | use deno_runtime::deno_core::anyhow::anyhow; 13 | use deno_runtime::deno_core::Resource; 14 | use deno_runtime::deno_core::ResourceId; 15 | use deno_runtime::deno_webgpu::texture::GpuTextureFormat; 16 | use serde::Deserialize; 17 | use deno_runtime::deno_webgpu::wgpu_core; 18 | use deno_runtime::deno_webgpu::wgpu_types; 19 | use deno_runtime::deno_webgpu::Instance; 20 | use deno_runtime::deno_webgpu::WebGpuDevice; 21 | use deno_runtime::deno_webgpu::WebGpuAdapter; 22 | use deno_runtime::deno_webgpu::texture::WebGpuTexture; 23 | 24 | use crate::window::WindowResource; 25 | 26 | fn get_resource(state: &mut ResourceTable, rid: ResourceId) -> Result, AnyError> { 27 | let res = state.get::(rid); 28 | 29 | if res.is_err() { 30 | Err(bad_resource_id()) 31 | } else { 32 | Ok(res.unwrap()) 33 | } 34 | } 35 | 36 | macro_rules! gfx_select { 37 | ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { 38 | match $id.backend() { 39 | #[cfg(not(target_os = "macos"))] 40 | wgpu_types::Backend::Vulkan => $global.$method::( $($param),* ), 41 | #[cfg(target_os = "macos")] 42 | wgpu_types::Backend::Metal => $global.$method::( $($param),* ), 43 | #[cfg(windows)] 44 | wgpu_types::Backend::Dx12 => $global.$method::( $($param),* ), 45 | #[cfg(all(unix, not(target_os = "macos")))] 46 | wgpu_types::Backend::Gl => $global.$method::( $($param),+ ), 47 | other => panic!("Unexpected backend {:?}", other), 48 | } 49 | }; 50 | } 51 | 52 | struct WebGpuSurface(wgpu_core::id::SurfaceId); 53 | impl Resource for WebGpuSurface { 54 | fn name(&self) -> Cow { 55 | "webGPUSurface".into() 56 | } 57 | } 58 | 59 | #[derive(Deserialize)] 60 | #[serde(rename_all = "camelCase")] 61 | pub struct CreateSurfaceArgs { 62 | window_rid: ResourceId, 63 | } 64 | 65 | pub fn op_webgpu_create_surface( 66 | state: &mut OpState, 67 | args: CreateSurfaceArgs, 68 | _: (), 69 | ) -> Result { 70 | let winres = get_resource::(&mut state.resource_table, args.window_rid)?; 71 | let instance = state.borrow::(); 72 | let surface_id = instance.instance_create_surface(&winres.0, std::marker::PhantomData); 73 | Ok(state.resource_table.add(WebGpuSurface(surface_id))) 74 | } 75 | 76 | #[derive(Deserialize)] 77 | #[serde(rename_all = "camelCase")] 78 | pub struct ConfigureSurfaceArgs { 79 | device_rid: ResourceId, 80 | surface_rid: ResourceId, 81 | format: GpuTextureFormat, 82 | usage: u32, 83 | width: u32, 84 | height: u32, 85 | } 86 | 87 | pub fn op_webgpu_configure_surface( 88 | state: &mut OpState, 89 | args: ConfigureSurfaceArgs, 90 | _: (), 91 | ) -> Result<(), AnyError> { 92 | let surface = state.resource_table.get::(args.surface_rid)?; 93 | let device = state.resource_table.get::(args.device_rid)?; 94 | let instance = state.borrow::(); 95 | 96 | let config = wgpu_types::SurfaceConfiguration { 97 | usage: wgpu_types::TextureUsages::from_bits(args.usage).unwrap(), 98 | format: args.format.try_into().unwrap(), 99 | width: args.width, 100 | height: args.height, 101 | present_mode: wgpu_types::PresentMode::Fifo, 102 | }; 103 | 104 | match gfx_select!(device.0 => instance.surface_configure( 105 | surface.0, 106 | device.0, 107 | &config 108 | )) { 109 | Some(err) => Err(err.into()), 110 | None => Ok(()), 111 | } 112 | } 113 | 114 | #[derive(Deserialize)] 115 | #[serde(rename_all = "camelCase")] 116 | pub struct SurfacePreferredFormatArgs { 117 | adapter_rid: ResourceId, 118 | surface_rid: ResourceId, 119 | } 120 | 121 | pub fn op_webgpu_surface_get_preferred_format( 122 | state: &mut OpState, 123 | args: SurfacePreferredFormatArgs, 124 | _: (), 125 | ) -> Result { 126 | let surface = state.resource_table.get::(args.surface_rid)?; 127 | let adapter = state.resource_table.get::(args.adapter_rid)?; 128 | let instance = state.borrow::(); 129 | let fmt = gfx_select!(adapter.0 => instance.surface_get_preferred_format(surface.0, adapter.0))?; 130 | Ok(fmt) 131 | } 132 | 133 | pub fn op_webgpu_surface_get_current_texture( 134 | state: &mut OpState, 135 | args: SurfacePreferredFormatArgs, 136 | _: (), 137 | ) -> Result { 138 | let surface = state.resource_table.get::(args.surface_rid)?; 139 | let adapter = state.resource_table.get::(args.adapter_rid)?; 140 | let instance = state.borrow::(); 141 | 142 | let current_texture = gfx_select!( 143 | adapter.0 => instance.surface_get_current_texture(surface.0, std::marker::PhantomData) 144 | )?; 145 | if let Some(texture) = current_texture.texture_id { 146 | Ok(state.resource_table.add(WebGpuTexture(texture))) 147 | } else { 148 | Err(anyhow!("No texture, status: {:?}", current_texture.status)) 149 | } 150 | } 151 | 152 | pub fn op_webgpu_surface_present( 153 | state: &mut OpState, 154 | args: SurfacePreferredFormatArgs, 155 | _: (), 156 | ) -> Result { 157 | let surface = state.resource_table.get::(args.surface_rid)?; 158 | let adapter = state.resource_table.get::(args.adapter_rid)?; 159 | let instance = state.borrow::(); 160 | 161 | let status = gfx_select!(adapter.0 => instance.surface_present(surface.0))?; 162 | Ok(String::from(match status { 163 | wgpu_types::SurfaceStatus::Good => "good", 164 | wgpu_types::SurfaceStatus::Suboptimal => "suboptimal", 165 | wgpu_types::SurfaceStatus::Timeout => "timeout", 166 | wgpu_types::SurfaceStatus::Outdated => "outdated", 167 | wgpu_types::SurfaceStatus::Lost => "lost", 168 | })) 169 | } 170 | 171 | pub fn op_webgpu_surface_drop( 172 | state: &mut OpState, 173 | rid: ResourceId, 174 | _: (), 175 | ) -> Result<(), AnyError> { 176 | let surface = state.resource_table.get::(rid)?; 177 | let instance = state.borrow::(); 178 | instance.surface_drop(surface.0); 179 | Ok(()) 180 | } 181 | 182 | pub fn init() -> Extension { 183 | Extension::builder() 184 | .ops(vec![ 185 | ("op_webgpu_create_surface", op_sync(op_webgpu_create_surface)), 186 | ("op_webgpu_configure_surface", op_sync(op_webgpu_configure_surface)), 187 | ("op_webgpu_surface_get_preferred_format", op_sync(op_webgpu_surface_get_preferred_format)), 188 | ("op_webgpu_surface_get_current_texture", op_sync(op_webgpu_surface_get_current_texture)), 189 | ("op_webgpu_surface_present", op_sync(op_webgpu_surface_present)), 190 | ("op_webgpu_surface_drop", op_sync(op_webgpu_surface_drop)), 191 | ]) 192 | .build() 193 | } 194 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::hash::Hash; 3 | use std::hash::Hasher; 4 | 5 | pub fn hash(t: T) -> u32 { 6 | let mut s = DefaultHasher::new(); 7 | t.hash(&mut s); 8 | s.finish() as u32 9 | } 10 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use deno_runtime::deno_core::Extension; 2 | use deno_runtime::deno_core::op_sync; 3 | use serde::Deserialize; 4 | use deno_runtime::deno_core::OpState; 5 | use deno_runtime::deno_core::ResourceId; 6 | use deno_runtime::deno_core::Resource; 7 | use deno_runtime::deno_core::ZeroCopyBuf; 8 | use deno_runtime::deno_core::error::AnyError; 9 | use winit_main::reexports::dpi::PhysicalSize; 10 | use winit_main::reexports::dpi::Size; 11 | use winit_main::reexports::window::Window; 12 | use winit_main::reexports::window::WindowAttributes; 13 | use winit_main::reexports::window::UserAttentionType; 14 | use winit_main::reexports::window::CursorIcon; 15 | use winit_main::reexports::window::Fullscreen; 16 | use winit_main::reexports::window::Icon; 17 | use winit_main::reexports::dpi::Position; 18 | use winit_main::reexports::dpi::PhysicalPosition; 19 | use raw_window_handle::RawWindowHandle; 20 | use raw_window_handle::HasRawWindowHandle; 21 | use deno_runtime::deno_core::serde_json::Value; 22 | 23 | use crate::EVENT_LOOP; 24 | use crate::util::hash; 25 | use crate::event_loop::serialize_physical_position; 26 | use crate::event_loop::serialize_physical_size; 27 | 28 | pub struct DynHasRawWindowHandle(pub Box); 29 | 30 | unsafe impl HasRawWindowHandle for DynHasRawWindowHandle { 31 | fn raw_window_handle(&self) -> RawWindowHandle { 32 | self.0.raw_window_handle() 33 | } 34 | } 35 | 36 | pub struct WindowResource(pub DynHasRawWindowHandle); 37 | 38 | impl Resource for WindowResource { 39 | fn name(&self) -> std::borrow::Cow { 40 | "Window".into() 41 | } 42 | } 43 | 44 | #[derive(Deserialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct CreateWindowArgs { 47 | title: Option, 48 | resizable: Option, 49 | decorations: Option, 50 | maximized: Option, 51 | visible: Option, 52 | transparent: Option, 53 | always_on_top: Option, 54 | width: Option, 55 | height: Option, 56 | min_width: Option, 57 | min_height: Option, 58 | max_width: Option, 59 | max_height: Option, 60 | } 61 | 62 | pub fn op_create_window( 63 | state: &mut OpState, 64 | args: CreateWindowArgs, 65 | _: () 66 | ) -> Result<(u32, ResourceId), AnyError> { 67 | let ev = EVENT_LOOP.lock().unwrap(); 68 | let ev = ev.as_ref().unwrap().lock().unwrap(); 69 | 70 | let mut attribs = WindowAttributes::default(); 71 | 72 | if let Some(title) = args.title { 73 | attribs.title = title; 74 | } 75 | 76 | if let Some(resizable) = args.resizable { 77 | attribs.resizable = resizable; 78 | } 79 | 80 | if let Some(decorations) = args.decorations { 81 | attribs.decorations = decorations; 82 | } 83 | 84 | if let Some(maximized) = args.maximized { 85 | attribs.maximized = maximized; 86 | } 87 | 88 | if let Some(visible) = args.visible { 89 | attribs.visible = visible; 90 | } 91 | 92 | if let Some(transparent) = args.transparent { 93 | attribs.transparent = transparent; 94 | } 95 | 96 | if let Some(always_on_top) = args.always_on_top { 97 | attribs.always_on_top = always_on_top; 98 | } 99 | 100 | if args.width.is_some() || args.height.is_some() { 101 | let width = args.width.unwrap_or(800); 102 | let height = args.height.unwrap_or(600); 103 | let size = PhysicalSize::new(width, height); 104 | attribs.inner_size = Some(Size::Physical(size)); 105 | } 106 | 107 | if args.min_width.is_some() || args.min_height.is_some() { 108 | let min_width = args.min_width.unwrap_or(0); 109 | let min_height = args.min_height.unwrap_or(0); 110 | let size = PhysicalSize::new(min_width, min_height); 111 | attribs.min_inner_size = Some(Size::Physical(size)); 112 | } 113 | 114 | if args.max_width.is_some() || args.max_height.is_some() { 115 | let max_width = args.max_width.unwrap_or(0); 116 | let max_height = args.max_height.unwrap_or(0); 117 | let size = PhysicalSize::new(max_width, max_height); 118 | attribs.max_inner_size = Some(Size::Physical(size)); 119 | } 120 | 121 | let window = ev.create_window(attribs)?; 122 | 123 | Ok((hash(window.id()), state.resource_table.add(WindowResource(DynHasRawWindowHandle(Box::new(window)))))) 124 | } 125 | 126 | #[derive(Deserialize)] 127 | pub struct SerdePosition { 128 | x: i32, 129 | y: i32, 130 | } 131 | 132 | impl Into> for SerdePosition { 133 | fn into(self) -> PhysicalPosition { 134 | PhysicalPosition::new(self.x, self.y) 135 | } 136 | } 137 | 138 | #[derive(Deserialize)] 139 | pub struct SerdeSize { 140 | width: u32, 141 | height: u32, 142 | } 143 | 144 | impl Into> for SerdeSize { 145 | fn into(self) -> PhysicalSize { 146 | PhysicalSize::new(self.width, self.height) 147 | } 148 | } 149 | 150 | fn get_window(state: &mut OpState, rid: ResourceId) -> Result<&Window, AnyError> { 151 | let window = state.resource_table.get::(rid)?; 152 | let window = window.0.0.as_ref() as *const _ as *const Window; 153 | let window = unsafe { &*window }; 154 | Ok(window) 155 | } 156 | 157 | pub fn op_window_fullscreen( 158 | state: &mut OpState, 159 | rid: ResourceId, 160 | _: () 161 | ) -> Result { 162 | let window = get_window(state, rid)?; 163 | let fs = window.fullscreen(); 164 | Ok(match fs { 165 | Some(_) => true, 166 | None => false, 167 | }) 168 | } 169 | 170 | pub fn op_window_inner_position( 171 | state: &mut OpState, 172 | rid: ResourceId, 173 | _: () 174 | ) -> Result { 175 | let window = get_window(state, rid)?; 176 | let position = window.inner_position()?; 177 | Ok(serialize_physical_position(position)) 178 | } 179 | 180 | pub fn op_window_inner_size( 181 | state: &mut OpState, 182 | rid: ResourceId, 183 | _: () 184 | ) -> Result { 185 | let window = get_window(state, rid)?; 186 | let size = window.inner_size(); 187 | Ok(serialize_physical_size(size)) 188 | } 189 | 190 | pub fn op_window_outer_position( 191 | state: &mut OpState, 192 | rid: ResourceId, 193 | _: () 194 | ) -> Result { 195 | let window = get_window(state, rid)?; 196 | let position = window.outer_position()?; 197 | Ok(serialize_physical_position(position)) 198 | } 199 | 200 | pub fn op_window_outer_size( 201 | state: &mut OpState, 202 | rid: ResourceId, 203 | _: () 204 | ) -> Result { 205 | let window = get_window(state, rid)?; 206 | let size = window.outer_size(); 207 | Ok(serialize_physical_size(size)) 208 | } 209 | 210 | pub fn op_window_request_redraw( 211 | state: &mut OpState, 212 | rid: ResourceId, 213 | _: () 214 | ) -> Result<(), AnyError> { 215 | let window = state.resource_table.get::(rid)?; 216 | let window = window.0.0.as_ref() as *const _ as *const Window; 217 | let window = unsafe { &*window }; 218 | window.request_redraw(); 219 | Ok(()) 220 | } 221 | 222 | pub fn op_window_request_user_attention( 223 | state: &mut OpState, 224 | args: (ResourceId, Option), 225 | _: (), 226 | ) -> Result<(), AnyError> { 227 | let window = get_window(state, args.0)?; 228 | let ty = match args.1 { 229 | Some(ty) => match ty.as_str() { 230 | "critical" => Some(UserAttentionType::Critical), 231 | "informational" => Some(UserAttentionType::Informational), 232 | _ => None, 233 | }, 234 | _ => None, 235 | }; 236 | window.request_user_attention(ty); 237 | Ok(()) 238 | } 239 | 240 | pub fn op_window_scale_factor( 241 | state: &mut OpState, 242 | rid: ResourceId, 243 | _: () 244 | ) -> Result { 245 | let window = get_window(state, rid)?; 246 | Ok(window.scale_factor()) 247 | } 248 | 249 | pub fn op_window_set_always_on_top( 250 | state: &mut OpState, 251 | args: (ResourceId, bool), 252 | _: () 253 | ) -> Result<(), AnyError> { 254 | let window = get_window(state, args.0)?; 255 | window.set_always_on_top(args.1); 256 | Ok(()) 257 | } 258 | 259 | pub fn op_window_set_cursor_grab( 260 | state: &mut OpState, 261 | args: (ResourceId, bool), 262 | _: () 263 | ) -> Result<(), AnyError> { 264 | let window = get_window(state, args.0)?; 265 | window.set_cursor_grab(args.1)?; 266 | Ok(()) 267 | } 268 | 269 | pub fn op_window_set_cursor_icon( 270 | state: &mut OpState, 271 | args: (ResourceId, String), 272 | _: (), 273 | ) -> Result<(), AnyError> { 274 | let window = get_window(state, args.0)?; 275 | let cursor = match args.1.as_str() { 276 | // TODO 277 | _ => CursorIcon::Default, 278 | }; 279 | window.set_cursor_icon(cursor); 280 | Ok(()) 281 | } 282 | 283 | pub fn op_window_set_cursor_position( 284 | state: &mut OpState, 285 | args: (ResourceId, SerdePosition), 286 | _: (), 287 | ) -> Result<(), AnyError> { 288 | let window = get_window(state, args.0)?; 289 | window.set_cursor_position(Position::Physical(args.1.into()))?; 290 | Ok(()) 291 | } 292 | 293 | pub fn op_window_set_cursor_visible( 294 | state: &mut OpState, 295 | args: (ResourceId, bool), 296 | _: () 297 | ) -> Result<(), AnyError> { 298 | let window = get_window(state, args.0)?; 299 | window.set_cursor_visible(args.1); 300 | Ok(()) 301 | } 302 | 303 | pub fn op_window_set_decorations( 304 | state: &mut OpState, 305 | args: (ResourceId, bool), 306 | _: () 307 | ) -> Result<(), AnyError> { 308 | let window = get_window(state, args.0)?; 309 | window.set_decorations(args.1); 310 | Ok(()) 311 | } 312 | 313 | pub fn op_window_set_fullscreen( 314 | state: &mut OpState, 315 | args: (ResourceId, bool), 316 | _: () 317 | ) -> Result<(), AnyError> { 318 | let window = get_window(state, args.0)?; 319 | window.set_fullscreen(match args.1 { 320 | true => Some(Fullscreen::Borderless(None)), 321 | false => None, 322 | }); 323 | Ok(()) 324 | } 325 | 326 | pub fn op_window_set_ime_position( 327 | state: &mut OpState, 328 | args: (ResourceId, SerdePosition), 329 | _: () 330 | ) -> Result<(), AnyError> { 331 | let window = get_window(state, args.0)?; 332 | window.set_ime_position(Position::Physical(args.1.into())); 333 | Ok(()) 334 | } 335 | 336 | pub fn op_window_set_inner_size( 337 | state: &mut OpState, 338 | args: (ResourceId, SerdeSize), 339 | _: () 340 | ) -> Result<(), AnyError> { 341 | let window = get_window(state, args.0)?; 342 | window.set_inner_size(Size::Physical(args.1.into())); 343 | Ok(()) 344 | } 345 | 346 | pub fn op_window_set_max_inner_size( 347 | state: &mut OpState, 348 | args: (ResourceId, Option), 349 | _: () 350 | ) -> Result<(), AnyError> { 351 | let window = get_window(state, args.0)?; 352 | window.set_max_inner_size(match args.1 { 353 | Some(size) => Some(Size::Physical(size.into())), 354 | None => None, 355 | }); 356 | Ok(()) 357 | } 358 | 359 | pub fn op_window_set_maximized( 360 | state: &mut OpState, 361 | args: (ResourceId, bool), 362 | _: () 363 | ) -> Result<(), AnyError> { 364 | let window = get_window(state, args.0)?; 365 | window.set_maximized(args.1); 366 | Ok(()) 367 | } 368 | 369 | pub fn op_window_set_min_inner_size( 370 | state: &mut OpState, 371 | args: (ResourceId, Option), 372 | _: () 373 | ) -> Result<(), AnyError> { 374 | let window = get_window(state, args.0)?; 375 | window.set_min_inner_size(match args.1 { 376 | Some(size) => Some(Size::Physical(size.into())), 377 | None => None, 378 | }); 379 | Ok(()) 380 | } 381 | 382 | pub fn op_window_set_minimized( 383 | state: &mut OpState, 384 | args: (ResourceId, bool), 385 | _: () 386 | ) -> Result<(), AnyError> { 387 | let window = get_window(state, args.0)?; 388 | window.set_minimized(args.1); 389 | Ok(()) 390 | } 391 | 392 | pub fn op_window_set_outer_position( 393 | state: &mut OpState, 394 | args: (ResourceId, SerdePosition), 395 | _: () 396 | ) -> Result<(), AnyError> { 397 | let window = get_window(state, args.0)?; 398 | window.set_outer_position(Position::Physical(args.1.into())); 399 | Ok(()) 400 | } 401 | 402 | pub fn op_window_set_resizable( 403 | state: &mut OpState, 404 | args: (ResourceId, bool), 405 | _: () 406 | ) -> Result<(), AnyError> { 407 | let window = get_window(state, args.0)?; 408 | window.set_resizable(args.1); 409 | Ok(()) 410 | } 411 | 412 | pub fn op_window_set_title( 413 | state: &mut OpState, 414 | args: (ResourceId, String), 415 | _: () 416 | ) -> Result<(), AnyError> { 417 | let window = get_window(state, args.0)?; 418 | window.set_title(&args.1); 419 | Ok(()) 420 | } 421 | 422 | pub fn op_window_set_visible( 423 | state: &mut OpState, 424 | args: (ResourceId, bool), 425 | _: () 426 | ) -> Result<(), AnyError> { 427 | let window = get_window(state, args.0)?; 428 | window.set_visible(args.1); 429 | Ok(()) 430 | } 431 | 432 | pub fn op_window_set_window_icon( 433 | state: &mut OpState, 434 | args: (ResourceId, u32, u32), 435 | zc: ZeroCopyBuf, 436 | ) -> Result<(), AnyError> { 437 | let window = get_window(state, args.0)?; 438 | let icon = Icon::from_rgba(zc.to_vec(), args.1, args.2)?; 439 | window.set_window_icon(Some(icon)); 440 | Ok(()) 441 | } 442 | 443 | macro_rules! op { 444 | ($name:ident) => { 445 | (stringify!($name), op_sync($name)) 446 | } 447 | } 448 | 449 | pub fn init() -> Extension { 450 | Extension::builder() 451 | .ops(vec![ 452 | op!(op_create_window), 453 | op!(op_window_fullscreen), 454 | op!(op_window_inner_position), 455 | op!(op_window_inner_size), 456 | op!(op_window_outer_position), 457 | op!(op_window_outer_size), 458 | op!(op_window_request_redraw), 459 | op!(op_window_request_user_attention), 460 | op!(op_window_scale_factor), 461 | op!(op_window_set_always_on_top), 462 | op!(op_window_set_cursor_grab), 463 | op!(op_window_set_cursor_icon), 464 | op!(op_window_set_cursor_position), 465 | op!(op_window_set_cursor_visible), 466 | op!(op_window_set_decorations), 467 | op!(op_window_set_fullscreen), 468 | op!(op_window_set_ime_position), 469 | op!(op_window_set_inner_size), 470 | op!(op_window_set_max_inner_size), 471 | op!(op_window_set_maximized), 472 | op!(op_window_set_min_inner_size), 473 | op!(op_window_set_minimized), 474 | op!(op_window_set_outer_position), 475 | op!(op_window_set_resizable), 476 | op!(op_window_set_title), 477 | op!(op_window_set_visible), 478 | op!(op_window_set_window_icon), 479 | ]) 480 | .build() 481 | } 482 | --------------------------------------------------------------------------------