├── .gitignore ├── LICENSE ├── README.md ├── api ├── util.ts ├── vk.min.js └── vk.ts ├── data ├── video.xml ├── vk.json └── vk.xml ├── deno.json ├── examples ├── compute │ └── main.ts └── triangle │ ├── main.ts │ └── shaders │ ├── frag.glsl │ ├── frag.spv │ ├── vert.glsl │ └── vert.spv ├── generator ├── emitter.ts ├── parse_xml.ts └── process_xml.ts └── mod.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | deno.lock 3 | *.dll 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deno Vulkan 2 | 3 | [![Tags](https://img.shields.io/github/release/deno-windowing/vulkan)](https://github.com/deno-windowing/vulkan/releases) 4 | [![Doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/vulkan/mod.ts) 5 | [![License](https://img.shields.io/github/license/deno-windowing/vulkan)](https://github.com/deno-windowing/vulkan/blob/master/LICENSE) 6 | [![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/DjDeveloperr) 7 | 8 | Vulkan API bindings for Deno 9 | 10 | ## Usage 11 | 12 | Since this module depends on unstable FFI API, you need to pass `--unstable` 13 | along with `--allow-ffi`. 14 | 15 | ```sh 16 | deno run --unstable --allow-ffi 17 | ``` 18 | 19 | ## Maintainers 20 | 21 | - Dj ([@DjDeveloperr](https://github.com/DjDeveloperr)) 22 | - Loading ([@load1n9](https://github.com/load1n9)) 23 | 24 | ## License 25 | 26 | [Apache-2.0](./LICENSE) licensed. 27 | 28 | Copyright 2023 © The Deno Windowing Team 29 | -------------------------------------------------------------------------------- /api/util.ts: -------------------------------------------------------------------------------- 1 | export const BUFFER = Symbol("vkStructBuffer"); 2 | export const DATAVIEW = Symbol("vkStructDataView"); 3 | export const LE = 4 | new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] === 0x78; 5 | 6 | export interface BaseStruct { 7 | readonly [BUFFER]: Uint8Array; 8 | } 9 | 10 | export type TypedArray = 11 | | Uint8Array 12 | | Uint16Array 13 | | Uint32Array 14 | | Int8Array 15 | | Int16Array 16 | | Int32Array 17 | | Float32Array 18 | | Float64Array 19 | | BigUint64Array 20 | | BigInt64Array; 21 | 22 | export type AnyBuffer = 23 | | ArrayBuffer 24 | | TypedArray 25 | | null 26 | | BaseStruct; 27 | 28 | export function anyBuffer(buffer: AnyBuffer) { 29 | if (!buffer) return null; 30 | else if (typeof buffer === "object" && BUFFER in buffer) { 31 | return (buffer as BaseStruct)[BUFFER]; 32 | } else if (buffer instanceof Uint8Array) return buffer; 33 | return new Uint8Array( 34 | buffer instanceof ArrayBuffer 35 | ? buffer 36 | : (buffer as unknown as ArrayBufferView).buffer, 37 | ); 38 | } 39 | 40 | export type AnyPointer = Deno.PointerValue | null | AnyBuffer; 41 | 42 | export function anyPointer(buffer: AnyPointer): Deno.PointerValue { 43 | if (!buffer) return 0; 44 | else if (typeof buffer === "number" || typeof buffer === "bigint") { 45 | return buffer; 46 | } 47 | const u8 = anyBuffer(buffer); 48 | return u8!.length === 0 ? 0 : Deno.UnsafePointer.of(u8 ?? new Uint8Array()); 49 | } 50 | 51 | export class CString extends Uint8Array { 52 | constructor(str: string) { 53 | super(str.length + 1); 54 | new TextEncoder().encodeInto(str, this); 55 | } 56 | } 57 | 58 | export class CStringArray extends Uint8Array { 59 | #view: DataView; 60 | #datas: CString[] = []; 61 | 62 | constructor(strs: string[]) { 63 | super(strs.length * 8); 64 | this.#view = new DataView(this.buffer); 65 | for (let i = 0; i < strs.length; i++) { 66 | const str = strs[i]; 67 | const data = new CString(str); 68 | this.#datas.push(data); 69 | this.#view.setBigUint64(i * 8, BigInt(Deno.UnsafePointer.of(data)), LE); 70 | } 71 | } 72 | } 73 | 74 | const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); 75 | 76 | export class PointerRef extends Uint8Array { 77 | #view: DataView; 78 | 79 | constructor() { 80 | super(8); 81 | this.#view = new DataView(this.buffer); 82 | } 83 | 84 | get value(): Deno.PointerValue { 85 | const ptr = this.#view.getBigUint64(0, LE); 86 | if (ptr < MAX_SAFE_INTEGER) return Number(ptr); 87 | return ptr; 88 | } 89 | 90 | set value(value: Deno.PointerValue) { 91 | this.#view.setBigUint64(0, BigInt(anyPointer(value)), LE); 92 | } 93 | } 94 | 95 | export class PointerArray extends BigUint64Array { 96 | #_datas: AnyPointer[] = []; 97 | 98 | constructor(ptrs: AnyPointer[] | number) { 99 | super(typeof ptrs === "number" ? ptrs : ptrs.length); 100 | if (typeof ptrs !== "number") { 101 | this.#_datas = ptrs; 102 | for (let i = 0; i < ptrs.length; i++) { 103 | this[i] = BigInt(anyPointer(ptrs[i])); 104 | } 105 | } 106 | } 107 | } 108 | 109 | export class StructArray implements BaseStruct { 110 | #data: Uint8Array; 111 | 112 | get data() { 113 | return this.#data; 114 | } 115 | 116 | get [BUFFER]() { 117 | return this.#data; 118 | } 119 | 120 | constructor( 121 | datas: T[] | number | Uint8Array, 122 | public Struct: (new (u8: Uint8Array) => T) & { size: number }, 123 | ) { 124 | this.#data = datas instanceof Uint8Array ? datas : new Uint8Array( 125 | typeof datas === "number" 126 | ? datas * Struct.size 127 | : datas.length * Struct.size, 128 | ); 129 | if (typeof datas !== "number" && !(datas instanceof Uint8Array)) { 130 | for (let i = 0; i < datas.length; i++) { 131 | this.#data.set(datas[i][BUFFER], i * Struct.size); 132 | } 133 | } 134 | } 135 | 136 | get length() { 137 | return this.#data.length / this.Struct.size; 138 | } 139 | 140 | get byteLength() { 141 | return this.#data.byteLength; 142 | } 143 | 144 | get(index: number): T { 145 | return new this.Struct(this.#data.subarray(index * this.Struct.size)); 146 | } 147 | 148 | set(index: number, value: T) { 149 | this.#data.set(value[BUFFER], index * this.Struct.size); 150 | } 151 | 152 | *[Symbol.iterator]() { 153 | for (let i = 0; i < this.length; i++) { 154 | yield this.get(i); 155 | } 156 | } 157 | } 158 | 159 | export function getBuffer( 160 | ptr: Deno.PointerValue, 161 | size: number, 162 | arr: new (buf: ArrayBuffer) => T, 163 | ): T { 164 | return new arr(Deno.UnsafePointerView.getArrayBuffer(ptr, size)); 165 | } 166 | 167 | export function makeVersion(major: number, minor: number, patch: number) { 168 | return (major << 22) | (minor << 12) | patch; 169 | } 170 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "parse-xml": "deno run -A ./generator/parse_xml.ts", 4 | "gen": "deno run -A ./generator/emitter.ts", 5 | "test": "deno run -A --unstable ./examples/triangle/main.ts", 6 | "test-compute": "deno run -A --unstable ./examples/compute/main.ts", 7 | "check": "deno check --unstable ./api/vk.ts" 8 | }, 9 | 10 | "fmt": { 11 | "files": { 12 | "exclude": [ 13 | "api/vk.ts", 14 | "api/vk.min.js" 15 | ] 16 | } 17 | }, 18 | 19 | "lint": { 20 | "files": { 21 | "exclude": [ 22 | "api/vk.ts", 23 | "api/vk.min.js" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/compute/main.ts: -------------------------------------------------------------------------------- 1 | import * as vk from "../../mod.ts"; 2 | 3 | export class ComputeApp { 4 | instance!: vk.Instance; 5 | physicalDevice!: vk.PhysicalDevice; 6 | device!: vk.Device; 7 | queueFamilyIndex!: number; 8 | queue!: vk.Queue; 9 | descriptorPool!: vk.DescriptorPool; 10 | descriptorSetLayout!: vk.DescriptorSetLayout; 11 | descriptorSet!: vk.DescriptorSet; 12 | buffers!: vk.Buffer[]; 13 | bufferSizes!: number[]; 14 | pipelineLayout!: vk.PipelineLayout; 15 | 16 | constructor() { 17 | this.createInstance(); 18 | this.pickPhysicalDevice(); 19 | this.findQueueFamily(); 20 | this.createDevice(); 21 | this.createPipeline(); 22 | } 23 | 24 | createInstance() { 25 | const instOut = new vk.PointerRef(); 26 | vk.CreateInstance( 27 | new vk.InstanceCreateInfo({ 28 | pApplicationInfo: new vk.ApplicationInfo({ 29 | pApplicationName: new vk.CString("Vulkan Compute"), 30 | applicationVersion: vk.makeVersion(1, 0, 0), 31 | pEngineName: new vk.CString("Deno Vulkan"), 32 | engineVersion: vk.makeVersion(1, 0, 0), 33 | apiVersion: vk.makeVersion(1, 2, 0), 34 | }), 35 | enabledLayerCount: 1, 36 | ppEnabledLayerNames: new vk.CStringArray([ 37 | "VK_LAYER_KHRONOS_validation", 38 | ]), 39 | }), 40 | null, 41 | instOut, 42 | ); 43 | this.instance = instOut.value; 44 | } 45 | 46 | pickPhysicalDevice() { 47 | const physicalDeviceCount = new Uint32Array(1); 48 | vk.EnumeratePhysicalDevices(this.instance, physicalDeviceCount, null); 49 | 50 | const physicalDevices = new vk.PointerArray(physicalDeviceCount[0]); 51 | vk.EnumeratePhysicalDevices( 52 | this.instance, 53 | physicalDeviceCount, 54 | physicalDevices, 55 | ); 56 | 57 | const properties: vk.PhysicalDeviceProperties[] = []; 58 | 59 | for (let i = 0; i < physicalDevices.length; i++) { 60 | const physicalDevice = physicalDevices[i]; 61 | const property = new vk.PhysicalDeviceProperties(); 62 | vk.GetPhysicalDeviceProperties(physicalDevice, property); 63 | properties.push(property); 64 | } 65 | 66 | const names = properties.map((p) => { 67 | const buf = new Uint8Array(p.deviceName); 68 | const name = new TextDecoder().decode(buf.subarray(0, buf.indexOf(0))); 69 | return name; 70 | }); 71 | 72 | if (names.length === 0) { 73 | console.log("No physical devices found"); 74 | Deno.exit(1); 75 | } 76 | 77 | let physicalDevice: vk.PhysicalDevice; 78 | 79 | if (names.length === 1) { 80 | console.log("Using physical device:", names[0]); 81 | physicalDevice = physicalDevices[0]; 82 | } else { 83 | console.log("Multiple physical devices found:"); 84 | for (let i = 0; i < names.length; i++) { 85 | console.log(` - ${i}: ${names[i]}`); 86 | } 87 | 88 | const index = prompt("Select physical device: "); 89 | physicalDevice = physicalDevices[parseInt(index!)]; 90 | if (!physicalDevice) { 91 | console.log("Invalid physical device"); 92 | Deno.exit(1); 93 | } 94 | } 95 | 96 | this.physicalDevice = physicalDevice; 97 | } 98 | 99 | findQueueFamily() { 100 | const queueFamilyCount = new Uint32Array(1); 101 | vk.GetPhysicalDeviceQueueFamilyProperties( 102 | this.physicalDevice, 103 | queueFamilyCount, 104 | null, 105 | ); 106 | 107 | const queueFamilies = new vk.StructArray( 108 | queueFamilyCount[0], 109 | vk.QueueFamilyProperties, 110 | ); 111 | vk.GetPhysicalDeviceQueueFamilyProperties( 112 | this.physicalDevice, 113 | queueFamilyCount, 114 | queueFamilies, 115 | ); 116 | 117 | let queueFamilyIndex = 0; 118 | let found = false; 119 | for (const props of queueFamilies) { 120 | if (props.queueFlags & vk.QueueFlagBits.QUEUE_COMPUTE_BIT) { 121 | found = true; 122 | break; 123 | } 124 | queueFamilyIndex++; 125 | } 126 | 127 | if (!found) { 128 | throw new Error("No compute queue family found"); 129 | } 130 | 131 | this.queueFamilyIndex = queueFamilyIndex; 132 | } 133 | 134 | createDevice() { 135 | const deviceOut = new vk.PointerRef(); 136 | vk.CreateDevice( 137 | this.physicalDevice, 138 | new vk.DeviceCreateInfo({ 139 | queueCreateInfoCount: 1, 140 | pQueueCreateInfos: new vk.StructArray([ 141 | new vk.DeviceQueueCreateInfo({ 142 | queueFamilyIndex: this.queueFamilyIndex, 143 | queueCount: 1, 144 | pQueuePriorities: new Float32Array([1.0]), 145 | }), 146 | ], vk.DeviceQueueCreateInfo), 147 | }), 148 | null, 149 | deviceOut, 150 | ); 151 | this.device = deviceOut.value; 152 | 153 | const queueOut = new vk.PointerRef(); 154 | vk.GetDeviceQueue(this.device, this.queueFamilyIndex, 0, queueOut); 155 | this.queue = queueOut.value; 156 | } 157 | 158 | createPipeline() { 159 | const descriptorCount = 2; 160 | 161 | const descriptorPoolSize = new vk.DescriptorPoolSize({ 162 | type: vk.DescriptorType.DESCRIPTOR_TYPE_STORAGE_BUFFER, 163 | descriptorCount, 164 | }); 165 | 166 | const descriptorPoolOut = new vk.PointerRef(); 167 | vk.CreateDescriptorPool( 168 | this.device, 169 | new vk.DescriptorPoolCreateInfo({ 170 | maxSets: 1, 171 | poolSizeCount: 1, 172 | pPoolSizes: descriptorPoolSize, 173 | }), 174 | null, 175 | descriptorPoolOut, 176 | ); 177 | this.descriptorPool = descriptorPoolOut.value; 178 | 179 | const descriptorSetLayoutBindings = new vk.StructArray( 180 | descriptorCount, 181 | vk.DescriptorSetLayoutBinding, 182 | ); 183 | for (let i = 0; i < descriptorCount; i++) { 184 | const desc = descriptorSetLayoutBindings.get(i); 185 | desc.binding = i; 186 | desc.descriptorType = vk.DescriptorType.DESCRIPTOR_TYPE_STORAGE_BUFFER; 187 | desc.descriptorCount = 1; 188 | desc.stageFlags = vk.ShaderStageFlagBits.SHADER_STAGE_COMPUTE_BIT; 189 | } 190 | 191 | const descriptorSetLayoutOut = new vk.PointerRef(); 192 | vk.CreateDescriptorSetLayout( 193 | this.device, 194 | new vk.DescriptorSetLayoutCreateInfo({ 195 | bindingCount: descriptorCount, 196 | pBindings: descriptorSetLayoutBindings, 197 | }), 198 | null, 199 | descriptorSetLayoutOut, 200 | ); 201 | this.descriptorSetLayout = descriptorSetLayoutOut.value; 202 | 203 | const descriptorSetAllocateInfo = new vk.DescriptorSetAllocateInfo({ 204 | descriptorPool: descriptorPoolOut.value, 205 | descriptorSetCount: 1, 206 | pSetLayouts: new vk.PointerArray([descriptorSetLayoutOut.value]), 207 | }); 208 | 209 | const descriptorSetOut = new vk.PointerRef(); 210 | vk.AllocateDescriptorSets( 211 | this.device, 212 | descriptorSetAllocateInfo, 213 | descriptorSetOut, 214 | ); 215 | this.descriptorSet = descriptorSetOut.value; 216 | 217 | // for (let i = 0; i < descriptorCount; i++) { 218 | // const descriptorBufferInfo = new vk.DescriptorBufferInfo({ 219 | // buffer: this.buffers[i], 220 | // offset: 0, 221 | // range: this.bufferSizes[i], 222 | // }); 223 | 224 | // const writeDescriptorSet = new vk.WriteDescriptorSet({ 225 | // dstSet: descriptorSetOut.value, 226 | // dstBinding: i, 227 | // dstArrayElement: 0, 228 | // descriptorCount: 1, 229 | // descriptorType: vk.DescriptorType.DESCRIPTOR_TYPE_STORAGE_BUFFER, 230 | // pBufferInfo: descriptorBufferInfo, 231 | // }); 232 | 233 | // vk.UpdateDescriptorSets(this.device, 1, writeDescriptorSet, 0, null); 234 | // } 235 | 236 | // const pushConstantRange = new vk.PushConstantRange({ 237 | // stageFlags: vk.ShaderStageFlagBits.SHADER_STAGE_COMPUTE_BIT, 238 | // offset: 0, 239 | // size: 4, 240 | // }); 241 | 242 | // const pipelineLayoutOut = new vk.PointerRef(); 243 | // vk.CreatePipelineLayout( 244 | // this.device, 245 | // new vk.PipelineLayoutCreateInfo({ 246 | // setLayoutCount: 1, 247 | // pSetLayouts: new vk.PointerArray([descriptorSetLayoutOut.value]), 248 | // pushConstantRangeCount: 1, 249 | // pPushConstantRanges: pushConstantRange, 250 | // }), 251 | // null, 252 | // pipelineLayoutOut, 253 | // ); 254 | // this.pipelineLayout = pipelineLayoutOut.value; 255 | } 256 | } 257 | 258 | const app = new ComputeApp(); 259 | console.log(app); 260 | -------------------------------------------------------------------------------- /examples/triangle/main.ts: -------------------------------------------------------------------------------- 1 | import * as vk from "../../api/vk.ts"; 2 | import * as dwm from "https://deno.land/x/dwm@0.2.1/mod.ts"; 3 | 4 | export class TriangleApplication { 5 | window!: dwm.DwmWindow; 6 | 7 | instance!: vk.Instance; 8 | surface!: vk.SurfaceKHR; 9 | physicalDevice!: vk.PhysicalDevice; 10 | device!: vk.Device; 11 | graphicsQueue!: vk.Queue; 12 | presentQueue!: vk.Queue; 13 | deviceMemoryProperties!: vk.PhysicalDeviceMemoryProperties; 14 | imageAvailableSemaphore!: vk.Semaphore; 15 | renderingFinishedSemaphore!: vk.Semaphore; 16 | 17 | vertexBuffer!: vk.Buffer; 18 | vertexBufferMemory!: vk.DeviceMemory; 19 | indexBuffer!: vk.Buffer; 20 | indexBufferMemory!: vk.DeviceMemory; 21 | vertexBindingDescription!: vk.VertexInputBindingDescription; 22 | vertexAttributeDescriptions!: vk.VertexInputAttributeDescription[]; 23 | 24 | // deno-fmt-ignore 25 | uniformBufferData = new Float32Array([ 26 | 1.0, 0.0, 0.0, 0.0, 27 | 0.0, 1.0, 0.0, 0.0, 28 | 0.0, 0.0, 1.0, 0.0, 29 | 0.0, 0.0, 0.0, 1.0, 30 | ]); 31 | uniformBuffer!: vk.Buffer; 32 | uniformBufferMemory!: vk.DeviceMemory; 33 | descriptorSetLayout!: vk.DescriptorSetLayout; 34 | descriptorPool!: vk.DescriptorPool; 35 | descriptorSet!: vk.DescriptorSet; 36 | 37 | swapChainExtent!: vk.Extent2D; 38 | swapChainFormat!: vk.Format; 39 | oldSwapChain!: vk.SwapchainKHR; 40 | swapChain!: vk.SwapchainKHR; 41 | swapChainImages!: BigUint64Array; 42 | swapChainImageViews!: BigUint64Array; 43 | swapChainFramebuffers!: BigUint64Array; 44 | 45 | renderPass!: vk.RenderPass; 46 | graphicsPipeline!: vk.Pipeline; 47 | pipelineLayout!: vk.PipelineLayout; 48 | 49 | commandPool!: vk.CommandPool; 50 | graphicsCommandBuffers!: BigUint64Array; 51 | 52 | graphicsQueueFamily!: number; 53 | presentQueueFamily!: number; 54 | 55 | timeStart!: number; 56 | 57 | constructor() { 58 | this.timeStart = performance.now(); 59 | } 60 | 61 | async run() { 62 | this.window = dwm.createWindow({ 63 | title: "Test", 64 | width: 800, 65 | height: 600, 66 | resizable: true, 67 | noClientAPI: true, 68 | }); 69 | 70 | addEventListener("framebuffersize", (event) => { 71 | if (event.match(this.window)) { 72 | this.onWindowResized(); 73 | } 74 | }); 75 | 76 | await this.setupVulkan(); 77 | await this.mainloop(); 78 | this.cleanup(true); 79 | } 80 | 81 | async setupVulkan() { 82 | this.oldSwapChain = 0; 83 | 84 | this.createInstance(); 85 | this.createDebugCallback(); 86 | this.createWindowSurface(); 87 | this.findPhysicalDevice(); 88 | this.checkSwapChainSupport(); 89 | this.findQueueFamilies(); 90 | this.createLogicalDevice(); 91 | this.createSemaphores(); 92 | this.createCommandPool(); 93 | this.createVertexBuffer(); 94 | this.createUniformBuffer(); 95 | this.createSwapChain(); 96 | this.createRenderPass(); 97 | this.createImageViews(); 98 | this.createFramebuffers(); 99 | await this.createGraphicsPipeline(); 100 | this.createDescriptorPool(); 101 | this.createDescriptorSet(); 102 | this.createCommandBuffers(); 103 | } 104 | 105 | async mainloop() { 106 | await dwm.mainloop(async () => { 107 | this.updateUniformData(); 108 | await this.draw(); 109 | }); 110 | } 111 | 112 | resized = false; 113 | 114 | onWindowResized() { 115 | this.resized = true; 116 | } 117 | 118 | async onWindowSizeChanged() { 119 | this.resized = false; 120 | 121 | this.cleanup(false); 122 | 123 | this.createSwapChain(); 124 | this.createRenderPass(); 125 | this.createImageViews(); 126 | this.createFramebuffers(); 127 | await this.createGraphicsPipeline(); 128 | this.createCommandBuffers(); 129 | } 130 | 131 | cleanup(fullClean: boolean) { 132 | vk.DeviceWaitIdle(this.device); 133 | 134 | vk.FreeCommandBuffers( 135 | this.device, 136 | this.commandPool, 137 | this.graphicsCommandBuffers.length, 138 | this.graphicsCommandBuffers, 139 | ); 140 | 141 | vk.DestroyPipeline(this.device, this.graphicsPipeline, null); 142 | vk.DestroyRenderPass(this.device, this.renderPass, null); 143 | 144 | for (let i = 0; i < this.swapChainFramebuffers.length; i++) { 145 | vk.DestroyFramebuffer(this.device, this.swapChainFramebuffers[i], null); 146 | vk.DestroyImageView(this.device, this.swapChainImageViews[i], null); 147 | } 148 | 149 | vk.DestroyDescriptorSetLayout( 150 | this.device, 151 | this.descriptorSetLayout, 152 | null, 153 | ); 154 | 155 | if (fullClean) { 156 | vk.DestroySemaphore(this.device, this.imageAvailableSemaphore, null); 157 | vk.DestroySemaphore(this.device, this.renderingFinishedSemaphore, null); 158 | 159 | vk.DestroyCommandPool(this.device, this.commandPool, null); 160 | 161 | vk.DestroyDescriptorPool(this.device, this.descriptorPool, null); 162 | vk.DestroyBuffer(this.device, this.uniformBuffer, null); 163 | vk.FreeMemory(this.device, this.uniformBufferMemory, null); 164 | 165 | vk.DestroyBuffer(this.device, this.vertexBuffer, null); 166 | vk.FreeMemory(this.device, this.vertexBufferMemory, null); 167 | vk.DestroyBuffer(this.device, this.indexBuffer, null); 168 | vk.FreeMemory(this.device, this.indexBufferMemory, null); 169 | 170 | vk.DestroySwapchainKHR(this.device, this.swapChain, null); 171 | 172 | vk.DestroyDevice(this.device, null); 173 | 174 | vk.DestroySurfaceKHR(this.instance, this.surface, null); 175 | 176 | vk.DestroyInstance(this.instance, null); 177 | } 178 | } 179 | 180 | createInstance() { 181 | const appInfo = new vk.ApplicationInfo({ 182 | applicationVersion: vk.makeVersion(1, 0, 0), 183 | engineVersion: vk.makeVersion(1, 0, 0), 184 | apiVersion: vk.makeVersion(1, 2, 0), 185 | pApplicationName: new vk.CString("Test"), 186 | pEngineName: new vk.CString("No Engine"), 187 | }); 188 | 189 | const names = dwm.getRequiredInstanceExtensions(); 190 | const createInfo = new vk.InstanceCreateInfo({ 191 | pApplicationInfo: appInfo, 192 | enabledExtensionCount: names.length, 193 | ppEnabledExtensionNames: new vk.CStringArray(names), 194 | // enabledLayerCount: 1, 195 | ppEnabledLayerNames: new vk.CStringArray(["VK_LAYER_KHRONOS_validation"]), 196 | }); 197 | 198 | const instOut = new vk.PointerRef(); 199 | vk.CreateInstance(createInfo, null, instOut); 200 | this.instance = instOut.value; 201 | } 202 | 203 | createWindowSurface() { 204 | this.surface = this.window.createSurface(this.instance); 205 | } 206 | 207 | findPhysicalDevice() { 208 | const deviceCount = new Uint32Array(1); 209 | vk.EnumeratePhysicalDevices( 210 | this.instance, 211 | deviceCount, 212 | null, 213 | ); 214 | 215 | if (deviceCount[0] < 1) { 216 | throw new Error("No devices found"); 217 | } 218 | 219 | const physicalDeviceOut = new vk.PointerRef(); 220 | deviceCount[0] = 1; 221 | vk.EnumeratePhysicalDevices( 222 | this.instance, 223 | deviceCount, 224 | physicalDeviceOut, 225 | ); 226 | this.physicalDevice = physicalDeviceOut.value; 227 | } 228 | 229 | checkSwapChainSupport() {} 230 | 231 | findQueueFamilies() { 232 | const queueFamilyCount = new Uint32Array(1); 233 | vk.GetPhysicalDeviceQueueFamilyProperties( 234 | this.physicalDevice, 235 | queueFamilyCount.buffer, 236 | null, 237 | ); 238 | 239 | if (queueFamilyCount[0] < 1) { 240 | throw new Error("No queue families found"); 241 | } 242 | 243 | const queueFamilyPropertiesPtr = new vk.StructArray( 244 | queueFamilyCount[0], 245 | vk.QueueFamilyProperties, 246 | ); 247 | vk.GetPhysicalDeviceQueueFamilyProperties( 248 | this.physicalDevice, 249 | queueFamilyCount, 250 | queueFamilyPropertiesPtr, 251 | ); 252 | const queueFamilies = [...queueFamilyPropertiesPtr]; 253 | 254 | let foundGraphicsQueueFamily = false; 255 | let foundPresentQueueFamily = false; 256 | 257 | for (let i = 0; i < queueFamilyCount[0]; i++) { 258 | const presentSupport = new Uint32Array(1); 259 | vk.GetPhysicalDeviceSurfaceSupportKHR( 260 | this.physicalDevice, 261 | i, 262 | this.surface, 263 | presentSupport, 264 | ); 265 | 266 | if ( 267 | queueFamilies[i].queueCount > 0 && 268 | queueFamilies[i].queueFlags & vk.QueueFlagBits.GRAPHICS 269 | ) { 270 | this.graphicsQueueFamily = i; 271 | foundGraphicsQueueFamily = true; 272 | 273 | if (presentSupport[0] > 0) { 274 | this.presentQueueFamily = i; 275 | foundPresentQueueFamily = true; 276 | break; 277 | } 278 | } 279 | 280 | if (!foundPresentQueueFamily && presentSupport[0] > 0) { 281 | this.presentQueueFamily = i; 282 | foundPresentQueueFamily = true; 283 | } 284 | } 285 | 286 | if (!foundGraphicsQueueFamily) { 287 | throw new Error("No graphics queue family found"); 288 | } 289 | } 290 | 291 | createLogicalDevice() { 292 | const queuePriority = new Float32Array([1.0]); 293 | 294 | const pQueueCreateInfos = new vk.StructArray( 295 | 2, 296 | vk.DeviceQueueCreateInfo, 297 | ); 298 | 299 | const queueCreateInfo = [ 300 | pQueueCreateInfos.get(0), 301 | pQueueCreateInfos.get(1), 302 | ]; 303 | 304 | queueCreateInfo[0].sType = vk.StructureType.DEVICE_QUEUE_CREATE_INFO; 305 | queueCreateInfo[0].queueFamilyIndex = this.graphicsQueueFamily; 306 | queueCreateInfo[0].queueCount = 1; 307 | queueCreateInfo[0].pQueuePriorities = queuePriority; 308 | 309 | queueCreateInfo[1].sType = vk.StructureType.DEVICE_QUEUE_CREATE_INFO; 310 | queueCreateInfo[1].queueFamilyIndex = this.presentQueueFamily; 311 | queueCreateInfo[1].queueCount = 1; 312 | queueCreateInfo[1].pQueuePriorities = queuePriority; 313 | 314 | const enabledFeatures = new vk.PhysicalDeviceFeatures(); 315 | if (Deno.build.os !== "darwin") { 316 | enabledFeatures.shaderClipDistance = 1; 317 | enabledFeatures.shaderCullDistance = 1; 318 | } 319 | 320 | const deviceCreateInfo = new vk.DeviceCreateInfo({ 321 | pQueueCreateInfos, 322 | queueCreateInfoCount: this.graphicsQueueFamily === this.presentQueueFamily 323 | ? 1 324 | : 2, 325 | pEnabledFeatures: enabledFeatures, 326 | enabledExtensionCount: 1, 327 | ppEnabledExtensionNames: new vk.CStringArray([ 328 | vk.KHR_SWAPCHAIN_EXTENSION_NAME, 329 | ]), 330 | // enabledLayerCount: 1, 331 | // ppEnabledLayerNames: new vk.CStringArray(["VK_LAYER_KHRONOS_validation"]), 332 | }); 333 | 334 | const deviceOut = new vk.PointerRef(); 335 | vk.CreateDevice( 336 | this.physicalDevice, 337 | deviceCreateInfo, 338 | null, 339 | deviceOut, 340 | ); 341 | this.device = deviceOut.value; 342 | 343 | const graphicsQueueOut = new vk.PointerRef(); 344 | vk.GetDeviceQueue( 345 | this.device, 346 | this.graphicsQueueFamily, 347 | 0, 348 | graphicsQueueOut, 349 | ); 350 | this.graphicsQueue = graphicsQueueOut.value; 351 | const presentQueueOut = new vk.PointerRef(); 352 | vk.GetDeviceQueue( 353 | this.device, 354 | this.presentQueueFamily, 355 | 0, 356 | presentQueueOut, 357 | ); 358 | this.presentQueue = presentQueueOut.value; 359 | 360 | const deviceMemoryProperties = new vk.PhysicalDeviceMemoryProperties(); 361 | vk.GetPhysicalDeviceMemoryProperties( 362 | this.physicalDevice, 363 | deviceMemoryProperties, 364 | ); 365 | this.deviceMemoryProperties = deviceMemoryProperties; 366 | } 367 | 368 | createDebugCallback() {} 369 | 370 | createSemaphores() { 371 | const createInfo = new vk.SemaphoreCreateInfo({}); 372 | 373 | const imageAvailableSemaphoreOut = new vk.PointerRef(); 374 | const renderingFinishedSemaphoreOut = new vk.PointerRef(); 375 | 376 | vk.CreateSemaphore( 377 | this.device, 378 | createInfo, 379 | null, 380 | imageAvailableSemaphoreOut, 381 | ); 382 | vk.CreateSemaphore( 383 | this.device, 384 | createInfo, 385 | null, 386 | renderingFinishedSemaphoreOut, 387 | ); 388 | 389 | this.imageAvailableSemaphore = imageAvailableSemaphoreOut.value; 390 | this.renderingFinishedSemaphore = renderingFinishedSemaphoreOut.value; 391 | } 392 | 393 | createCommandPool() { 394 | const poolCreateInfo = new vk.CommandPoolCreateInfo({ 395 | queueFamilyIndex: this.graphicsQueueFamily, 396 | }); 397 | 398 | const commandPoolOut = new vk.PointerRef(); 399 | vk.CreateCommandPool( 400 | this.device, 401 | poolCreateInfo, 402 | null, 403 | commandPoolOut, 404 | ); 405 | this.commandPool = commandPoolOut.value; 406 | } 407 | 408 | createVertexBuffer() { 409 | // deno-fmt-ignore 410 | const vertices = new Float32Array([ 411 | -0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 412 | -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 413 | 0.5, 0.5, 0.0, 0.0, 0.0, 1.0, 414 | ]); 415 | 416 | const indices = new Uint16Array([0, 1, 2]); 417 | 418 | const memAlloc = new vk.MemoryAllocateInfo({}); 419 | const memReqs = new vk.MemoryRequirements(); 420 | 421 | const cmdBufInfo = new vk.CommandBufferAllocateInfo({ 422 | commandPool: this.commandPool, 423 | level: vk.CommandBufferLevel.PRIMARY, 424 | commandBufferCount: 1, 425 | }); 426 | 427 | const copyCommandBufferOut = new vk.PointerRef(); 428 | vk.AllocateCommandBuffers( 429 | this.device, 430 | cmdBufInfo, 431 | copyCommandBufferOut, 432 | ); 433 | const copyCommandBuffer = copyCommandBufferOut.value; 434 | 435 | const vertexBufferInfo = new vk.BufferCreateInfo({ 436 | size: vertices.byteLength, 437 | usage: vk.BufferUsageFlagBits.TRANSFER_SRC, 438 | }); 439 | 440 | const vertexBufferOut = new vk.PointerRef(); 441 | vk.CreateBuffer( 442 | this.device, 443 | vertexBufferInfo, 444 | null, 445 | vertexBufferOut, 446 | ); 447 | const vertexBuffer = vertexBufferOut.value; 448 | 449 | vk.GetBufferMemoryRequirements( 450 | this.device, 451 | vertexBuffer, 452 | memReqs, 453 | ); 454 | memAlloc.allocationSize = memReqs.size; 455 | memAlloc.memoryTypeIndex = this.getMemoryType( 456 | memReqs.memoryTypeBits, 457 | vk.MemoryPropertyFlagBits.HOST_VISIBLE, 458 | ); 459 | const vertexBufferMemoryOut = new vk.PointerRef(); 460 | vk.AllocateMemory( 461 | this.device, 462 | memAlloc, 463 | null, 464 | vertexBufferMemoryOut, 465 | ); 466 | const vertexBufferMemory = vertexBufferMemoryOut.value; 467 | 468 | const dataOut = new vk.PointerRef(); 469 | vk.MapMemory( 470 | this.device, 471 | vertexBufferMemory, 472 | 0, 473 | vertices.byteLength, 474 | 0, 475 | dataOut, 476 | ); 477 | vk.getBuffer(dataOut.value, vertices.byteLength, Float32Array).set( 478 | vertices, 479 | ); 480 | vk.UnmapMemory(this.device, vertexBufferMemory); 481 | vk.BindBufferMemory( 482 | this.device, 483 | vertexBuffer, 484 | vertexBufferMemory, 485 | 0, 486 | ); 487 | 488 | vertexBufferInfo.usage = vk.BufferUsageFlagBits.TRANSFER_DST | 489 | vk.BufferUsageFlagBits.VERTEX_BUFFER; 490 | vk.CreateBuffer( 491 | this.device, 492 | vertexBufferInfo, 493 | null, 494 | vertexBufferOut, 495 | ); 496 | this.vertexBuffer = vertexBufferOut.value; 497 | vk.GetBufferMemoryRequirements( 498 | this.device, 499 | this.vertexBuffer, 500 | memReqs, 501 | ); 502 | memAlloc.allocationSize = memReqs.size; 503 | memAlloc.memoryTypeIndex = this.getMemoryType( 504 | memReqs.memoryTypeBits, 505 | vk.MemoryPropertyFlagBits.DEVICE_LOCAL, 506 | ); 507 | vk.AllocateMemory( 508 | this.device, 509 | memAlloc, 510 | null, 511 | vertexBufferMemoryOut, 512 | ); 513 | this.vertexBufferMemory = vertexBufferMemoryOut.value; 514 | vk.BindBufferMemory( 515 | this.device, 516 | this.vertexBuffer, 517 | this.vertexBufferMemory, 518 | 0, 519 | ); 520 | 521 | const indexBufferInfo = new vk.BufferCreateInfo({ 522 | size: indices.byteLength, 523 | usage: vk.BufferUsageFlagBits.TRANSFER_SRC, 524 | }); 525 | 526 | const indexBufferOut = new vk.PointerRef(); 527 | vk.CreateBuffer( 528 | this.device, 529 | indexBufferInfo, 530 | null, 531 | indexBufferOut, 532 | ); 533 | const indexBuffer = indexBufferOut.value; 534 | vk.GetBufferMemoryRequirements( 535 | this.device, 536 | indexBuffer, 537 | memReqs, 538 | ); 539 | memAlloc.allocationSize = memReqs.size; 540 | memAlloc.memoryTypeIndex = this.getMemoryType( 541 | memReqs.memoryTypeBits, 542 | vk.MemoryPropertyFlagBits.HOST_VISIBLE, 543 | ); 544 | const indexBufferMemoryOut = new vk.PointerRef(); 545 | vk.AllocateMemory( 546 | this.device, 547 | memAlloc, 548 | null, 549 | indexBufferMemoryOut, 550 | ); 551 | const indexBufferMemory = indexBufferMemoryOut.value; 552 | vk.MapMemory( 553 | this.device, 554 | indexBufferMemory, 555 | 0, 556 | indices.byteLength, 557 | 0, 558 | dataOut, 559 | ); 560 | vk.getBuffer(dataOut.value, indices.byteLength, Uint16Array).set(indices); 561 | vk.UnmapMemory(this.device, indexBufferMemory); 562 | vk.BindBufferMemory( 563 | this.device, 564 | indexBuffer, 565 | indexBufferMemory, 566 | 0, 567 | ); 568 | 569 | indexBufferInfo.usage = vk.BufferUsageFlagBits.TRANSFER_DST | 570 | vk.BufferUsageFlagBits.INDEX_BUFFER; 571 | vk.CreateBuffer( 572 | this.device, 573 | indexBufferInfo, 574 | null, 575 | indexBufferOut, 576 | ); 577 | this.indexBuffer = indexBufferOut.value; 578 | vk.GetBufferMemoryRequirements( 579 | this.device, 580 | this.indexBuffer, 581 | memReqs, 582 | ); 583 | memAlloc.allocationSize = memReqs.size; 584 | memAlloc.memoryTypeIndex = this.getMemoryType( 585 | memReqs.memoryTypeBits, 586 | vk.MemoryPropertyFlagBits.DEVICE_LOCAL, 587 | ); 588 | vk.AllocateMemory( 589 | this.device, 590 | memAlloc, 591 | null, 592 | indexBufferMemoryOut, 593 | ); 594 | this.indexBufferMemory = indexBufferMemoryOut.value; 595 | vk.BindBufferMemory( 596 | this.device, 597 | this.indexBuffer, 598 | this.indexBufferMemory, 599 | 0, 600 | ); 601 | 602 | const bufferBeginInfo = new vk.CommandBufferBeginInfo({ 603 | flags: vk.CommandBufferUsageFlagBits.ONE_TIME_SUBMIT, 604 | }); 605 | 606 | vk.BeginCommandBuffer(copyCommandBuffer, bufferBeginInfo); 607 | 608 | const copyRegion = new vk.BufferCopy({ 609 | size: vertices.byteLength, 610 | }); 611 | vk.CmdCopyBuffer( 612 | copyCommandBuffer, 613 | vertexBuffer, 614 | this.vertexBuffer, 615 | 1, 616 | copyRegion, 617 | ); 618 | copyRegion.size = indices.byteLength; 619 | vk.CmdCopyBuffer( 620 | copyCommandBuffer, 621 | indexBuffer, 622 | this.indexBuffer, 623 | 1, 624 | copyRegion, 625 | ); 626 | 627 | vk.EndCommandBuffer(copyCommandBuffer); 628 | 629 | const submitInfo = new vk.SubmitInfo({ 630 | commandBufferCount: 1, 631 | pCommandBuffers: new vk.PointerArray([copyCommandBuffer]), 632 | }); 633 | 634 | vk.QueueSubmit(this.graphicsQueue, 1, submitInfo, 0); 635 | vk.QueueWaitIdle(this.graphicsQueue); 636 | 637 | vk.FreeCommandBuffers( 638 | this.device, 639 | this.commandPool, 640 | 1, 641 | new vk.PointerArray([copyCommandBuffer]), 642 | ); 643 | 644 | vk.DestroyBuffer(this.device, vertexBuffer, null); 645 | vk.FreeMemory(this.device, vertexBufferMemory, null); 646 | vk.DestroyBuffer(this.device, indexBuffer, null); 647 | vk.FreeMemory(this.device, indexBufferMemory, null); 648 | 649 | this.vertexBindingDescription = new vk.VertexInputBindingDescription({ 650 | binding: 0, 651 | stride: 4 * 3, 652 | inputRate: vk.VertexInputRate.VERTEX, 653 | }); 654 | 655 | this.vertexAttributeDescriptions = [ 656 | // vec2 position 657 | new vk.VertexInputAttributeDescription({ 658 | binding: 0, 659 | location: 0, 660 | format: vk.Format.R32G32B32_SFLOAT, 661 | }), 662 | // vec3 color 663 | new vk.VertexInputAttributeDescription({ 664 | binding: 0, 665 | location: 1, 666 | format: vk.Format.R32G32B32_SFLOAT, 667 | offset: 4 * 3, 668 | }), 669 | ]; 670 | } 671 | 672 | createUniformBuffer() { 673 | const bufferInfo = new vk.BufferCreateInfo({ 674 | size: 4 * 4 * 4, 675 | usage: vk.BufferUsageFlagBits.UNIFORM_BUFFER, 676 | }); 677 | 678 | const bufferOut = new vk.PointerRef(); 679 | vk.CreateBuffer( 680 | this.device, 681 | bufferInfo, 682 | null, 683 | bufferOut, 684 | ); 685 | this.uniformBuffer = bufferOut.value; 686 | 687 | const memReqs = new vk.MemoryRequirements(); 688 | vk.GetBufferMemoryRequirements( 689 | this.device, 690 | this.uniformBuffer, 691 | memReqs, 692 | ); 693 | 694 | const memAlloc = new vk.MemoryAllocateInfo({ 695 | allocationSize: memReqs.size, 696 | memoryTypeIndex: this.getMemoryType( 697 | memReqs.memoryTypeBits, 698 | vk.MemoryPropertyFlagBits.HOST_VISIBLE | 699 | vk.MemoryPropertyFlagBits.HOST_COHERENT, 700 | ), 701 | }); 702 | 703 | const bufferMemoryOut = new vk.PointerRef(); 704 | vk.AllocateMemory( 705 | this.device, 706 | memAlloc, 707 | null, 708 | bufferMemoryOut, 709 | ); 710 | this.uniformBufferMemory = bufferMemoryOut.value; 711 | 712 | vk.BindBufferMemory( 713 | this.device, 714 | this.uniformBuffer, 715 | this.uniformBufferMemory, 716 | 0, 717 | ); 718 | 719 | this.updateUniformData(); 720 | } 721 | 722 | updateUniformData() { 723 | // const timeNow = performance.now(); 724 | // const timeDelta = timeNow - this.timeStart; 725 | // const angle = (timeDelta % 4000) / 4000 * Math.PI * 2; 726 | 727 | const dataOut = new vk.PointerRef(); 728 | vk.MapMemory( 729 | this.device, 730 | this.uniformBufferMemory, 731 | 0, 732 | 4 * 4 * 4, 733 | 0, 734 | dataOut, 735 | ); 736 | vk.getBuffer(dataOut.value, this.uniformBufferData.byteLength, Float32Array) 737 | .set(this.uniformBufferData); 738 | vk.UnmapMemory(this.device, this.uniformBufferMemory); 739 | } 740 | 741 | getMemoryType( 742 | typeBits: number, 743 | properties: vk.Flags, 744 | ) { 745 | for (let i = 0; i < 32; i++) { 746 | if ((typeBits & 1) === 1) { 747 | if ( 748 | (this.deviceMemoryProperties.memoryTypes[i].propertyFlags & 749 | properties) === properties 750 | ) { 751 | return i; 752 | } 753 | } 754 | typeBits >>= 1; 755 | } 756 | return 0; 757 | } 758 | 759 | createSwapChain() { 760 | const surfaceCapabilities = new vk.SurfaceCapabilitiesKHR(); 761 | vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( 762 | this.physicalDevice, 763 | this.surface, 764 | surfaceCapabilities, 765 | ); 766 | 767 | const formatCountOut = new Uint32Array(1); 768 | vk.GetPhysicalDeviceSurfaceFormatsKHR( 769 | this.physicalDevice, 770 | this.surface, 771 | formatCountOut, 772 | null, 773 | ); 774 | 775 | const surfaceFormats = new vk.StructArray( 776 | formatCountOut[0], 777 | vk.SurfaceFormatKHR, 778 | ); 779 | vk.GetPhysicalDeviceSurfaceFormatsKHR( 780 | this.physicalDevice, 781 | this.surface, 782 | formatCountOut, 783 | surfaceFormats, 784 | ); 785 | 786 | const presentModeCountOut = new Uint32Array(1); 787 | vk.GetPhysicalDeviceSurfacePresentModesKHR( 788 | this.physicalDevice, 789 | this.surface, 790 | presentModeCountOut, 791 | null, 792 | ); 793 | 794 | const presentModes = new Uint32Array(presentModeCountOut[0]); 795 | vk.GetPhysicalDeviceSurfacePresentModesKHR( 796 | this.physicalDevice, 797 | this.surface, 798 | presentModeCountOut, 799 | presentModes, 800 | ); 801 | 802 | let imageCount = surfaceCapabilities.minImageCount + 1; 803 | if ( 804 | surfaceCapabilities.maxImageCount > 0 && 805 | imageCount > surfaceCapabilities.maxImageCount 806 | ) { 807 | imageCount = surfaceCapabilities.maxImageCount; 808 | } 809 | 810 | const surfaceFormat = this.chooseSurfaceFormat([...surfaceFormats]); 811 | 812 | this.swapChainExtent = this.chooseSwapExtent(surfaceCapabilities); 813 | 814 | let surfaceTransform = 0; 815 | if ( 816 | surfaceCapabilities.supportedTransforms & 817 | vk.SurfaceTransformFlagBitsKHR.SURFACE_TRANSFORM_IDENTITY_BIT_KHR 818 | ) { 819 | surfaceTransform = 820 | vk.SurfaceTransformFlagBitsKHR.SURFACE_TRANSFORM_IDENTITY_BIT_KHR; 821 | } else { 822 | surfaceTransform = surfaceCapabilities.currentTransform; 823 | } 824 | 825 | const presentMode = this.choosePresentMode([...presentModes]); 826 | 827 | const createInfo = new vk.SwapchainCreateInfoKHR({ 828 | surface: this.surface, 829 | minImageCount: imageCount, 830 | imageFormat: surfaceFormat.format, 831 | imageColorSpace: surfaceFormat.colorSpace, 832 | imageExtent: this.swapChainExtent, 833 | imageArrayLayers: 1, 834 | imageUsage: vk.ImageUsageFlagBits.COLOR_ATTACHMENT, 835 | imageSharingMode: vk.SharingMode.EXCLUSIVE, 836 | queueFamilyIndexCount: 0, 837 | pQueueFamilyIndices: 0, 838 | preTransform: surfaceTransform, 839 | compositeAlpha: 840 | vk.CompositeAlphaFlagBitsKHR.COMPOSITE_ALPHA_OPAQUE_BIT_KHR, 841 | presentMode, 842 | clipped: 1, 843 | oldSwapchain: this.oldSwapChain, 844 | }); 845 | 846 | const swapChainOut = new vk.PointerRef(); 847 | vk.CreateSwapchainKHR( 848 | this.device, 849 | createInfo, 850 | null, 851 | swapChainOut, 852 | ); 853 | this.swapChain = swapChainOut.value; 854 | 855 | if (this.oldSwapChain) { 856 | vk.DestroySwapchainKHR(this.device, this.oldSwapChain, null); 857 | } 858 | 859 | this.oldSwapChain = this.swapChain; 860 | 861 | this.swapChainFormat = surfaceFormat.format; 862 | 863 | const actualImageCount = new Uint32Array(1); 864 | vk.GetSwapchainImagesKHR( 865 | this.device, 866 | this.swapChain, 867 | actualImageCount, 868 | null, 869 | ); 870 | 871 | this.swapChainImages = new BigUint64Array(actualImageCount[0]); 872 | vk.GetSwapchainImagesKHR( 873 | this.device, 874 | this.swapChain, 875 | actualImageCount, 876 | this.swapChainImages, 877 | ); 878 | } 879 | 880 | chooseSurfaceFormat(availableFormats: vk.SurfaceFormatKHR[]) { 881 | if ( 882 | availableFormats.length === 1 && 883 | availableFormats[0].format === vk.Format.UNDEFINED 884 | ) { 885 | return new vk.SurfaceFormatKHR({ 886 | format: vk.Format.B8G8R8A8_UNORM, 887 | colorSpace: vk.ColorSpaceKHR.COLOR_SPACE_SRGB_NONLINEAR_KHR, 888 | }); 889 | } 890 | 891 | for (const availableFormat of availableFormats) { 892 | if ( 893 | availableFormat.format === vk.Format.B8G8R8A8_UNORM && 894 | availableFormat.colorSpace === 895 | vk.ColorSpaceKHR.COLOR_SPACE_SRGB_NONLINEAR_KHR 896 | ) { 897 | return availableFormat; 898 | } 899 | } 900 | 901 | return availableFormats[0]; 902 | } 903 | 904 | chooseSwapExtent(surfaceCapabilities: vk.SurfaceCapabilitiesKHR) { 905 | if (surfaceCapabilities.currentExtent.width === -1) { 906 | return new vk.Extent2D({ 907 | width: Math.min( 908 | Math.max( 909 | this.window.size.width, 910 | surfaceCapabilities.minImageExtent.width, 911 | ), 912 | surfaceCapabilities.maxImageExtent.width, 913 | ), 914 | height: Math.min( 915 | Math.max( 916 | this.window.size.height, 917 | surfaceCapabilities.minImageExtent.height, 918 | ), 919 | surfaceCapabilities.maxImageExtent.height, 920 | ), 921 | }); 922 | } else { 923 | return surfaceCapabilities.currentExtent; 924 | } 925 | } 926 | 927 | choosePresentMode(presentModes: vk.PresentModeKHR[]) { 928 | for (const presentMode of presentModes) { 929 | if (presentMode === vk.PresentModeKHR.PRESENT_MODE_MAILBOX_KHR) { 930 | return presentMode; 931 | } 932 | } 933 | return vk.PresentModeKHR.PRESENT_MODE_FIFO_KHR; 934 | } 935 | 936 | createRenderPass() { 937 | const attachmentDescription = new vk.AttachmentDescription({ 938 | format: this.swapChainFormat, 939 | samples: vk.SampleCountFlagBits.VK_1, 940 | loadOp: vk.AttachmentLoadOp.CLEAR, 941 | storeOp: vk.AttachmentStoreOp.STORE, 942 | stencilLoadOp: vk.AttachmentLoadOp.DONT_CARE, 943 | stencilStoreOp: vk.AttachmentStoreOp.DONT_CARE, 944 | initialLayout: vk.ImageLayout.UNDEFINED, 945 | finalLayout: vk.ImageLayout.PRESENT_SRC_KHR, 946 | }); 947 | 948 | const colorAttachment = new vk.AttachmentReference({ 949 | attachment: 0, 950 | layout: vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL, 951 | }); 952 | 953 | const subPassDescription = new vk.SubpassDescription({ 954 | pipelineBindPoint: vk.PipelineBindPoint.GRAPHICS, 955 | colorAttachmentCount: 1, 956 | pColorAttachments: colorAttachment, 957 | }); 958 | 959 | const createInfo = new vk.RenderPassCreateInfo({ 960 | attachmentCount: 1, 961 | pAttachments: attachmentDescription, 962 | subpassCount: 1, 963 | pSubpasses: subPassDescription, 964 | }); 965 | 966 | const renderPassOut = new vk.PointerRef(); 967 | vk.CreateRenderPass( 968 | this.device, 969 | createInfo, 970 | null, 971 | renderPassOut, 972 | ); 973 | this.renderPass = renderPassOut.value; 974 | } 975 | 976 | createImageViews() { 977 | this.swapChainImageViews = new BigUint64Array(this.swapChainImages.length); 978 | 979 | for (let i = 0; i < this.swapChainImages.length; i++) { 980 | const components = new vk.ComponentMapping({ 981 | r: vk.ComponentSwizzle.IDENTITY, 982 | g: vk.ComponentSwizzle.IDENTITY, 983 | b: vk.ComponentSwizzle.IDENTITY, 984 | a: vk.ComponentSwizzle.IDENTITY, 985 | }); 986 | 987 | const subresourceRange = new vk.ImageSubresourceRange({ 988 | aspectMask: vk.ImageAspectFlagBits.COLOR, 989 | baseMipLevel: 0, 990 | levelCount: 1, 991 | baseArrayLayer: 0, 992 | layerCount: 1, 993 | }); 994 | 995 | const createInfo = new vk.ImageViewCreateInfo({ 996 | image: this.swapChainImages[i], 997 | viewType: vk.ImageViewType.VK_2D, 998 | format: this.swapChainFormat, 999 | components, 1000 | subresourceRange, 1001 | }); 1002 | 1003 | const imageViewOut = new vk.PointerRef(); 1004 | vk.CreateImageView( 1005 | this.device, 1006 | createInfo, 1007 | null, 1008 | imageViewOut, 1009 | ); 1010 | this.swapChainImageViews[i] = BigInt(imageViewOut.value); 1011 | } 1012 | } 1013 | 1014 | createFramebuffers() { 1015 | this.swapChainFramebuffers = new BigUint64Array( 1016 | this.swapChainImages.length, 1017 | ); 1018 | 1019 | for (let i = 0; i < this.swapChainImages.length; i++) { 1020 | const createInfo = new vk.FramebufferCreateInfo({ 1021 | renderPass: this.renderPass, 1022 | attachmentCount: 1, 1023 | pAttachments: new vk.PointerArray([this.swapChainImageViews[i]]), 1024 | width: this.swapChainExtent.width, 1025 | height: this.swapChainExtent.height, 1026 | layers: 1, 1027 | }); 1028 | 1029 | const framebufferOut = new vk.PointerRef(); 1030 | vk.CreateFramebuffer( 1031 | this.device, 1032 | createInfo, 1033 | null, 1034 | framebufferOut, 1035 | ); 1036 | 1037 | this.swapChainFramebuffers[i] = BigInt(framebufferOut.value); 1038 | } 1039 | } 1040 | 1041 | async createShaderModule(file: string | URL) { 1042 | const data = new Uint8Array( 1043 | await (await (await fetch(file)).arrayBuffer()), 1044 | ); 1045 | 1046 | const createInfo = new vk.ShaderModuleCreateInfo({ 1047 | codeSize: data.byteLength, 1048 | pCode: data, 1049 | }); 1050 | 1051 | const shaderModuleOut = new vk.PointerRef(); 1052 | vk.CreateShaderModule( 1053 | this.device, 1054 | createInfo, 1055 | null, 1056 | shaderModuleOut, 1057 | ); 1058 | return shaderModuleOut.value; 1059 | } 1060 | 1061 | async createGraphicsPipeline() { 1062 | const vertexShaderModule = await this.createShaderModule( 1063 | new URL("./shaders/vert.spv", import.meta.url), 1064 | ); 1065 | const fragmentShaderModule = await this.createShaderModule( 1066 | new URL("./shaders/frag.spv", import.meta.url), 1067 | ); 1068 | 1069 | const vertexShaderCreateInfo = new vk.PipelineShaderStageCreateInfo({ 1070 | stage: vk.ShaderStageFlagBits.VERTEX, 1071 | module: vertexShaderModule, 1072 | pName: new vk.CString("main"), 1073 | }); 1074 | 1075 | const fragmentShaderCreateInfo = new vk.PipelineShaderStageCreateInfo({ 1076 | stage: vk.ShaderStageFlagBits.FRAGMENT, 1077 | module: fragmentShaderModule, 1078 | pName: new vk.CString("main"), 1079 | }); 1080 | 1081 | const shaderStages = new vk.StructArray( 1082 | [ 1083 | vertexShaderCreateInfo, 1084 | fragmentShaderCreateInfo, 1085 | ], 1086 | vk.PipelineShaderStageCreateInfo, 1087 | ); 1088 | 1089 | const vertexAttributeDescriptions = new vk.StructArray( 1090 | this.vertexAttributeDescriptions, 1091 | vk.VertexInputAttributeDescription, 1092 | ); 1093 | 1094 | const vertexInputCreateInfo = new vk.PipelineVertexInputStateCreateInfo({ 1095 | vertexBindingDescriptionCount: 1, 1096 | pVertexBindingDescriptions: this.vertexBindingDescription, 1097 | vertexAttributeDescriptionCount: 2, 1098 | pVertexAttributeDescriptions: vertexAttributeDescriptions, 1099 | }); 1100 | 1101 | const inputAssemblyCreateInfo = new vk.PipelineInputAssemblyStateCreateInfo( 1102 | { 1103 | topology: vk.PrimitiveTopology.TRIANGLE_LIST, 1104 | primitiveRestartEnable: 0, 1105 | }, 1106 | ); 1107 | 1108 | const viewport = new vk.Viewport({ 1109 | x: 0, 1110 | y: 0, 1111 | width: this.swapChainExtent.width, 1112 | height: this.swapChainExtent.height, 1113 | minDepth: 0, 1114 | maxDepth: 1, 1115 | }); 1116 | 1117 | const scissor = new vk.Rect2D({ 1118 | offset: new vk.Offset2D({ x: 0, y: 0 }), 1119 | extent: this.swapChainExtent, 1120 | }); 1121 | 1122 | const viewportCreateInfo = new vk.PipelineViewportStateCreateInfo({ 1123 | viewportCount: 1, 1124 | pViewports: viewport, 1125 | scissorCount: 1, 1126 | pScissors: scissor, 1127 | }); 1128 | 1129 | const rasterizerCreateInfo = new vk.PipelineRasterizationStateCreateInfo({ 1130 | depthClampEnable: 0, 1131 | rasterizerDiscardEnable: 0, 1132 | polygonMode: vk.PolygonMode.FILL, 1133 | cullMode: vk.CullModeFlagBits.BACK, 1134 | frontFace: vk.FrontFace.CLOCKWISE, 1135 | depthBiasEnable: 0, 1136 | depthBiasConstantFactor: 0, 1137 | depthBiasClamp: 0, 1138 | depthBiasSlopeFactor: 0, 1139 | lineWidth: 1, 1140 | }); 1141 | 1142 | const multisampleCreateInfo = new vk.PipelineMultisampleStateCreateInfo({ 1143 | sampleShadingEnable: 0, 1144 | rasterizationSamples: vk.SampleCountFlagBits.VK_1, 1145 | minSampleShading: 1, 1146 | alphaToCoverageEnable: 0, 1147 | alphaToOneEnable: 0, 1148 | }); 1149 | 1150 | const colorBlendAttachmentState = new vk.PipelineColorBlendAttachmentState({ 1151 | blendEnable: 0, 1152 | srcColorBlendFactor: vk.BlendFactor.ONE, 1153 | dstColorBlendFactor: vk.BlendFactor.ZERO, 1154 | colorBlendOp: vk.BlendOp.ADD, 1155 | srcAlphaBlendFactor: vk.BlendFactor.ONE, 1156 | dstAlphaBlendFactor: vk.BlendFactor.ZERO, 1157 | alphaBlendOp: vk.BlendOp.ADD, 1158 | colorWriteMask: vk.ColorComponentFlagBits.R | 1159 | vk.ColorComponentFlagBits.G | 1160 | vk.ColorComponentFlagBits.B | 1161 | vk.ColorComponentFlagBits.A, 1162 | }); 1163 | 1164 | const colorBlendCreateInfo = new vk.PipelineColorBlendStateCreateInfo({ 1165 | logicOpEnable: 0, 1166 | logicOp: vk.LogicOp.COPY, 1167 | attachmentCount: 1, 1168 | pAttachments: colorBlendAttachmentState, 1169 | blendConstants: new Float32Array([0, 0, 0, 0]), 1170 | }); 1171 | 1172 | const layoutBinding = new vk.DescriptorSetLayoutBinding({ 1173 | descriptorType: vk.DescriptorType.UNIFORM_BUFFER, 1174 | descriptorCount: 1, 1175 | stageFlags: vk.ShaderStageFlagBits.VERTEX, 1176 | }); 1177 | 1178 | const descriptorLayoutCreateInfo = new vk.DescriptorSetLayoutCreateInfo({ 1179 | bindingCount: 1, 1180 | pBindings: layoutBinding, 1181 | }); 1182 | 1183 | const descriptorSetLayoutOut = new vk.PointerRef(); 1184 | vk.CreateDescriptorSetLayout( 1185 | this.device, 1186 | descriptorLayoutCreateInfo, 1187 | null, 1188 | descriptorSetLayoutOut, 1189 | ); 1190 | this.descriptorSetLayout = descriptorSetLayoutOut.value; 1191 | 1192 | const layoutCreateInfo = new vk.PipelineLayoutCreateInfo({ 1193 | setLayoutCount: 1, 1194 | pSetLayouts: new vk.PointerArray([this.descriptorSetLayout]), 1195 | }); 1196 | 1197 | const pipelineLayoutOut = new vk.PointerRef(); 1198 | vk.CreatePipelineLayout( 1199 | this.device, 1200 | layoutCreateInfo, 1201 | null, 1202 | pipelineLayoutOut, 1203 | ); 1204 | this.pipelineLayout = pipelineLayoutOut.value; 1205 | 1206 | const pipelineCreateInfo = new vk.GraphicsPipelineCreateInfo({ 1207 | stageCount: 2, 1208 | pStages: shaderStages, 1209 | pVertexInputState: vertexInputCreateInfo, 1210 | pInputAssemblyState: inputAssemblyCreateInfo, 1211 | pViewportState: viewportCreateInfo, 1212 | pRasterizationState: rasterizerCreateInfo, 1213 | pMultisampleState: multisampleCreateInfo, 1214 | pColorBlendState: colorBlendCreateInfo, 1215 | layout: this.pipelineLayout, 1216 | renderPass: this.renderPass, 1217 | subpass: 0, 1218 | basePipelineHandle: 0, 1219 | basePipelineIndex: -1, 1220 | }); 1221 | 1222 | const pipelineOut = new vk.PointerRef(); 1223 | vk.CreateGraphicsPipelines( 1224 | this.device, 1225 | 0, 1226 | 1, 1227 | pipelineCreateInfo, 1228 | null, 1229 | pipelineOut, 1230 | ); 1231 | this.graphicsPipeline = pipelineOut.value; 1232 | 1233 | vk.DestroyShaderModule(this.device, vertexShaderModule, null); 1234 | vk.DestroyShaderModule(this.device, fragmentShaderModule, null); 1235 | } 1236 | 1237 | createDescriptorPool() { 1238 | const typeCount = new vk.DescriptorPoolSize({ 1239 | type: vk.DescriptorType.UNIFORM_BUFFER, 1240 | descriptorCount: 1, 1241 | }); 1242 | 1243 | const createInfo = new vk.DescriptorPoolCreateInfo({ 1244 | poolSizeCount: 1, 1245 | pPoolSizes: typeCount, 1246 | maxSets: 1, 1247 | }); 1248 | 1249 | const poolOut = new vk.PointerRef(); 1250 | vk.CreateDescriptorPool( 1251 | this.device, 1252 | createInfo, 1253 | null, 1254 | poolOut, 1255 | ); 1256 | this.descriptorPool = poolOut.value; 1257 | } 1258 | 1259 | createDescriptorSet() { 1260 | const allocInfo = new vk.DescriptorSetAllocateInfo({ 1261 | descriptorPool: this.descriptorPool, 1262 | descriptorSetCount: 1, 1263 | pSetLayouts: new vk.PointerArray([this.descriptorSetLayout]), 1264 | }); 1265 | 1266 | const descriptorSetOut = new vk.PointerRef(); 1267 | vk.AllocateDescriptorSets( 1268 | this.device, 1269 | allocInfo, 1270 | descriptorSetOut, 1271 | ); 1272 | this.descriptorSet = descriptorSetOut.value; 1273 | 1274 | const descriptorBufferInfo = new vk.DescriptorBufferInfo({ 1275 | buffer: this.uniformBuffer, 1276 | offset: 0, 1277 | range: 64, 1278 | }); 1279 | 1280 | const writeDescriptorSet = new vk.WriteDescriptorSet({ 1281 | dstSet: this.descriptorSet, 1282 | descriptorCount: 1, 1283 | descriptorType: vk.DescriptorType.UNIFORM_BUFFER, 1284 | pBufferInfo: descriptorBufferInfo, 1285 | dstBinding: 0, 1286 | }); 1287 | 1288 | vk.UpdateDescriptorSets( 1289 | this.device, 1290 | 1, 1291 | writeDescriptorSet, 1292 | 0, 1293 | null, 1294 | ); 1295 | } 1296 | 1297 | createCommandBuffers() { 1298 | this.graphicsCommandBuffers = new BigUint64Array( 1299 | this.swapChainImages.length, 1300 | ); 1301 | 1302 | const allocInfo = new vk.CommandBufferAllocateInfo({ 1303 | commandPool: this.commandPool, 1304 | level: vk.CommandBufferLevel.PRIMARY, 1305 | commandBufferCount: this.graphicsCommandBuffers.length, 1306 | }); 1307 | 1308 | vk.AllocateCommandBuffers( 1309 | this.device, 1310 | allocInfo, 1311 | this.graphicsCommandBuffers, 1312 | ); 1313 | 1314 | const beginInfo = new vk.CommandBufferBeginInfo({ 1315 | flags: vk.CommandBufferUsageFlagBits.SIMULTANEOUS_USE, 1316 | }); 1317 | 1318 | const subResourceRange = new vk.ImageSubresourceRange({ 1319 | aspectMask: vk.ImageAspectFlagBits.COLOR, 1320 | baseMipLevel: 0, 1321 | levelCount: 1, 1322 | baseArrayLayer: 0, 1323 | layerCount: 1, 1324 | }); 1325 | 1326 | const clearValues = new Float32Array([0.0, 0.0, 0.0, 1.0]); 1327 | 1328 | for (let i = 0; i < this.graphicsCommandBuffers.length; i++) { 1329 | vk.BeginCommandBuffer(this.graphicsCommandBuffers[i], beginInfo); 1330 | 1331 | const presentToDrawBarrier = new vk.ImageMemoryBarrier({ 1332 | srcAccessMask: 0, 1333 | dstAccessMask: vk.AccessFlagBits.COLOR_ATTACHMENT_WRITE, 1334 | oldLayout: vk.ImageLayout.UNDEFINED, 1335 | newLayout: vk.ImageLayout.PRESENT_SRC_KHR, 1336 | }); 1337 | 1338 | if (this.presentQueueFamily !== this.graphicsQueueFamily) { 1339 | presentToDrawBarrier.srcQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED; 1340 | presentToDrawBarrier.dstQueueFamilyIndex = vk.QUEUE_FAMILY_IGNORED; 1341 | } else { 1342 | presentToDrawBarrier.srcQueueFamilyIndex = this.presentQueueFamily; 1343 | presentToDrawBarrier.dstQueueFamilyIndex = this.graphicsQueueFamily; 1344 | } 1345 | 1346 | presentToDrawBarrier.image = this.swapChainImages[i]; 1347 | presentToDrawBarrier.subresourceRange = subResourceRange; 1348 | 1349 | vk.CmdPipelineBarrier( 1350 | this.graphicsCommandBuffers[i], 1351 | vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT, 1352 | vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT, 1353 | 0, 1354 | 0, 1355 | null, 1356 | 0, 1357 | null, 1358 | 1, 1359 | presentToDrawBarrier, 1360 | ); 1361 | 1362 | const renderPassBeginInfo = new vk.RenderPassBeginInfo({ 1363 | renderPass: this.renderPass, 1364 | framebuffer: this.swapChainFramebuffers[i], 1365 | renderArea: new vk.Rect2D({ 1366 | offset: new vk.Offset2D({ x: 0, y: 0 }), 1367 | extent: this.swapChainExtent, 1368 | }), 1369 | clearValueCount: 1, 1370 | pClearValues: clearValues, 1371 | }); 1372 | 1373 | vk.CmdBeginRenderPass( 1374 | this.graphicsCommandBuffers[i], 1375 | renderPassBeginInfo, 1376 | vk.SubpassContents.INLINE, 1377 | ); 1378 | 1379 | vk.CmdBindDescriptorSets( 1380 | this.graphicsCommandBuffers[i], 1381 | vk.PipelineBindPoint.GRAPHICS, 1382 | this.pipelineLayout, 1383 | 0, 1384 | 1, 1385 | new vk.PointerArray([this.descriptorSet]), 1386 | 0, 1387 | null, 1388 | ); 1389 | 1390 | vk.CmdBindPipeline( 1391 | this.graphicsCommandBuffers[i], 1392 | vk.PipelineBindPoint.GRAPHICS, 1393 | this.graphicsPipeline, 1394 | ); 1395 | 1396 | const offset = new vk.PointerRef(); 1397 | vk.CmdBindVertexBuffers( 1398 | this.graphicsCommandBuffers[i], 1399 | 0, 1400 | 1, 1401 | new vk.PointerArray([this.vertexBuffer]), 1402 | offset, 1403 | ); 1404 | 1405 | vk.CmdBindIndexBuffer( 1406 | this.graphicsCommandBuffers[i], 1407 | this.indexBuffer, 1408 | 0, 1409 | vk.IndexType.UINT16, 1410 | ); 1411 | 1412 | vk.CmdDrawIndexed(this.graphicsCommandBuffers[i], 3, 1, 0, 0, 0); 1413 | 1414 | vk.CmdEndRenderPass(this.graphicsCommandBuffers[i]); 1415 | 1416 | if (this.presentQueueFamily !== this.graphicsQueueFamily) { 1417 | const drawToPresentBarrier = new vk.ImageMemoryBarrier({ 1418 | srcAccessMask: vk.AccessFlagBits.COLOR_ATTACHMENT_WRITE, 1419 | dstAccessMask: vk.AccessFlagBits.MEMORY_READ, 1420 | oldLayout: vk.ImageLayout.PRESENT_SRC_KHR, 1421 | newLayout: vk.ImageLayout.PRESENT_SRC_KHR, 1422 | srcQueueFamilyIndex: this.graphicsQueueFamily, 1423 | dstQueueFamilyIndex: this.presentQueueFamily, 1424 | image: this.swapChainImages[i], 1425 | subresourceRange: subResourceRange, 1426 | }); 1427 | 1428 | vk.CmdPipelineBarrier( 1429 | this.graphicsCommandBuffers[i], 1430 | vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT, 1431 | vk.PipelineStageFlagBits.BOTTOM_OF_PIPE, 1432 | 0, 1433 | 0, 1434 | null, 1435 | 0, 1436 | null, 1437 | 1, 1438 | drawToPresentBarrier, 1439 | ); 1440 | } 1441 | 1442 | vk.EndCommandBuffer(this.graphicsCommandBuffers[i]); 1443 | } 1444 | 1445 | vk.DestroyPipelineLayout(this.device, this.pipelineLayout, null); 1446 | } 1447 | 1448 | async draw() { 1449 | const imageIndex = new Uint32Array(1); 1450 | try { 1451 | vk.AcquireNextImageKHR( 1452 | this.device, 1453 | this.swapChain, 1454 | 1000000000, 1455 | this.imageAvailableSemaphore, 1456 | 0, 1457 | imageIndex, 1458 | ); 1459 | // deno-lint-ignore no-explicit-any 1460 | } catch (e: any) { 1461 | if (e.code === vk.Result.ERROR_OUT_OF_DATE_KHR) { 1462 | await this.onWindowSizeChanged(); 1463 | return; 1464 | } else { 1465 | throw e; 1466 | } 1467 | } 1468 | 1469 | const submitInfo = new vk.SubmitInfo({ 1470 | waitSemaphoreCount: 1, 1471 | pWaitSemaphores: new vk.PointerArray([this.imageAvailableSemaphore]), 1472 | signalSemaphoreCount: 1, 1473 | pSignalSemaphores: new vk.PointerArray([this.renderingFinishedSemaphore]), 1474 | pWaitDstStageMask: new Uint32Array([ 1475 | vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT, 1476 | ]), 1477 | commandBufferCount: 1, 1478 | pCommandBuffers: new vk.PointerArray([ 1479 | this.graphicsCommandBuffers[imageIndex[0]], 1480 | ]), 1481 | }); 1482 | 1483 | vk.QueueSubmit(this.graphicsQueue, 1, submitInfo, 0); 1484 | 1485 | const presentInfo = new vk.PresentInfoKHR({ 1486 | waitSemaphoreCount: 1, 1487 | pWaitSemaphores: new vk.PointerArray([this.renderingFinishedSemaphore]), 1488 | swapchainCount: 1, 1489 | pSwapchains: new vk.PointerArray([this.swapChain]), 1490 | pImageIndices: imageIndex, 1491 | }); 1492 | 1493 | try { 1494 | const res = vk.QueuePresentKHR(this.presentQueue, presentInfo); 1495 | if (res === vk.Result.SUBOPTIMAL_KHR || this.resized) { 1496 | this.onWindowSizeChanged(); 1497 | } 1498 | // deno-lint-ignore no-explicit-any 1499 | } catch (e: any) { 1500 | if (e.code === vk.Result.ERROR_OUT_OF_DATE_KHR) { 1501 | this.onWindowSizeChanged(); 1502 | } else { 1503 | throw e; 1504 | } 1505 | } 1506 | } 1507 | } 1508 | 1509 | if (!dwm.vulkanSupported()) { 1510 | console.log("Vulkan not supported"); 1511 | Deno.exit(1); 1512 | } 1513 | 1514 | const app = new TriangleApplication(); 1515 | await app.run(); 1516 | -------------------------------------------------------------------------------- /examples/triangle/shaders/frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /examples/triangle/shaders/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deno-windowing/vulkan/34b72ebe261eb8dd37cfcc6d6808fda68f74b982/examples/triangle/shaders/frag.spv -------------------------------------------------------------------------------- /examples/triangle/shaders/vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec3 fragColor; 4 | 5 | vec2 positions[3] = vec2[]( 6 | vec2(0.0, -0.5), 7 | vec2(0.5, 0.5), 8 | vec2(-0.5, 0.5) 9 | ); 10 | 11 | vec3 colors[3] = vec3[]( 12 | vec3(1.0, 0.0, 0.0), 13 | vec3(0.0, 1.0, 0.0), 14 | vec3(0.0, 0.0, 1.0) 15 | ); 16 | 17 | void main() { 18 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 19 | fragColor = colors[gl_VertexIndex]; 20 | } 21 | -------------------------------------------------------------------------------- /examples/triangle/shaders/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deno-windowing/vulkan/34b72ebe261eb8dd37cfcc6d6808fda68f74b982/examples/triangle/shaders/vert.spv -------------------------------------------------------------------------------- /generator/emitter.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "https://deno.land/x/swc@0.2.1/mod.ts"; 2 | import { 3 | block, 4 | commands, 5 | constants, 6 | emit, 7 | enums, 8 | Field, 9 | getIdent, 10 | getTypeSize, 11 | isArray, 12 | isStruct, 13 | jsify, 14 | newline, 15 | output, 16 | structs, 17 | tymap, 18 | typedefs, 19 | typeToJS, 20 | unions, 21 | } from "./process_xml.ts"; 22 | 23 | function stripVk(name: any) { 24 | if (typeof name !== "string") return name; 25 | if (name.startsWith("Vk") || name.startsWith("vk")) return name.slice(2); 26 | else if (name.startsWith("VK_")) return name.slice(3); 27 | return name; 28 | } 29 | 30 | function toConstCase(name: string) { 31 | return name 32 | .split(/(?=[A-Z])/) 33 | .map((x) => x.toUpperCase()) 34 | .join("_"); 35 | } 36 | 37 | console.log("Emitting..."); 38 | 39 | newline(); 40 | 41 | emit( 42 | `import { AnyBuffer, AnyPointer, anyBuffer, anyPointer, BUFFER, DATAVIEW, LE, BaseStruct } from "./util.ts";`, 43 | ); 44 | 45 | newline(); 46 | emit("/// Type definitions"); 47 | 48 | for (const ty of typedefs) { 49 | newline(); 50 | emit(`export type ${stripVk(ty.name)} = ${stripVk(ty.type)};`); 51 | } 52 | 53 | newline(); 54 | emit("/// Constants"); 55 | 56 | for (const e of constants) { 57 | newline(); 58 | emit(`/// ${e.name}`); 59 | if (e.comment) emit(`/// ${e.comment}`); 60 | newline(); 61 | for (const c of e.constants) { 62 | if (c.name.includes("_SPEC_VERSION")) continue; 63 | if (c.comment) emit(`/** ${c.comment} */`); 64 | emit(`export const ${stripVk(c.name)} = ${stripVk(c.value)};`); 65 | } 66 | } 67 | 68 | newline(); 69 | emit("/// Enums"); 70 | 71 | for (const e of enums) { 72 | newline(); 73 | if (e.comment) emit(`/** ${e.comment} */`); 74 | emit(`export enum ${stripVk(e.name)} {`); 75 | const ec = toConstCase(stripVk(e.name)).replace("_FLAG_BITS", ""); 76 | block(() => { 77 | const pushed: string[] = []; 78 | for (const c of e.enums) { 79 | if (c.comment) emit(`/** ${c.comment} */`); 80 | const n = stripVk(c.name); 81 | function maybeSlice(x: string, vkOnly = false) { 82 | if (typeof x !== "string") return x; 83 | if (vkOnly && !x.startsWith("VK_")) return x; 84 | x = stripVk(x); 85 | let sliced = x.startsWith?.(ec + "_") ? x.slice(ec.length + 1) : x; 86 | if (e.name.endsWith("FlagBits") && sliced.endsWith("_BIT")) { 87 | sliced = sliced.slice(0, -4); 88 | } 89 | if (sliced.match?.(/^[0-9]/)) sliced = "VK_" + sliced; 90 | return sliced; 91 | } 92 | const final = maybeSlice(n); 93 | if (pushed.includes(final)) continue; 94 | pushed.push(final); 95 | emit( 96 | `${final} = ${ 97 | typeof c.value === "string" && c.value.startsWith("VK") 98 | ? maybeSlice( 99 | e.enums.find((x) => x.name === c.value)?.value ?? c.value, 100 | true, 101 | ) 102 | : c.value 103 | },`, 104 | ); 105 | } 106 | }); 107 | emit(`}`); 108 | } 109 | 110 | newline(); 111 | emit("/// Structs"); 112 | 113 | for (const s of structs) { 114 | newline(); 115 | emit(`export interface Init${stripVk(s.name)} {`); 116 | block(() => { 117 | for (const f of s.fields) { 118 | if (f.name === "sType" && f.values?.length === 1) continue; 119 | const nativearr = isArray(f.ffi) && (f.ffi.array in tymap); 120 | emit( 121 | `${jsify(f.name)}?: ${ 122 | nativearr 123 | ? tymap[f.ffi.array as keyof typeof tymap] 124 | : f.text?.endsWith("*") 125 | ? "AnyPointer" 126 | : stripVk(typeToJS(f.type)) 127 | }${f.len && !nativearr ? "[]" : ""};`, 128 | ); 129 | } 130 | }); 131 | emit("}"); 132 | newline(); 133 | if (s.comment) emit(`/** ${s.comment} */`); 134 | emit(`export class ${stripVk(s.name)} implements BaseStruct {`); 135 | block(() => { 136 | emit(`static size = ${s.size};`); 137 | 138 | newline(); 139 | 140 | emit("#data!: Uint8Array;"); 141 | emit("#view!: DataView;"); 142 | 143 | newline(); 144 | 145 | emit(`get [BUFFER]() { return this.#data; }`); 146 | emit(`get [DATAVIEW]() { return this.#view; }`); 147 | 148 | newline(); 149 | 150 | emit(`constructor();`); 151 | emit(`constructor(ptr: Deno.PointerValue);`); 152 | emit(`constructor(init: Init${stripVk(s.name)});`); 153 | emit(`constructor(data: Uint8Array);`); 154 | emit( 155 | `constructor(data?: Deno.PointerValue | Uint8Array | Init${ 156 | stripVk(s.name) 157 | }) {`, 158 | ); 159 | block(() => { 160 | emit(`if (data === undefined) {`); 161 | block(() => { 162 | emit(`this.#data = new Uint8Array(${stripVk(s.name)}.size);`); 163 | emit( 164 | "this.#view = new DataView(this.#data.buffer, this.#data.byteOffset);", 165 | ); 166 | }); 167 | emit( 168 | `} else if (typeof data === "number" || typeof data === "bigint") {`, 169 | ); 170 | block(() => { 171 | emit( 172 | `this.#data = new Uint8Array(Deno.UnsafePointerView.getArrayBuffer(data, ${ 173 | stripVk(s.name) 174 | }.size));`, 175 | ); 176 | emit( 177 | "this.#view = new DataView(this.#data.buffer, this.#data.byteOffset);", 178 | ); 179 | }); 180 | emit("} else if (data instanceof Uint8Array) {"); 181 | block(() => { 182 | emit(`if (data.byteLength < ${stripVk(s.name)}.size) {`); 183 | block(() => { 184 | emit(`throw new Error("Data buffer too small");`); 185 | }); 186 | emit("}"); 187 | emit("this.#data = data;"); 188 | emit("this.#view = new DataView(data.buffer, data.byteOffset);"); 189 | }); 190 | emit("} else {"); 191 | block(() => { 192 | emit(`this.#data = new Uint8Array(${stripVk(s.name)}.size);`); 193 | emit( 194 | "this.#view = new DataView(this.#data.buffer, this.#data.byteOffset);", 195 | ); 196 | for (const f of s.fields) { 197 | if (f.name === "sType" && f.values?.length === 1) { 198 | continue; 199 | } else { 200 | const name = jsify(f.name); 201 | emit( 202 | `if (data.${name} !== undefined) this.${name} = data.${name};`, 203 | ); 204 | } 205 | } 206 | }); 207 | emit("}"); 208 | for (const f of s.fields) { 209 | if (f.name === "sType" && f.values?.length === 1) { 210 | emit( 211 | `this.sType = StructureType.${ 212 | stripVk(f.values?.[0]).slice("STRUCTURE_TYPE_".length) 213 | };`, 214 | ); 215 | } 216 | } 217 | }); 218 | emit("}"); 219 | 220 | for (const f of s.fields) { 221 | newline(); 222 | if (f.comment) emit(`/** ${f.comment} */`); 223 | const isptr = f.text?.endsWith("*"); 224 | emit(`get ${jsify(f.name)}() {`); 225 | function emitFieldGetter(f: Field) { 226 | if (isptr) { 227 | emit(`return this.#view.getBigUint64(${f.offset}, LE);`); 228 | } else { 229 | switch (f.ffi) { 230 | case "i8": 231 | emit(`return this.#view.getInt8(${f.offset});`); 232 | break; 233 | case "u8": 234 | emit(`return this.#view.getUint8(${f.offset});`); 235 | break; 236 | case "i16": 237 | emit(`return this.#view.getInt16(${f.offset}, LE);`); 238 | break; 239 | case "u16": 240 | emit(`return this.#view.getUint16(${f.offset}, LE);`); 241 | break; 242 | case "i32": 243 | emit(`return this.#view.getInt32(${f.offset}, LE);`); 244 | break; 245 | case "u32": 246 | emit(`return this.#view.getUint32(${f.offset}, LE);`); 247 | break; 248 | case "isize": 249 | case "i64": 250 | emit(`return this.#view.getBigInt64(${f.offset}, LE);`); 251 | break; 252 | case "usize": 253 | case "buffer": 254 | case "pointer": 255 | case "u64": 256 | emit(`return this.#view.getBigUint64(${f.offset}, LE);`); 257 | break; 258 | case "f32": 259 | emit(`return this.#view.getFloat32(${f.offset}, LE);`); 260 | break; 261 | case "f64": 262 | emit(`return this.#view.getFloat64(${f.offset}, LE);`); 263 | break; 264 | default: { 265 | if (isStruct(f.ffi)) { 266 | const name = f.type; 267 | emit( 268 | `return new ${ 269 | stripVk(name) 270 | }(this.#data.subarray(${f.offset}, ${f.offset} + ${ 271 | stripVk(name) 272 | }.size));`, 273 | ); 274 | break; 275 | } 276 | if (isArray(f.ffi)) { 277 | if (tymap[f.ffi.array as keyof typeof tymap]) { 278 | emit( 279 | `return new ${ 280 | tymap[f.ffi.array as keyof typeof tymap] 281 | }(this.#data.buffer, this.#data.byteOffset + ${f.offset}, ${f.ffi.len});`, 282 | ); 283 | } else { 284 | emit(`const result: ${stripVk(typeToJS(f.type))}[] = [];`); 285 | emit(`for (let i = 0; i < ${f.ffi.len}; i++) {`); 286 | block(() => { 287 | emit(`result.push((() => {`); 288 | block(() => { 289 | const tysize = getTypeSize(f.ffi.array); 290 | emitFieldGetter({ 291 | name: f.name, 292 | offset: `${f.offset} + i * ${tysize}` as any, 293 | type: f.type, 294 | ffi: f.ffi.array, 295 | } as any); 296 | }); 297 | emit(`})());`); 298 | }); 299 | emit(`}`); 300 | emit(`return result;`); 301 | } 302 | break; 303 | } 304 | emit( 305 | `throw new Error(\`Unknown type: ${JSON.stringify(f.ffi)}\`);`, 306 | ); 307 | break; 308 | } 309 | } 310 | } 311 | } 312 | block(() => { 313 | emitFieldGetter(f); 314 | }); 315 | emit(`}`); 316 | newline(); 317 | const nativearr = isArray(f.ffi) && (f.ffi.array in tymap); 318 | emit( 319 | `set ${jsify(f.name)}(value: ${ 320 | nativearr 321 | ? `${tymap[f.ffi.array as keyof typeof tymap]}` 322 | : (isptr ? "AnyPointer" : stripVk(typeToJS(f.type))) 323 | }${f.len && !nativearr ? "[]" : ""}) {`, 324 | ); 325 | function emitFieldSetter(f: Field, vname = "value") { 326 | if (isptr) { 327 | emit( 328 | `this.#view.setBigUint64(${f.offset}, BigInt(anyPointer(${vname})), LE);`, 329 | ); 330 | } else if (nativearr) { 331 | emit(`this.#data.set(new Uint8Array(${vname}.buffer), ${f.offset});`); 332 | } else { 333 | switch (f.ffi) { 334 | case "i8": 335 | emit(`this.#view.setInt8(${f.offset}, Number(${vname}));`); 336 | break; 337 | case "u8": 338 | emit(`this.#view.setUint8(${f.offset}, Number(${vname}));`); 339 | break; 340 | case "i16": 341 | emit(`this.#view.setInt16(${f.offset}, Number(${vname}), LE);`); 342 | break; 343 | case "u16": 344 | emit(`this.#view.setUint16(${f.offset}, Number(${vname}), LE);`); 345 | break; 346 | case "i32": 347 | emit(`this.#view.setInt32(${f.offset}, Number(${vname}), LE);`); 348 | break; 349 | case "u32": 350 | emit(`this.#view.setUint32(${f.offset}, Number(${vname}), LE);`); 351 | break; 352 | case "isize": 353 | case "i64": 354 | emit( 355 | `this.#view.setBigInt64(${f.offset}, BigInt(${vname}), LE);`, 356 | ); 357 | break; 358 | case "usize": 359 | case "u64": 360 | emit( 361 | `this.#view.setBigUint64(${f.offset}, BigInt(${vname}), LE);`, 362 | ); 363 | break; 364 | case "buffer": 365 | case "pointer": 366 | emit( 367 | `this.#view.setBigUint64(${f.offset}, BigInt(anyPointer(${vname})), LE);`, 368 | ); 369 | break; 370 | case "f32": 371 | emit(`this.#view.setFloat32(${f.offset}, Number(${vname}), LE);`); 372 | break; 373 | case "f64": 374 | emit(`this.#view.setFloat64(${f.offset}, Number(${vname}), LE);`); 375 | break; 376 | default: { 377 | if (isStruct(f.ffi)) { 378 | const name = f.type; 379 | emit( 380 | `if (${vname}[BUFFER].byteLength < ${stripVk(name)}.size) {`, 381 | ); 382 | block(() => { 383 | emit(`throw new Error("Data buffer too small");`); 384 | }); 385 | emit("}"); 386 | emit(`this.#data.set(${vname}[BUFFER], ${f.offset});`); 387 | break; 388 | } 389 | if (isArray(f.ffi)) { 390 | const tysize = getTypeSize(f.ffi.array); 391 | emit(`for (let i = 0; i < ${vname}.length; i++) {`); 392 | block(() => { 393 | emitFieldSetter({ 394 | name: f.name, 395 | offset: `${f.offset} + i * ${tysize}` as any, 396 | type: f.type, 397 | ffi: f.ffi.array, 398 | } as any, `${vname}[i]`); 399 | }); 400 | emit("}"); 401 | break; 402 | } 403 | emit( 404 | `throw new Error(\`Unknown type: ${JSON.stringify(f.ffi)}\`);`, 405 | ); 406 | break; 407 | } 408 | } 409 | } 410 | } 411 | block(() => { 412 | emitFieldSetter(f); 413 | }); 414 | emit(`}`); 415 | } 416 | }); 417 | emit(`}`); 418 | } 419 | 420 | newline(); 421 | emit("/// Unions"); 422 | 423 | for (const s of unions) { 424 | newline(); 425 | if (s.comment) emit(`/** ${s.comment} */`); 426 | emit(`export class ${stripVk(s.name)} {`); 427 | block(() => { 428 | emit(`static size = ${s.size};`); 429 | 430 | newline(); 431 | 432 | emit("#data: Uint8Array;"); 433 | emit("#view: DataView;"); 434 | 435 | newline(); 436 | 437 | emit("constructor(data: Uint8Array) {"); 438 | block(() => { 439 | emit(`if (data.byteLength < ${stripVk(s.name)}.size) {`); 440 | block(() => { 441 | emit(`throw new Error("Data buffer too small");`); 442 | }); 443 | emit("}"); 444 | emit("this.#data = data;"); 445 | emit("this.#view = new DataView(data.buffer);"); 446 | }); 447 | emit("}"); 448 | }); 449 | emit(`}`); 450 | } 451 | 452 | function toSkipCMD(name: string) { 453 | if (name === "vkCreateSwapchainKHR") return false; 454 | if (name === "vkGetPhysicalDeviceSurfaceSupportKHR") return false; 455 | if (name === "vkGetPhysicalDeviceSurfaceCapabilitiesKHR") return false; 456 | if (name === "vkGetPhysicalDeviceSurfaceFormatsKHR") return false; 457 | if (name === "vkGetPhysicalDeviceSurfacePresentModesKHR") return false; 458 | if (name === "vkDestroySwapchainKHR") return false; 459 | if (name === "vkGetSwapchainImagesKHR") return false; 460 | if (name === "vkAcquireNextImageKHR") return false; 461 | if (name === "vkQueuePresentKHR") return false; 462 | if (name === "vkDestroySurfaceKHR") return false; 463 | 464 | if (name.endsWith("NV")) return true; 465 | if (name.endsWith("NX")) return true; 466 | if (name.endsWith("NVX")) return true; 467 | if (name.endsWith("NN")) return true; 468 | if (name.endsWith("KHR")) return true; 469 | if (name.endsWith("EXT")) return true; 470 | if (name.endsWith("QCOM")) return true; 471 | if (name.endsWith("FUCHSIA")) return true; 472 | if (name.endsWith("INTEL")) return true; 473 | if (name.endsWith("ANDROID")) return true; 474 | if (name.endsWith("VALVE")) return true; 475 | if (name.endsWith("HUAWEI")) return true; 476 | if (name.endsWith("GGP")) return true; 477 | if (name.endsWith("AMD")) return true; 478 | if (name.endsWith("GOOGLE")) return true; 479 | if (name.endsWith("MVK")) return true; 480 | return false; 481 | } 482 | 483 | newline(); 484 | emit("/// FFI Library"); 485 | 486 | emit( 487 | `const lib = Deno.dlopen(Deno.build.os === "windows" ? "vulkan-1" : Deno.build.os === "darwin" ? "libvulkan.dylib.1" : "libvulkan.so.1", {`, 488 | ); 489 | block(() => { 490 | for (const cmd of commands) { 491 | if (toSkipCMD(cmd.name)) continue; 492 | emit( 493 | `"${cmd.name}": ${ 494 | JSON.stringify(cmd.ffi, null, 2).split("\n").map(( 495 | e, 496 | i, 497 | ) => (i === 0 ? e : `${" ".repeat(getIdent())}${e}`)).join("\n") 498 | },`, 499 | ); 500 | } 501 | }); 502 | emit(`} as const).symbols;`); 503 | 504 | newline(); 505 | 506 | emit(`export class VulkanError extends Error {`); 507 | block(() => { 508 | emit(`constructor(public code: Result) {`); 509 | block(() => { 510 | emit(`super(\`Vulkan error: \${code} (\${Result[code]})\`);`); 511 | }); 512 | emit(`}`); 513 | }); 514 | emit(`}`); 515 | 516 | newline(); 517 | emit("/// Commands"); 518 | 519 | for (const cmd of commands) { 520 | if (toSkipCMD(cmd.name)) continue; 521 | newline(); 522 | if (cmd.comment) emit(`/** ${cmd.comment} */`); 523 | emit(`export function ${stripVk(cmd.name)}(`); 524 | block(() => { 525 | for (const p of cmd.params) { 526 | emit( 527 | `${jsify(p.name)}: ${ 528 | p.text?.endsWith("*") ? `AnyBuffer` : stripVk(typeToJS(p.type)) 529 | },`, 530 | ); 531 | } 532 | }); 533 | emit(`): ${stripVk(typeToJS(cmd.type))} {`); 534 | block(() => { 535 | emit(`${cmd.type !== "void" ? "const ret = " : ""}lib.${cmd.name}(`); 536 | block(() => { 537 | for (const p of cmd.params) { 538 | const jsn = jsify(p.name); 539 | emit( 540 | `${p.text?.endsWith("*") ? `anyBuffer(${jsn})` : jsn},`, 541 | ); 542 | } 543 | }); 544 | emit(`);`); 545 | if (cmd.type !== "void") { 546 | if (cmd.type === "VkResult") { 547 | emit( 548 | `if (${ 549 | cmd.successCodes.map((e) => `ret === Result.${stripVk(e)}`).join( 550 | " || ", 551 | ) 552 | }) {`, 553 | ); 554 | block(() => { 555 | emit("return ret;"); 556 | }); 557 | emit(`} else {`); 558 | block(() => { 559 | emit( 560 | `throw new VulkanError(ret as Result);`, 561 | ); 562 | }); 563 | emit("}"); 564 | } else { 565 | emit(`return ret;`); 566 | } 567 | } 568 | }); 569 | emit(`}`); 570 | } 571 | 572 | newline(); 573 | 574 | emit(`export * from "./util.ts";`); 575 | 576 | const src = output(); 577 | 578 | Deno.writeTextFileSync(new URL("../api/vk.ts", import.meta.url), src); 579 | 580 | console.log("Generated api/vk.ts"); 581 | 582 | const { code } = transform(src, { 583 | "jsc": { 584 | "target": "es2022", 585 | "minify": { 586 | "compress": { 587 | "unused": true, 588 | }, 589 | }, 590 | "parser": { 591 | "syntax": "typescript", 592 | }, 593 | }, 594 | }); 595 | 596 | Deno.writeTextFileSync(new URL("../api/vk.min.js", import.meta.url), code); 597 | 598 | console.log("Generated api/vk.min.js"); 599 | -------------------------------------------------------------------------------- /generator/parse_xml.ts: -------------------------------------------------------------------------------- 1 | import { XMLParser } from "npm:fast-xml-parser"; 2 | 3 | const parser = new XMLParser({ 4 | ignoreAttributes: false, 5 | allowBooleanAttributes: true, 6 | parseTagValue: true, 7 | parseAttributeValue: true, 8 | alwaysCreateTextNode: true, 9 | attributeNamePrefix: "$", 10 | }); 11 | const xml = Deno.readTextFileSync(new URL("../data/vk.xml", import.meta.url)); 12 | const videoXml = Deno.readTextFileSync( 13 | new URL("../data/video.xml", import.meta.url), 14 | ); 15 | 16 | console.log("Parsing..."); 17 | const json = parser.parse(xml); 18 | console.log("Done!"); 19 | const json2 = parser.parse(videoXml); 20 | 21 | json.registry.types.type = [ 22 | ...json2.registry.types.type, 23 | ...json.registry.types.type, 24 | ]; 25 | json.registry.enums = [...json2.registry.enums, ...json.registry.enums]; 26 | json.registry.extensions.extension = [ 27 | ...json2.registry.extensions.extension, 28 | ...json.registry.extensions.extension, 29 | ]; 30 | 31 | Deno.writeTextFileSync( 32 | new URL("../data/vk.json", import.meta.url), 33 | JSON.stringify(json, null, 2), 34 | ); 35 | -------------------------------------------------------------------------------- /generator/process_xml.ts: -------------------------------------------------------------------------------- 1 | const api = JSON.parse( 2 | Deno.readTextFileSync(new URL("../data/vk.json", import.meta.url)), 3 | ); 4 | 5 | let src = `/// This file is auto-generated. Do not edit.\n`; 6 | let ident = 0; 7 | 8 | export function output() { 9 | return src; 10 | } 11 | 12 | export function getIdent() { 13 | return ident; 14 | } 15 | 16 | export function newline() { 17 | emit(""); 18 | } 19 | 20 | export function emit(line: string, newline = "\n") { 21 | src += (" ".repeat(ident)) + line + newline; 22 | } 23 | 24 | export function block(fn: CallableFunction) { 25 | ident += 1; 26 | fn(); 27 | ident -= 1; 28 | } 29 | 30 | export function jsify(name: string) { 31 | if (name === "function") { 32 | return "vk_function"; 33 | } else { 34 | return name; 35 | } 36 | } 37 | 38 | export function valueToJS(value: string) { 39 | if (typeof value === "number") { 40 | return value; 41 | } else if (typeof value === "string") { 42 | return value.replaceAll(/F$/g, "").replaceAll(/U\)$/g, ")").replaceAll( 43 | /ULL\)$/g, 44 | "n)", 45 | ); 46 | } else if (typeof value === "undefined") { 47 | return undefined; 48 | } else { 49 | throw new Error("Unknown value type " + Deno.inspect(value)); 50 | } 51 | } 52 | 53 | const C_TYPES = { 54 | "uint8_t": "number", 55 | "uint16_t": "number", 56 | "uint32_t": "number", 57 | "uint64_t": "Deno.PointerValue", 58 | "int8_t": "number", 59 | "int16_t": "number", 60 | "int32_t": "number", 61 | "int64_t": "Deno.PointerValue", 62 | "float": "number", 63 | "double": "number", 64 | "char": "number", 65 | "size_t": "Deno.PointerValue", 66 | "ssize_t": "Deno.PointerValue", 67 | "HINSTANCE": "Deno.PointerValue", 68 | "HWND": "Deno.PointerValue", 69 | "Window": "Deno.PointerValue", 70 | "xcb_window_t": "Deno.PointerValue", 71 | "zx_handle_t": "Deno.PointerValue", 72 | "GgpStreamDescriptor": "Deno.PointerValue", 73 | "HANDLE": "Deno.PointerValue", 74 | "DWORD": "number", 75 | "LPCWSTR": "Deno.PointerValue", 76 | "int": "number", 77 | "GgpFrameToken": "Deno.PointerValue", 78 | "HMONITOR": "Deno.PointerValue", 79 | "VisualID": "number", 80 | "xcb_visualid_t": "number", 81 | "RROutput": "number", 82 | }; 83 | 84 | export function typeToJS(ty: string): string { 85 | if (ty in C_TYPES) { 86 | return (C_TYPES as any)[ty]; 87 | } else { 88 | if (ty.startsWith("PFN_")) { 89 | return "Deno.PointerValue"; 90 | } 91 | return ty; 92 | } 93 | } 94 | 95 | const C_TYPES_FFI = { 96 | "uint8_t": "u8", 97 | "uint16_t": "u16", 98 | "uint32_t": "u32", 99 | "uint64_t": "u64", 100 | "int8_t": "i8", 101 | "int16_t": "i16", 102 | "int32_t": "i32", 103 | "int64_t": "i64", 104 | "float": "f32", 105 | "double": "f64", 106 | "char": "u8", 107 | "size_t": "usize", 108 | "ssize_t": "isize", 109 | "void": "void", 110 | "HINSTANCE": "pointer", 111 | "HWND": "pointer", 112 | "Window": "pointer", 113 | "xcb_window_t": "pointer", 114 | "zx_handle_t": "pointer", 115 | "GgpStreamDescriptor": "pointer", 116 | "HANDLE": "pointer", 117 | "DWORD": "u32", 118 | "LPCWSTR": "pointer", 119 | "int": "i32", 120 | "GgpFrameToken": "u64", 121 | "HMONITOR": "pointer", 122 | "VisualID": "u32", 123 | "xcb_visualid_t": "u32", 124 | "RROutput": "u32", 125 | }; 126 | 127 | export const tymap = { 128 | u8: "Uint8Array", 129 | i8: "Int8Array", 130 | u16: "Uint16Array", 131 | i16: "Int16Array", 132 | u32: "Uint32Array", 133 | i32: "Int32Array", 134 | u64: "BigUint64Array", 135 | i64: "BigInt64Array", 136 | f32: "Float32Array", 137 | f64: "Float64Array", 138 | }; 139 | 140 | export function typeToFFI(ty: string): any { 141 | if (ty in C_TYPES_FFI) { 142 | return (C_TYPES_FFI as any)[ty]; 143 | } else { 144 | const enumty = enums.find((e) => e.name === ty); 145 | if (enumty) { 146 | return enumty.bitwidth === 32 ? "u32" : "u64"; 147 | } 148 | const structty = api.registry.types.type.find((e: any) => 149 | e.$name === ty && e.$category === "struct" && e.member 150 | ); 151 | if (structty) { 152 | return { 153 | struct: structty.member.map((f: any) => 154 | f["#text"]?.endsWith("*") ? "buffer" : typeToFFI(f.type["#text"]) 155 | ), 156 | }; 157 | } 158 | const unionty = api.registry.types.type.find((e: any) => 159 | e.$name === ty && e.$category === "union" && e.member 160 | ); 161 | if (unionty) { 162 | return { 163 | union: unionty.member.map((f: any) => 164 | f["#text"]?.endsWith("*") ? "buffer" : typeToFFI(f.type["#text"]) 165 | ), 166 | }; 167 | } 168 | const tydef = typedefs.find((e) => e.name === ty); 169 | if (tydef) { 170 | return tydef.ffi; 171 | } 172 | if (ty.startsWith("PFN_")) { 173 | return "function"; 174 | } 175 | if (ty.startsWith("MTL") || ty.startsWith("CAMetal")) { 176 | return "pointer"; 177 | } 178 | throw new Error("Unknown type " + ty); 179 | } 180 | } 181 | 182 | export interface Typedef { 183 | name: string; 184 | type: string; 185 | ffi: any; 186 | } 187 | 188 | export interface Constant { 189 | name: string; 190 | value: any; 191 | comment?: string; 192 | } 193 | 194 | export interface Constants { 195 | name: string; 196 | comment?: string; 197 | constants: Constant[]; 198 | } 199 | 200 | export interface Enums { 201 | name: string; 202 | bitwidth: number; 203 | comment?: string; 204 | enums: Constant[]; 205 | } 206 | 207 | export interface Field { 208 | name: string; 209 | type: string; 210 | offset: number; 211 | optional: boolean; 212 | text?: string; 213 | ffi: any; 214 | len?: number | number[]; 215 | enum?: string | string[]; 216 | comment?: string; 217 | values?: string[]; 218 | } 219 | 220 | export interface Struct { 221 | name: string; 222 | fields: Field[]; 223 | comment?: string; 224 | size: number; 225 | } 226 | 227 | export interface UnionType { 228 | name: string; 229 | type: string; 230 | ffi: any; 231 | text?: string; 232 | comment?: string; 233 | } 234 | 235 | export interface Union { 236 | name: string; 237 | types: UnionType[]; 238 | comment?: string; 239 | size: number; 240 | } 241 | 242 | export interface CommandParams { 243 | name: string; 244 | type: string; 245 | text?: string; 246 | comment?: string; 247 | len?: string; 248 | optional: boolean; 249 | ffi: any; 250 | } 251 | 252 | export interface Command { 253 | name: string; 254 | type: string; 255 | params: CommandParams[]; 256 | successCodes: string[]; 257 | errorCodes: string[]; 258 | comment?: string; 259 | ffi: any; 260 | } 261 | 262 | export const typedefs: Typedef[] = []; 263 | export const constants: Constants[] = []; 264 | export const enums: Enums[] = []; 265 | export const structs: Struct[] = []; 266 | export const unions: Union[] = []; 267 | export const commands: Command[] = []; 268 | 269 | for (const data of api.registry.enums) { 270 | if (data.$type === "bitmask" || data.$type === "enum") { 271 | if (!data.enum) data.enum = []; 272 | if (!Array.isArray(data.enum)) data.enum = [data.enum]; 273 | enums.push({ 274 | name: data.$name, 275 | bitwidth: data.$bitwidth ?? 32, 276 | comment: data.$comment, 277 | enums: data.enum.map((e: any) => ({ 278 | name: e.$name, 279 | value: e.$value !== undefined 280 | ? valueToJS(e.$value) 281 | : e.$bitpos !== undefined 282 | ? `1 << ${e.$bitpos}` 283 | : e.$alias !== undefined 284 | ? e.$alias 285 | : undefined, 286 | comment: e.$comment, 287 | })), 288 | }); 289 | } else if ("enum" in data) { 290 | if (!data.enum) data.enum = []; 291 | if (!Array.isArray(data.enum)) data.enum = [data.enum]; 292 | constants.push({ 293 | name: data.$name, 294 | comment: data.$comment, 295 | constants: data.enum.map((member: any) => ({ 296 | name: member.$name, 297 | value: valueToJS(member.$value), 298 | comment: member.$comment, 299 | })), 300 | }); 301 | } else { 302 | console.log("unknown", data); 303 | } 304 | } 305 | 306 | export function isStruct(type: any) { 307 | return typeof type === "object" && type !== null && ("struct" in type); 308 | } 309 | 310 | export function isUnion(type: any) { 311 | return typeof type === "object" && type !== null && ("union" in type); 312 | } 313 | 314 | export function isArray(type: any) { 315 | return typeof type === "object" && type !== null && ("array" in type); 316 | } 317 | 318 | export function getAlignSize( 319 | type: any, 320 | cache?: WeakMap, 321 | ): number { 322 | if (isStruct(type)) { 323 | return getAlignSize(type.struct[0], cache); 324 | } else if (isUnion(type)) { 325 | return getAlignSize(type.union[0], cache); 326 | } else if (isArray(type)) { 327 | return getAlignSize(type.array, cache); 328 | } else { 329 | return getTypeSize(type, cache); 330 | } 331 | } 332 | 333 | export function getTypeSize( 334 | type: any, 335 | cache = new WeakMap(), 336 | ) { 337 | if (isStruct(type)) { 338 | const cached = cache.get(type); 339 | if (cached !== undefined) { 340 | if (cached === null) { 341 | throw new TypeError("Recursive struct definition"); 342 | } 343 | return cached; 344 | } 345 | cache.set(type, null); 346 | let size = 0; 347 | let alignment = 1; 348 | for (const field of type.struct) { 349 | const fieldSize = getTypeSize(field, cache); 350 | const alignSize = getAlignSize(field, cache); 351 | alignment = Math.max(alignment, alignSize); 352 | size = Math.ceil(size / alignSize) * alignSize; 353 | size += fieldSize; 354 | } 355 | size = Math.ceil(size / alignment) * alignment; 356 | cache.set(type, size); 357 | return size; 358 | } 359 | 360 | if (isArray(type)) { 361 | const cached = cache.get(type); 362 | if (cached !== undefined) { 363 | if (cached === null) { 364 | throw new TypeError("Recursive array definition"); 365 | } 366 | return cached; 367 | } 368 | cache.set(type, null); 369 | let size = 0; 370 | let alignment = 1; 371 | for (let i = 0; i < type.len; i++) { 372 | const fieldSize = getTypeSize(type.array, cache); 373 | alignment = Math.max(alignment, fieldSize); 374 | size = Math.ceil(size / fieldSize) * fieldSize; 375 | size += fieldSize; 376 | } 377 | size = Math.ceil(size / alignment) * alignment; 378 | cache.set(type, size); 379 | return size; 380 | } 381 | 382 | if (isUnion(type)) { 383 | const cached = cache.get(type); 384 | if (cached !== undefined) { 385 | if (cached === null) { 386 | throw new TypeError("Recursive union definition"); 387 | } 388 | return cached; 389 | } 390 | cache.set(type, null); 391 | let size = 0; 392 | for (const field of type.union) { 393 | size = Math.max(size, getTypeSize(field, cache)); 394 | } 395 | cache.set(type, size); 396 | return size; 397 | } 398 | 399 | switch (type) { 400 | case "bool": 401 | case "u8": 402 | case "i8": 403 | return 1; 404 | case "u16": 405 | case "i16": 406 | return 2; 407 | case "u32": 408 | case "i32": 409 | case "f32": 410 | return 4; 411 | case "u64": 412 | case "i64": 413 | case "f64": 414 | case "pointer": 415 | case "buffer": 416 | case "function": 417 | case "usize": 418 | case "isize": 419 | return 8; 420 | default: 421 | throw new TypeError(`Unsupported type: ${Deno.inspect(type)}`); 422 | } 423 | } 424 | 425 | export function extendEnum(ext: { 426 | $extends: string; 427 | $alias?: string; 428 | $bitpos?: number; 429 | $value?: number; 430 | $extnumber?: number; 431 | $offset?: number; 432 | $name: string; 433 | $comment?: string; 434 | }) { 435 | const base = enums.find((e) => e.name === ext.$extends); 436 | if (!base) { 437 | throw new Error(`Enum ${ext.$extends} not found`); 438 | } 439 | if (base.enums.some((e) => e.name === ext.$name)) return; 440 | base.enums.push({ 441 | name: ext.$name, 442 | value: ext.$alias ?? 443 | (ext.$bitpos !== undefined ? `1 << ${ext.$bitpos}` : ext.$value ?? 444 | `1${String(ext.$extnumber! - 1).padStart(6, "0")}${ 445 | ext.$offset!.toString().padStart(3, "0") 446 | }`), 447 | comment: ext.$comment, 448 | }); 449 | } 450 | 451 | export function extendConstants(name: string, ext: any) { 452 | let base = constants.find((e) => e.name === name); 453 | if (!base) { 454 | base = { 455 | name, 456 | constants: [], 457 | }; 458 | constants.push(base); 459 | } 460 | base.constants.push({ 461 | name: ext.$name, 462 | value: valueToJS(ext.$value), 463 | }); 464 | } 465 | 466 | for (const ft of api.registry.feature) { 467 | for (const x of ft.require) { 468 | if ("enum" in x) { 469 | if (!Array.isArray(x.enum)) x.enum = [x.enum]; 470 | for (const e of x.enum) { 471 | if (e.$extends) { 472 | extendEnum(e); 473 | } 474 | } 475 | } 476 | } 477 | } 478 | 479 | for (const ext of api.registry.extensions.extension) { 480 | if (!Array.isArray(ext.require)) ext.require = [ext.require]; 481 | for (const x of ext.require) { 482 | if ("enum" in x) { 483 | if (!Array.isArray(x.enum)) x.enum = [x.enum]; 484 | for (const e of x.enum) { 485 | if (e.$extends) { 486 | extendEnum(Object.assign({ 487 | $extnumber: ext.$number, 488 | }, e)); 489 | } else if (e.$name && e.$value) { 490 | extendConstants(ext.$name, e); 491 | } 492 | } 493 | } 494 | } 495 | } 496 | 497 | for (const ty of api.registry.types.type) { 498 | if ( 499 | (ty.$category === "basetype" || ty.$category === "bitmask") && 500 | ty["#text"] === "typedef;" 501 | ) { 502 | typedefs.push({ 503 | name: ty.name["#text"], 504 | type: typeToJS(ty.type["#text"]), 505 | ffi: typeToFFI(ty.type["#text"]), 506 | }); 507 | } else if (ty.$category === "basetype") { 508 | const name = ty.name["#text"]; 509 | const tx = ty["#text"]; 510 | if (tx.startsWith("#ifdef __OBJC__")) { 511 | if (tx.includes("\ntypedef ") && tx.endsWith("*;\n#endif")) { 512 | typedefs.push({ 513 | name, 514 | type: "Deno.PointerValue", 515 | ffi: "pointer", 516 | }); 517 | } 518 | } 519 | if (tx.startsWith("typedef struct ") && tx.endsWith("*;")) { 520 | typedefs.push({ 521 | name, 522 | type: "Deno.PointerValue", 523 | ffi: "pointer", 524 | }); 525 | } 526 | } else if (ty.$category === "handle") { 527 | typedefs.push({ 528 | name: ty.$name ?? ty.name["#text"], 529 | type: ty.$alias ?? "Deno.PointerValue", 530 | ffi: "pointer", 531 | }); 532 | } else if (ty.$category === "struct" && ty.member) { 533 | const struct: Struct = { 534 | name: ty.$name, 535 | fields: [], 536 | comment: ty.$comment, 537 | size: 0, 538 | }; 539 | structs.push(struct); 540 | if (!Array.isArray(ty.member)) ty.member = [ty.member]; 541 | let size = 0; 542 | let alignment = 1; 543 | struct.fields = ty.member.map((member: any) => { 544 | let field: Field = { 545 | name: member.name["#text"], 546 | type: member.type["#text"], 547 | offset: size, 548 | optional: member.$optional, 549 | text: member["#text"], 550 | len: undefined, 551 | comment: member.$comment, 552 | ffi: "void", 553 | values: member.$values?.split(","), 554 | enum: member.enum 555 | ? (Array.isArray(member.enum) 556 | ? member.enum.map((e: any) => e["#text"]) 557 | : member.enum?.["#text"]) 558 | : undefined, 559 | }; 560 | 561 | if (field.text?.endsWith("*")) { 562 | field.ffi = "pointer"; 563 | } else { 564 | field.ffi = typeToFFI(field.type); 565 | } 566 | 567 | if (field.text?.startsWith("[")) { 568 | if (field.enum) { 569 | if (Array.isArray(field.enum)) { 570 | field.len = field.enum.map((en) => { 571 | return constants.map((c) => 572 | c.constants.find((c) => c.name === en) 573 | ).find((e) => e !== undefined)?.value!; 574 | }); 575 | } else {field.len = constants.map((c) => 576 | c.constants.find((c) => c.name === field.enum) 577 | ).find((e) => e !== undefined)?.value;} 578 | } else { 579 | const match = field.text.match(/^\[(\d+)\]*/); 580 | if (match) { 581 | field.len = match.slice(1).map((e) => parseInt(e)); 582 | } 583 | } 584 | if (!field.len) { 585 | throw new Error(`Invalid length: ${Deno.inspect(field)}`); 586 | } 587 | field.ffi = { 588 | array: field.ffi, 589 | len: Array.isArray(field.len) 590 | ? field.len.reduce((p, a) => p * a, 1) 591 | : field.len, 592 | }; 593 | } 594 | 595 | const fieldSize = getTypeSize(field.ffi); 596 | const alignSize = getAlignSize(field.ffi); 597 | alignment = Math.max(alignment, alignSize); 598 | size = Math.ceil(size / alignSize) * alignSize; 599 | 600 | field.offset = size; 601 | 602 | size += fieldSize; 603 | return field; 604 | }); 605 | size = Math.ceil(size / alignment) * alignment; 606 | struct.size = size; 607 | } else if (ty.$category === "union" && ty.member) { 608 | const union: Union = { 609 | name: ty.$name, 610 | types: [], 611 | comment: ty.$comment, 612 | size: 0, 613 | }; 614 | unions.push(union); 615 | if (!Array.isArray(ty.member)) ty.member = [ty.member]; 616 | union.types = ty.member.map((member: any) => ({ 617 | name: member.name["#text"], 618 | type: member.type["#text"], 619 | ffi: typeToFFI(member.type["#text"]), 620 | comment: member.$comment, 621 | text: member["#text"], 622 | })); 623 | union.size = getTypeSize({ 624 | union: union.types.map((e) => e.text?.endsWith("*") ? "pointer" : e.ffi), 625 | }); 626 | } else if (ty.$name && ty.$alias) { 627 | typedefs.push({ 628 | name: ty.$name, 629 | type: ty.$alias, 630 | ffi: typeToFFI(ty.$alias), 631 | }); 632 | } 633 | } 634 | 635 | for (const cmd of api.registry.commands.command) { 636 | if (cmd.$alias) continue; // TODO 637 | const name = cmd.proto.name["#text"]; 638 | const type = cmd.proto.type["#text"]; 639 | const params: CommandParams[] = []; 640 | if (cmd.param) { 641 | if (!Array.isArray(cmd.param)) cmd.param = [cmd.param]; 642 | for (const param of cmd.param) { 643 | const name = param.name["#text"]; 644 | const type = param.type["#text"]; 645 | const optional = param.$optional; 646 | const len = param.$len; 647 | const comment = param.$comment; 648 | const text = param["#text"]; 649 | params.push({ 650 | name, 651 | type, 652 | optional, 653 | len, 654 | comment, 655 | text, 656 | ffi: text?.endsWith("*") ? "buffer" : typeToFFI(type), 657 | }); 658 | } 659 | } 660 | commands.push({ 661 | name, 662 | type, 663 | params, 664 | comment: cmd.$comment, 665 | successCodes: cmd.$successcodes?.split(",") ?? [], 666 | errorCodes: cmd.$errorcodes?.split(",") ?? [], 667 | ffi: { 668 | parameters: params.map((e) => e.ffi), 669 | result: cmd["#text"]?.endsWith("*") ? "pointer" : typeToFFI(type), 670 | }, 671 | }); 672 | } 673 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./api/vk.ts"; 2 | --------------------------------------------------------------------------------