@cosmos-js/wgpu-struct 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +25 -0
  2. package/src/index.ts +223 -0
  3. package/tsconfig.json +16 -0
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@cosmos-js/wgpu-struct",
3
+ "version": "0.1.0",
4
+ "description": "Helper for cosmos-js to descriptor wgpu structs in js.",
5
+ "main": "./dist/index.js",
6
+ "author": {
7
+ "name": "Kryft Studios",
8
+ "url": "https://github.com/KryftStudios",
9
+ "email": "AfriaStudios@outlook.in"
10
+ },
11
+ "license": "Apache-2.0",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "keywords": [
16
+ "webgpu",
17
+ "struct",
18
+ "gpu",
19
+ "cpu"
20
+ ],
21
+ "type": "module",
22
+ "repository": {
23
+ "url": "https://github.com/Kryft-Studios"
24
+ }
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,223 @@
1
+
2
+ export enum LENGTH {
3
+ BIT8 = 1,
4
+ BIT16 = 2,
5
+ BIT32 = 4
6
+ }
7
+ export const WGPU_DATA_TYPE = {
8
+ /**Represents a U8 (1 byte) */
9
+ U8: { type: "uint", length: LENGTH.BIT8 },
10
+ /**Represents a U16 (2 byte) */
11
+ U16: { type: "uint", length: LENGTH.BIT16 },
12
+ /**Represents a U32 (4 byte) */
13
+ U32: { type: "uint", length: LENGTH.BIT32 },
14
+ /**Represent a I8 (1 byte) */
15
+ I8: { type: "int", length: LENGTH.BIT8 },
16
+ /**Represent a I16 (2 byte) */
17
+ I16: { type: "int", length: LENGTH.BIT16 },
18
+ /**Represent a I32 (4 byte) */
19
+ I32: { type: "int", length: LENGTH.BIT32 },
20
+ /**Represent a F16 (2 byte) */
21
+ F16: { type: "float", length: LENGTH.BIT16 },
22
+ /**Repesent a F32 (4 byte) */
23
+ F32: { type: "float", length: LENGTH.BIT32 }
24
+ } as const;
25
+ export type WGPU_DATA_TYPE = typeof WGPU_DATA_TYPE[keyof typeof WGPU_DATA_TYPE]
26
+ export interface TYPE_METADATA {
27
+ /**
28
+ * Represents the length of the type.
29
+ *
30
+ * e.g. **`length: 4`** meaning it's a **`array<yourType, 4>`**
31
+ */
32
+ length: number,
33
+ /**
34
+ * Represents the webgpu type;
35
+ *
36
+ * e.g. **`WGPU_DATA_TYPE.U8`** represents a **Uint8 [0-256]**
37
+ */
38
+ type: WGPU_DATA_TYPE
39
+ }
40
+
41
+ export type FIELD = ({
42
+ /**Name of the field */
43
+ name: string,
44
+ required: boolean,
45
+ }) & (({
46
+ /**
47
+ * The type of the field;
48
+ *
49
+ * If you want to represent a vec3i; use `{length: 3, type: WGPU_DATA_TYPE.I32}`
50
+ */
51
+ type: TYPE_METADATA;
52
+ }) | ({
53
+ /**
54
+ * Inner struct
55
+ */
56
+ innerStruct: WGPU_STRUCT;
57
+ length: number;
58
+ }))
59
+
60
+ export type WGPU_STRUCT = FIELD[];
61
+ export type DIGESTABLE_FIELD = {
62
+ name: string;
63
+ length: number;
64
+ required: boolean;
65
+ } & (
66
+ { struct: DIGESTABLE_STRUCT_LAYOUT } | { type: WGPU_DATA_TYPE }
67
+ )
68
+ export interface DIGESTABLE_STRUCT_LAYOUT {
69
+ byteLength: number;
70
+ items: DIGESTABLE_FIELD[];
71
+ }
72
+ export interface DATA_TYPE {
73
+ [x: string]: number[] | DATA_TYPE[]
74
+ }
75
+ export namespace StructValidation {
76
+ export function validateNames(struct: WGPU_STRUCT) {
77
+ let set: Record<string, true> = {};
78
+ for (const field of struct) {
79
+ if (set[field.name]) throw new Error("Duplicate field name " + field.name)
80
+ else if ((field as { name: string } & { innerStruct: WGPU_STRUCT })?.innerStruct) validateNames((field as { name: string } & { innerStruct: WGPU_STRUCT }).innerStruct)
81
+ set[field.name] = true;
82
+ }
83
+ set = {}
84
+ }
85
+ function getByteLength(struct: WGPU_STRUCT) {
86
+ let bytes = 0;
87
+ for (const field of struct) {
88
+ const assingleTyped = (field as { name: string } & { type: TYPE_METADATA })
89
+ if (assingleTyped.type) {
90
+ bytes += assingleTyped.type.type.length * assingleTyped.type.length
91
+ }
92
+ const asstructTyped = (field as { name: string } & { innerStruct: WGPU_STRUCT; length: number })
93
+ if (asstructTyped.innerStruct) {
94
+ bytes += asstructTyped.length * getByteLength(asstructTyped.innerStruct);
95
+ }
96
+ }
97
+ return bytes;
98
+ }
99
+ export function validateBytelength(struct: WGPU_STRUCT) {
100
+ const byteLength = getByteLength(struct);
101
+ if (byteLength % 4 !== 0) {
102
+ throw new Error(`Length of the given struct is not divisible by 4.`);
103
+ }
104
+ if (byteLength % 16 !== 0) {
105
+ throw new Error(`Length of the given struct is not divisble by 16`);
106
+ }
107
+ }
108
+ export function validateData(data: DATA_TYPE, layout: DIGESTABLE_STRUCT_LAYOUT) {
109
+ for (const field of layout.items) {
110
+ const fieldData = data[field.name];
111
+ if (!fieldData && field.required) throw new Error(`Error while validating data: "${field.name}" not given`);
112
+ if (fieldData.length !== field.length) throw new Error(`[Field "${field.name}"]: Given data is of ${fieldData.length} where as ${field.length} is required.`);
113
+ const asTyped = field as { name: string } & { type: WGPU_DATA_TYPE };
114
+ if (asTyped.type) {
115
+ if (fieldData.every(a => typeof a !== "number")) {
116
+ throw new Error(`[Field "${field.name}"]: Array given is not of number[] type`)
117
+ }
118
+ } else {
119
+ for (const data of fieldData) {
120
+ validateData(data as DATA_TYPE, (field as { struct: DIGESTABLE_STRUCT_LAYOUT }).struct)
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ export namespace StructConverter {
127
+ export function convertToDigestableLayout(struct: WGPU_STRUCT) {
128
+ const layout: DIGESTABLE_STRUCT_LAYOUT = { byteLength: 0, items: [] };
129
+ for (const field of struct) {
130
+ const fieldTyped = field as ({ name: string } & { type: TYPE_METADATA, required: boolean });
131
+ const fieldStruct = field as ({ name: string } & { innerStruct: WGPU_STRUCT, length: number, required: boolean });
132
+ if (fieldTyped.type) {
133
+ const byteLength = fieldTyped.type.type.length * fieldTyped.type.length;
134
+ layout.byteLength += byteLength;
135
+ layout.items.push({ "length": fieldTyped.type.length, "name": fieldTyped.name, "type": fieldTyped.type.type, required: fieldTyped.required })
136
+ } else if (fieldStruct.innerStruct) {
137
+ const digested = convertToDigestableLayout(fieldStruct.innerStruct);
138
+ const byteLength = fieldStruct.length * digested.byteLength;
139
+ layout.byteLength += byteLength;
140
+ layout.items.push({ length: fieldStruct.length, name: fieldStruct.name, struct: digested, required: fieldStruct.required });
141
+ }
142
+ }
143
+ return layout;
144
+ }
145
+ }
146
+
147
+ export namespace Packer {
148
+ /** Packs data to {type: "f32"|"i32"|"u32", value: number}[] */
149
+ export function packToBit32(data: DATA_TYPE, layout: DIGESTABLE_STRUCT_LAYOUT) {
150
+ const buffer = new ArrayBuffer(layout.byteLength);
151
+ const view = new DataView(buffer);
152
+
153
+ const chunkTypes: ("f32" | "i32" | "u32")[] = new Array(layout.byteLength / 4).fill("u32");
154
+
155
+ let byteOffset = 0;
156
+
157
+ function writeField(fieldData: number[] | DATA_TYPE[], field: DIGESTABLE_FIELD) {
158
+ const isPrimitive = "type" in field;
159
+
160
+ if (isPrimitive) {
161
+ const primitiveType = field.type.type;
162
+ const bitLength = field.type.length;
163
+ const numericArray = fieldData as number[];
164
+
165
+ for (let i = 0; i < field.length; i++) {
166
+ const value = numericArray[i] ?? 0;
167
+ const chunkIndex = Math.floor(byteOffset / 4);
168
+ if (primitiveType === "float") chunkTypes[chunkIndex] = "f32";
169
+ else if (primitiveType === "int") chunkTypes[chunkIndex] = "i32";
170
+
171
+ if (primitiveType === "uint") {
172
+ if (bitLength === 1) view.setUint8(byteOffset, value);
173
+ else if (bitLength === 2) view.setUint16(byteOffset, value, true);
174
+ else if (bitLength === 4) view.setUint32(byteOffset, value, true);
175
+ } else if (primitiveType === "int") {
176
+ if (bitLength === 1) view.setInt8(byteOffset, value);
177
+ else if (bitLength === 2) view.setInt16(byteOffset, value, true);
178
+ else if (bitLength === 4) view.setInt32(byteOffset, value, true);
179
+ } else if (primitiveType === "float") {
180
+ if (bitLength === 4) view.setFloat32(byteOffset, value, true);
181
+ }
182
+
183
+ byteOffset += bitLength;
184
+ }
185
+ } else {
186
+ const structArray = fieldData as DATA_TYPE[];
187
+ const innerLayout = field.struct;
188
+
189
+ for (let i = 0; i < field.length; i++) {
190
+ const innerData = structArray[i] ?? {};
191
+ for (const innerField of innerLayout.items) {
192
+ writeField(innerData[innerField.name], innerField);
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ for (const item of layout.items) {
199
+ writeField(data[item.name], item);
200
+ }
201
+
202
+ const result: { type: "f32" | "i32" | "u32"; value: number }[] = [];
203
+ const total32BitChunks = layout.byteLength / 4;
204
+
205
+ for (let i = 0; i < total32BitChunks; i++) {
206
+ const currentByteOffset = i * 4;
207
+ const targetType = chunkTypes[i];
208
+ let value = 0;
209
+
210
+ if (targetType === "f32") {
211
+ value = view.getFloat32(currentByteOffset, true);
212
+ } else if (targetType === "i32") {
213
+ value = view.getInt32(currentByteOffset, true);
214
+ } else {
215
+ value = view.getUint32(currentByteOffset, true);
216
+ }
217
+
218
+ result.push({ type: targetType, value });
219
+ }
220
+
221
+ return result;
222
+ }
223
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024",
4
+ "module": "nodenext",
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "moduleResolution": "nodenext",
8
+ "declaration": true,
9
+ "emitDeclarationOnly": false,
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "types": ["@webgpu/types"]
14
+ },
15
+ "include": ["src"],
16
+ }