rm-webgpu-compute-tasks 0.0.1

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 +29 -0
  2. package/src/index.d.ts +574 -0
  3. package/src/index.js +2323 -0
package/src/index.js ADDED
@@ -0,0 +1,2323 @@
1
+ import * as THREE from "three";
2
+ import { Vector2, Vector3, Vector4, Matrix4, Matrix3, Color } from "three";
3
+ async function include(path, exportDefault = true) {
4
+ if (typeof global !== "undefined" && typeof require !== "undefined") {
5
+ return require(path);
6
+ } else {
7
+ let pack = await import(
8
+ /* @vite-ignore */
9
+ path
10
+ );
11
+ if (exportDefault) pack = pack.default;
12
+ return pack;
13
+ }
14
+ }
15
+ function arrayBufferViewToWgslType(value) {
16
+ if (value instanceof Float32Array) return "f32";
17
+ if (value instanceof Int32Array) return "i32";
18
+ if (value instanceof Uint32Array) return "u32";
19
+ throw new Error(
20
+ `Unsupported ArrayBufferView type: ${value.constructor.name}`
21
+ );
22
+ }
23
+ function capitalize(str) {
24
+ if (!str) return str;
25
+ return str[0].toUpperCase() + str.slice(1);
26
+ }
27
+ function isWgslType(type) {
28
+ return wgslTypes.includes(type);
29
+ }
30
+ function isStruct(s) {
31
+ if (s && Array.isArray(s) && s.length) {
32
+ return s.every((v) => v && typeof v === "object" && "name" in v && "type" in v && isWgslType(v.type));
33
+ }
34
+ return false;
35
+ }
36
+ function isStructArray(s) {
37
+ return s && "layout" in s && isStruct(s.layout);
38
+ }
39
+ function padding(offset, type) {
40
+ const alignment = TYPE_ALIGN[type];
41
+ return (alignment - offset % alignment) % alignment;
42
+ }
43
+ function roundUp(offset, align) {
44
+ return offset + (align - offset % align) % align;
45
+ }
46
+ function buildStructTypeByData(data) {
47
+ const keys = Object.keys(data);
48
+ const types = keys.map((k) => {
49
+ const v = data[k];
50
+ if (Array.isArray(v)) {
51
+ for (const t of ["vec2", "vec3", "vec4", "mat3x3", "mat4x4"]) {
52
+ if (TYPE_SIZE[t] === v.length) return t;
53
+ }
54
+ throw new Error(`${k} 不支持的数组长度 ${v.length}`);
55
+ }
56
+ if (typeof v === "number") return "f32";
57
+ throw new Error(`${k} 不支持的类型`);
58
+ });
59
+ if (keys.length !== types.length) throw new Error("keys 与 types 长度不一致");
60
+ let offset = 0;
61
+ const layout = keys.map((k, i) => {
62
+ const type = types[i];
63
+ offset += padding(offset, type);
64
+ const info = {
65
+ name: k,
66
+ type,
67
+ offset,
68
+ size: TYPE_SIZE[type]
69
+ };
70
+ offset += TYPE_SIZE[type];
71
+ return info;
72
+ });
73
+ const structAlign = Math.max(...types.map((t) => TYPE_ALIGN[t]));
74
+ const stride = offset + (structAlign - offset % structAlign) % structAlign;
75
+ return {
76
+ stride,
77
+ layout
78
+ };
79
+ }
80
+ const TYPE_SIZE = {
81
+ f32: 1,
82
+ u32: 1,
83
+ vec2: 2,
84
+ vec3: 3,
85
+ vec4: 4,
86
+ mat3x3: 12,
87
+ mat4x4: 16
88
+ // array: Infinity
89
+ };
90
+ const TYPE_ALIGN = {
91
+ f32: 1,
92
+ u32: 1,
93
+ vec2: 2,
94
+ vec3: 4,
95
+ vec4: 4,
96
+ mat3x3: 4,
97
+ mat4x4: 4
98
+ // array: 1
99
+ };
100
+ class AtomicUint32Array extends Uint32Array {
101
+ }
102
+ const wgslTypes = ["f32", "u32", "vec2", "vec3", "vec4", "mat3x3", "mat4x4"];
103
+ let adapter$1 = null;
104
+ let device$1 = null;
105
+ class GpuComputed {
106
+ template;
107
+ option;
108
+ pipeline;
109
+ device;
110
+ groupLayout;
111
+ code;
112
+ constructor(template, option) {
113
+ this.template = template;
114
+ this.option = option;
115
+ this.improveTemplateOption(this.template);
116
+ }
117
+ /** 完善模版数据
118
+ * @param template
119
+ */
120
+ improveTemplateOption(template) {
121
+ const improveStruct = (struct) => {
122
+ let offset = 0;
123
+ struct.forEach((item) => {
124
+ offset += padding(offset, item.type);
125
+ item.offset = offset;
126
+ item.size = TYPE_SIZE[item.type];
127
+ offset += item.size;
128
+ });
129
+ };
130
+ const improveStructArray = (structArr) => {
131
+ improveStruct(structArr.layout);
132
+ const last = structArr.layout[structArr.layout.length - 1], stride = last.offset + last.size;
133
+ let maxAlign = 1;
134
+ for (const item of structArr.layout) maxAlign = Math.max(maxAlign, TYPE_ALIGN[item.type]);
135
+ structArr.stride = roundUp(stride, maxAlign);
136
+ };
137
+ const keys = Object.keys(template);
138
+ keys.forEach((key) => {
139
+ const value = template[key];
140
+ if (isStruct(value)) improveStruct(value);
141
+ else if (isStructArray(value)) improveStructArray(value);
142
+ else if (Array.isArray(value) && typeof value[0] === "number") {
143
+ template[key] = new Float32Array();
144
+ }
145
+ });
146
+ }
147
+ /** 获取Gpu设备
148
+ * @returns
149
+ */
150
+ async getDevice() {
151
+ if (!adapter$1 || !device$1) throw new Error("webgpu未初始化或不可用");
152
+ return { adapter: adapter$1, device: device$1 };
153
+ }
154
+ /**
155
+ * 初始化计算管线
156
+ */
157
+ async initPipeline() {
158
+ if (!this.template) throw new Error("初始化计算管线错误,未找到可用数据模版");
159
+ await GpuComputed.init();
160
+ const template = this.template;
161
+ const { device: device2 } = await this.getDevice(), structCodes = [], groupCodes = [], groupLayoutOption = [];
162
+ this.device = device2;
163
+ Object.keys(template).forEach((name, index) => {
164
+ groupLayoutOption.push({
165
+ binding: index,
166
+ // 绑定到组里的0号位插槽
167
+ visibility: GPUShaderStage.COMPUTE,
168
+ // 数据在哪些阶段可以使用, 计算着色器、片元着色器、顶点着色器
169
+ buffer: {
170
+ type: "storage"
171
+ }
172
+ });
173
+ if (isStruct(template[name])) {
174
+ const value = template[name], code2 = value.map((item) => `${item.name}:${item.type === "f32" ? "f32" : item.type + "<f32>"}`).join(","), structName = `${capitalize(name)}Struct`;
175
+ structCodes.push(`struct ${capitalize(name)}Struct {${code2}};`);
176
+ groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: ${structName};`);
177
+ } else if (isStructArray(template[name])) {
178
+ const value = template[name], code2 = value.layout.map((item) => `${item.name}:${item.type === "f32" ? "f32" : item.type + "<f32>"}`).join(","), structName = `${capitalize(name)}Struct`;
179
+ structCodes.push(`struct ${structName} {${code2}};`);
180
+ groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<${structName}>;`);
181
+ } else if (ArrayBuffer.isView(template[name]) && !(template[name] instanceof DataView)) {
182
+ const value = template[name];
183
+ const type = arrayBufferViewToWgslType(value);
184
+ if (value instanceof AtomicUint32Array) groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<atomic<${type}>>;`);
185
+ else groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<${type}>;`);
186
+ }
187
+ });
188
+ const {
189
+ beforeCodes = [],
190
+ workgroupSize = [32, 1, 1],
191
+ globalInvocationIdName = "grid",
192
+ workgroupIndexName = "index",
193
+ code = ""
194
+ } = this.option ?? {};
195
+ const codeFull = (
196
+ /*wgsl*/
197
+ `
198
+ ${structCodes.join("")}
199
+ ${groupCodes.join("")}
200
+ ${beforeCodes.join(" ") ?? ""}
201
+
202
+ @compute @workgroup_size(${workgroupSize.join(",")})
203
+ fn main(@builtin(global_invocation_id) ${globalInvocationIdName}: vec3<u32>) {
204
+ var ${workgroupIndexName} = ${globalInvocationIdName}.x;
205
+ ${code}
206
+ }
207
+ `
208
+ );
209
+ this.code = codeFull;
210
+ const groupLayout = device2.createBindGroupLayout({
211
+ entries: groupLayoutOption
212
+ });
213
+ this.groupLayout = groupLayout;
214
+ this.pipeline = device2.createComputePipeline({
215
+ layout: device2.createPipelineLayout({
216
+ bindGroupLayouts: [groupLayout]
217
+ }),
218
+ compute: {
219
+ module: device2.createShaderModule({ code: codeFull, label: "" }),
220
+ entryPoint: "main"
221
+ }
222
+ });
223
+ }
224
+ /**
225
+ * 根据数据创建buffer组
226
+ * @param data 数据
227
+ * @param bindGroup 已创建的bindGroup, 会在没有传入对应数据时使用,用于Buffer重复利用,不需要重新构建,按名字判断,所以请确保名称和类型一致性
228
+ * @returns
229
+ */
230
+ createBindGroup(data, bindGroup) {
231
+ if (!this.template) throw new Error("创建buffer组错误,未找到可用数据模版");
232
+ if (!this.device) throw new Error("创建buffer组错误,未找到可用的gpu设备,请确保初始化完计算管线");
233
+ const device2 = this.device, template = this.template, buffers = [], bufferMap = bindGroup?.buffers?.reduce((map, item) => {
234
+ map.set(item.name, item.buffer);
235
+ return map;
236
+ }, /* @__PURE__ */ new Map());
237
+ function buildStruct(valueObj, tem, offset = 0, data2) {
238
+ if (ArrayBuffer.isView(valueObj) || Array.isArray(valueObj)) return valueObj;
239
+ if (!data2) {
240
+ const last = tem[tem.length - 1], stride = last.offset + last.size;
241
+ let maxAlign = 1;
242
+ for (const item of tem) maxAlign = Math.max(maxAlign, TYPE_ALIGN[item.type]);
243
+ data2 = data2 ?? new Float32Array(roundUp(stride, maxAlign)).fill(0);
244
+ }
245
+ tem.forEach((item) => {
246
+ let value = valueObj[item.name];
247
+ if (!Array.isArray(value)) value = [value];
248
+ for (let i = 0; i < item.size; i++) {
249
+ data2[offset + item.offset + i] = Number(value[i] ?? 0);
250
+ }
251
+ });
252
+ return data2;
253
+ }
254
+ function buildStructArray(values, tem) {
255
+ if (ArrayBuffer.isView(values) || typeof values[0] === "number") return values;
256
+ const data2 = new Float32Array(tem.stride * values.length).fill(0);
257
+ values.forEach((value, i) => {
258
+ const offset = i * tem.stride;
259
+ buildStruct(value, tem.layout, offset, data2);
260
+ });
261
+ return data2;
262
+ }
263
+ Object.keys(template).forEach((name) => {
264
+ if (!(name in data)) {
265
+ if (bufferMap && bufferMap.has(name)) {
266
+ return buffers.push({ name, buffer: bufferMap.get(name) });
267
+ }
268
+ throw new Error(`传入的数据中,不存在${name}字段`);
269
+ }
270
+ const tem = template[name];
271
+ const value = data[name];
272
+ let array = [];
273
+ if (isStruct(tem)) array = buildStruct(value, tem);
274
+ else if (isStructArray(tem)) array = buildStructArray(value, tem);
275
+ else if (Array.isArray(value) || ArrayBuffer.isView(value)) array = value;
276
+ let arrayBuffer = null;
277
+ if (tem instanceof Float32Array) arrayBuffer = new Float32Array(array);
278
+ else if (tem instanceof Uint32Array) arrayBuffer = new Uint32Array(array);
279
+ else if (tem instanceof Int32Array) arrayBuffer = new Int32Array(array);
280
+ else arrayBuffer = new Float32Array(array);
281
+ if (!arrayBuffer) throw new Error("不支持的数组类型" + tem);
282
+ const buffer = device2.createBuffer({
283
+ size: arrayBuffer.byteLength,
284
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
285
+ });
286
+ device2.queue.writeBuffer(buffer, 0, arrayBuffer);
287
+ buffers.push({ name, buffer });
288
+ });
289
+ const group = device2.createBindGroup({
290
+ layout: this.groupLayout,
291
+ entries: buffers.map((item, index) => ({
292
+ binding: index,
293
+ resource: { buffer: item.buffer }
294
+ }))
295
+ });
296
+ return {
297
+ group,
298
+ buffers
299
+ };
300
+ }
301
+ /** 数据映射回模版数据
302
+ * @param array
303
+ * @param key
304
+ */
305
+ dataMap(array, key) {
306
+ if (!(key in this.template)) throw new Error("未找到数据字段:" + key);
307
+ if (isStructArray(this.template[key])) {
308
+ const tems = this.template[key], count = array.length / tems.stride, list = [];
309
+ for (let i = 0; i < count; i++) {
310
+ const base = i * tems.stride;
311
+ const obj = {};
312
+ tems.layout.forEach((item) => {
313
+ const data = array.slice(base + item.offset, base + item.offset + item.size);
314
+ obj[item.name] = data.length === 1 ? data[0] : [...data];
315
+ });
316
+ list.push(obj);
317
+ }
318
+ return list;
319
+ }
320
+ if (isStruct(this.template[key])) {
321
+ const tem = this.template[key];
322
+ const obj = {};
323
+ tem.forEach((item) => {
324
+ const data = array.slice(item.offset, item.offset + item.size);
325
+ obj[item.name] = data.length === 1 ? data[0] : [...data];
326
+ });
327
+ return obj;
328
+ }
329
+ return array;
330
+ }
331
+ /** 开始计算
332
+ * @param group 数据组
333
+ * @param workgroupCount 工作组大小
334
+ * @param synchronize 需要同步的数据字段
335
+ * @returns
336
+ */
337
+ async computed(group, workgroupCount, synchronize = []) {
338
+ if (!this.pipeline) throw new Error("未找到可用计算管线,请确保计算管线已经创建成功");
339
+ const device2 = this.device;
340
+ const pipeline = this.pipeline;
341
+ const encoder = device2.createCommandEncoder();
342
+ const pass = encoder.beginComputePass();
343
+ pass.setPipeline(pipeline);
344
+ pass.setBindGroup(0, group.group);
345
+ pass.dispatchWorkgroups(workgroupCount[0], workgroupCount[1], workgroupCount[2]);
346
+ pass.end();
347
+ const syncBuffers = group.buffers?.map((item) => {
348
+ if (synchronize?.includes(item.name)) {
349
+ const stagingBuffer = device2.createBuffer({
350
+ size: item.buffer.size,
351
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
352
+ });
353
+ encoder.copyBufferToBuffer(item.buffer, 0, stagingBuffer, 0, stagingBuffer.size);
354
+ return { buffer: stagingBuffer, name: item.name };
355
+ }
356
+ }).filter((i) => !!i);
357
+ device2.queue.submit([encoder.finish()]);
358
+ await device2.queue.onSubmittedWorkDone();
359
+ const map = /* @__PURE__ */ new Map();
360
+ await Promise.all(
361
+ syncBuffers.map(async (item) => {
362
+ await item.buffer.mapAsync(GPUMapMode.READ);
363
+ const mappedRange = item.buffer.getMappedRange();
364
+ let arrayBuffer = null;
365
+ if (this.template[item.name] instanceof Float32Array) arrayBuffer = new Float32Array(mappedRange);
366
+ else if (this.template[item.name] instanceof Uint32Array) arrayBuffer = new Uint32Array(mappedRange);
367
+ else if (this.template[item.name] instanceof Int32Array) arrayBuffer = new Int32Array(mappedRange);
368
+ else arrayBuffer = new Float32Array(mappedRange);
369
+ map.set(item.name, arrayBuffer);
370
+ })
371
+ );
372
+ const results = synchronize.map((name) => map.get(name));
373
+ return results;
374
+ }
375
+ /** 初始化gpu设备
376
+ * @returns
377
+ */
378
+ static async init(opt = {}) {
379
+ if (adapter$1 && device$1) return;
380
+ if (typeof globalThis !== "undefined" && typeof window === "undefined") {
381
+ const { create, globals } = await include("webgpu", false);
382
+ Object.assign(globalThis, globals);
383
+ if (!globalThis.navigator) globalThis.navigator = {};
384
+ Object.assign(globalThis.navigator, { gpu: create([]) });
385
+ }
386
+ if (!navigator.gpu) throw new Error("该环境不支持webgpu");
387
+ if (!adapter$1) adapter$1 = await navigator.gpu.requestAdapter(opt);
388
+ if (!adapter$1) throw new Error("获取适配器失败");
389
+ device$1 = await adapter$1.requestDevice(opt);
390
+ if (!adapter$1) throw new Error("获取设备失败");
391
+ }
392
+ static set(adapter_, device_) {
393
+ adapter$1 = adapter_;
394
+ device$1 = device_;
395
+ }
396
+ /** 注销gpu设备
397
+ */
398
+ static destroy() {
399
+ if (device$1) device$1.destroy();
400
+ device$1 = null;
401
+ }
402
+ /**
403
+ * @param data
404
+ */
405
+ static buildBufferTypeByData(data) {
406
+ const bufferDataType = Object.keys(data).reduce((obj, k) => {
407
+ let value = data[k];
408
+ if (Array.isArray(value) && typeof value[0] === "number") value = new Float32Array();
409
+ if (Array.isArray(value)) {
410
+ if (typeof value[0] === "object" || value.length) {
411
+ const buffer = buildStructTypeByData(value[0]);
412
+ obj[k] = buffer;
413
+ } else console.log(`字段:${k}, 不支持该值对应数据类型或数组为空`);
414
+ } else if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
415
+ obj[k] = value;
416
+ } else if (typeof value === "object") {
417
+ const buffer = buildStructTypeByData(value);
418
+ obj[k] = buffer.layout;
419
+ } else console.log(`字段:${k}, 不支持的数据类型`);
420
+ return obj;
421
+ }, {});
422
+ return bufferDataType;
423
+ }
424
+ /** 通过数据创建
425
+ * @param opt
426
+ * @returns
427
+ */
428
+ static async fromByData(opt) {
429
+ let { data, ...option } = opt;
430
+ const bufferDataType = this.buildBufferTypeByData(data);
431
+ const gpuComputed = new GpuComputed(bufferDataType, option);
432
+ await gpuComputed.initPipeline();
433
+ return gpuComputed;
434
+ }
435
+ /** 快捷计算方法
436
+ * @param opt
437
+ * @returns
438
+ */
439
+ static async computed(opt) {
440
+ let { data, map = false, workgroupCount, synchronize, onSuccess, ...option } = opt;
441
+ const gpuComputed = await this.fromByData({ data, ...option }), group = gpuComputed.createBindGroup(data), results = await gpuComputed.computed(group, workgroupCount, synchronize);
442
+ onSuccess && onSuccess({ gpuComputed, group, results });
443
+ if (map) return results.map((data2, i) => gpuComputed.dataMap(data2, synchronize[i]));
444
+ return results;
445
+ }
446
+ }
447
+ let adapter = null;
448
+ let device = null;
449
+ async function initWebgpu(opt = {}) {
450
+ if (adapter && device) return;
451
+ if (typeof globalThis !== "undefined" && typeof window === "undefined") {
452
+ const { create, globals } = await include("webgpu", false);
453
+ Object.assign(globalThis, globals);
454
+ if (!globalThis.navigator) globalThis.navigator = {};
455
+ Object.assign(globalThis.navigator, { gpu: create([]) });
456
+ }
457
+ if (!navigator.gpu) throw new Error("该环境不支持webgpu");
458
+ if (!adapter) adapter = await navigator.gpu.requestAdapter(opt);
459
+ if (!adapter) throw new Error("获取适配器失败");
460
+ device = await adapter.requestDevice(opt);
461
+ if (!adapter) throw new Error("获取设备失败");
462
+ GpuComputed.set(adapter, device);
463
+ return { device, adapter };
464
+ }
465
+ async function destroyWebgpu() {
466
+ if (adapter && device) {
467
+ device.destroy();
468
+ GpuComputed.destroy();
469
+ adapter = null;
470
+ device = null;
471
+ }
472
+ }
473
+ const getDevice = () => {
474
+ if (!device) throw new Error("未初始化webgpu");
475
+ return device;
476
+ };
477
+ const getAdapter = () => {
478
+ if (!adapter) throw new Error("未初始化webgpu");
479
+ return adapter;
480
+ };
481
+ function createBuffer(device2, options) {
482
+ const { data, size, usage, mappedAtCreation = false, label } = options;
483
+ let bufferSize = size ?? 0;
484
+ if (data) {
485
+ bufferSize = data.byteLength;
486
+ }
487
+ bufferSize = Math.ceil(bufferSize / 4) * 4;
488
+ const buffer = device2.createBuffer({
489
+ size: bufferSize,
490
+ usage,
491
+ mappedAtCreation
492
+ });
493
+ if (label) {
494
+ buffer.label = label;
495
+ }
496
+ if (data) {
497
+ if (mappedAtCreation) {
498
+ const mapping = buffer.getMappedRange();
499
+ new Uint8Array(mapping).set(
500
+ data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer)
501
+ );
502
+ buffer.unmap();
503
+ } else {
504
+ device2.queue.writeBuffer(
505
+ buffer,
506
+ 0,
507
+ data instanceof ArrayBuffer ? data : data.buffer
508
+ );
509
+ }
510
+ }
511
+ return buffer;
512
+ }
513
+ function createTexture(device2, options) {
514
+ const {
515
+ size,
516
+ format,
517
+ usage,
518
+ dimension = "2d",
519
+ mipLevelCount = 1,
520
+ sampleCount = 1,
521
+ // data,
522
+ // bytesPerRow,
523
+ label
524
+ } = options;
525
+ const texture = device2.createTexture({
526
+ size,
527
+ format,
528
+ usage,
529
+ dimension,
530
+ mipLevelCount,
531
+ sampleCount
532
+ });
533
+ if (label) {
534
+ texture.label = label;
535
+ }
536
+ return texture;
537
+ }
538
+ function createRenderPipeline(device2, options) {
539
+ const {
540
+ layout = "auto",
541
+ vertex: vertex2,
542
+ primitive,
543
+ depthStencil,
544
+ multisample,
545
+ fragment: fragment2,
546
+ label
547
+ } = options;
548
+ const pipeline = device2.createRenderPipeline({
549
+ layout,
550
+ vertex: vertex2,
551
+ primitive,
552
+ depthStencil,
553
+ multisample,
554
+ fragment: fragment2
555
+ });
556
+ if (label) {
557
+ pipeline.label = label;
558
+ }
559
+ return pipeline;
560
+ }
561
+ function createComputePipeline(device2, options) {
562
+ const { layout = "auto", compute, label } = options;
563
+ const pipeline = device2.createComputePipeline({
564
+ layout,
565
+ compute
566
+ });
567
+ if (label) {
568
+ pipeline.label = label;
569
+ }
570
+ return pipeline;
571
+ }
572
+ const FORMAT_BYTES = {
573
+ rgba8unorm: 4,
574
+ "rgba8unorm-srgb": 4,
575
+ bgra8unorm: 4,
576
+ rg8unorm: 2,
577
+ r8unorm: 1,
578
+ r16float: 2,
579
+ r32float: 4,
580
+ rgba16float: 8,
581
+ rgba32float: 16,
582
+ depth32float: 4
583
+ };
584
+ async function readGPUBuffer(buffer, size, Type = Uint8Array, device2 = getDevice()) {
585
+ const readBuffer = device2.createBuffer({
586
+ size,
587
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
588
+ });
589
+ const encoder = device2.createCommandEncoder();
590
+ encoder.copyBufferToBuffer(buffer, 0, readBuffer, 0, size);
591
+ device2.queue.submit([encoder.finish()]);
592
+ await readBuffer.mapAsync(GPUMapMode.READ);
593
+ const mapped = readBuffer.getMappedRange();
594
+ const copy = mapped.slice(0);
595
+ const result = new Type(copy);
596
+ readBuffer.unmap();
597
+ return result;
598
+ }
599
+ async function readTextureBuffer(texture, options = {}, device2 = getDevice()) {
600
+ const mipLevel = options.mipLevel ?? 0;
601
+ const width = Math.max(1, (texture.width ?? 1) >> mipLevel);
602
+ const height = Math.max(1, (texture.height ?? 1) >> mipLevel);
603
+ const layer = options.face ?? options.layer ?? 0;
604
+ const format = texture.format ?? "rgba8unorm";
605
+ const bytesPerPixel = FORMAT_BYTES[format] ?? 4;
606
+ const bytesPerRow = Math.ceil(width * bytesPerPixel / 256) * 256;
607
+ const bufferSize = bytesPerRow * height;
608
+ const buffer = device2.createBuffer({
609
+ size: bufferSize,
610
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
611
+ });
612
+ const encoder = device2.createCommandEncoder();
613
+ let srcTexture = texture;
614
+ try {
615
+ encoder.copyTextureToBuffer(
616
+ {
617
+ texture: srcTexture,
618
+ mipLevel,
619
+ origin: { x: 0, y: 0, z: layer }
620
+ },
621
+ {
622
+ buffer,
623
+ bytesPerRow,
624
+ rowsPerImage: height
625
+ },
626
+ {
627
+ width,
628
+ height,
629
+ depthOrArrayLayers: 1
630
+ }
631
+ );
632
+ } catch {
633
+ const temp = device2.createTexture({
634
+ size: [width, height, 1],
635
+ format,
636
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
637
+ });
638
+ encoder.copyTextureToTexture(
639
+ { texture: srcTexture },
640
+ { texture: temp },
641
+ { width, height, depthOrArrayLayers: 1 }
642
+ );
643
+ encoder.copyTextureToBuffer(
644
+ {
645
+ texture: temp
646
+ },
647
+ {
648
+ buffer,
649
+ bytesPerRow,
650
+ rowsPerImage: height
651
+ },
652
+ {
653
+ width,
654
+ height,
655
+ depthOrArrayLayers: 1
656
+ }
657
+ );
658
+ }
659
+ device2.queue.submit([encoder.finish()]);
660
+ await buffer.mapAsync(GPUMapMode.READ);
661
+ const mapped = new Uint8Array(buffer.getMappedRange());
662
+ const result = new Uint8Array(width * height * bytesPerPixel);
663
+ for (let y = 0; y < height; y++) {
664
+ const srcOffset = y * bytesPerRow;
665
+ const dstOffset = y * width * bytesPerPixel;
666
+ result.set(
667
+ mapped.subarray(
668
+ srcOffset,
669
+ srcOffset + width * bytesPerPixel
670
+ ),
671
+ dstOffset
672
+ );
673
+ }
674
+ buffer.unmap();
675
+ return {
676
+ data: result,
677
+ width,
678
+ height,
679
+ bytesPerPixel,
680
+ format
681
+ };
682
+ }
683
+ async function readCubeTexture(texture, face = 0, mipLevel = 0, device2 = getDevice()) {
684
+ const result = await readTextureBuffer(texture, {
685
+ face,
686
+ mipLevel
687
+ }, device2);
688
+ return {
689
+ data: result.data,
690
+ width: result.width,
691
+ height: result.height,
692
+ format: result.format,
693
+ bytesPerPixel: result.bytesPerPixel
694
+ };
695
+ }
696
+ const shaderLocationMap = /* @__PURE__ */ new Map();
697
+ shaderLocationMap.set("position", 0).set("normal ", 1).set("uv", 2).set("color", 3);
698
+ class BufferGeometry extends THREE.BufferGeometry {
699
+ gpuAttributes = /* @__PURE__ */ new Map();
700
+ indexBuffer;
701
+ indexFormat;
702
+ needsUpdate = true;
703
+ count = 0;
704
+ update() {
705
+ if (!this.needsUpdate) return;
706
+ const device2 = getDevice();
707
+ let index = 0;
708
+ for (const name in this.attributes) {
709
+ const attr = this.attributes[name];
710
+ if (name === "position") this.count = attr.count;
711
+ const array = attr.array;
712
+ const stride = attr.itemSize * 4;
713
+ const buffer = device2.createBuffer({
714
+ size: array.byteLength,
715
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
716
+ });
717
+ device2.queue.writeBuffer(buffer, 0, array);
718
+ const format = this.getVertexFormat(attr);
719
+ const shaderLocation = shaderLocationMap.get(name) ?? shaderLocationMap.size + 2 + index;
720
+ this.gpuAttributes.set(name, { buffer, stride, format, shaderLocation });
721
+ index++;
722
+ }
723
+ if (this.index) {
724
+ const array = this.index.array;
725
+ this.indexBuffer = device2.createBuffer({
726
+ size: array.byteLength,
727
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
728
+ });
729
+ device2.queue.writeBuffer(this.indexBuffer, 0, array);
730
+ this.indexFormat = array instanceof Uint32Array ? "uint32" : "uint16";
731
+ }
732
+ this.needsUpdate = false;
733
+ }
734
+ setAttribute(name, attribute) {
735
+ super.setAttribute(name, attribute);
736
+ this.needsUpdate = true;
737
+ return this;
738
+ }
739
+ getVertexFormat(attr) {
740
+ const itemSize = attr.itemSize;
741
+ const array = attr.array;
742
+ const type = array instanceof Float32Array ? "float32" : array instanceof Uint32Array ? "uint32" : array instanceof Uint16Array ? "uint16" : "float32";
743
+ if (itemSize === 1) return `${type}`;
744
+ if (itemSize === 2) return `${type}x2`;
745
+ if (itemSize === 3) return `${type}x3`;
746
+ if (itemSize === 4) return `${type}x4`;
747
+ throw new Error("unsupported attribute size");
748
+ }
749
+ }
750
+ const layoutCache$2 = /* @__PURE__ */ new WeakMap();
751
+ const OBJECT_UNIFORM_SIZE = 128;
752
+ class Object3D extends THREE.Object3D {
753
+ static get bindGroupLayout() {
754
+ const device2 = getDevice();
755
+ let layout = layoutCache$2.get(device2);
756
+ if (layout) return layout;
757
+ layout = device2.createBindGroupLayout({
758
+ entries: [
759
+ {
760
+ binding: 0,
761
+ visibility: GPUShaderStage.VERTEX,
762
+ buffer: { type: "uniform" }
763
+ }
764
+ ]
765
+ });
766
+ layoutCache$2.set(device2, layout);
767
+ return layout;
768
+ }
769
+ buffer;
770
+ bindGroup;
771
+ needsUpdate = true;
772
+ material;
773
+ geometry;
774
+ uniformArray = new Float32Array(OBJECT_UNIFORM_SIZE / 4);
775
+ constructor(geometry, material) {
776
+ super();
777
+ this.material = material;
778
+ this.geometry = geometry;
779
+ const device2 = getDevice();
780
+ this.buffer = device2.createBuffer({
781
+ size: OBJECT_UNIFORM_SIZE,
782
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
783
+ });
784
+ this.bindGroup = device2.createBindGroup({
785
+ layout: Object3D.bindGroupLayout,
786
+ entries: [
787
+ {
788
+ binding: 0,
789
+ resource: {
790
+ buffer: this.buffer
791
+ }
792
+ }
793
+ ]
794
+ });
795
+ }
796
+ updateMatrixWorld(force) {
797
+ super.updateMatrixWorld(force);
798
+ this.needsUpdate = true;
799
+ }
800
+ _updateBuffer() {
801
+ if (!this.needsUpdate) return;
802
+ const device2 = getDevice();
803
+ const model = this.matrixWorld;
804
+ const normal = new THREE.Matrix3().getNormalMatrix(model);
805
+ let offset = 0;
806
+ this.uniformArray.set(model.elements, offset);
807
+ offset += 16;
808
+ const n = normal.elements;
809
+ this.uniformArray[offset++] = n[0];
810
+ this.uniformArray[offset++] = n[1];
811
+ this.uniformArray[offset++] = n[2];
812
+ this.uniformArray[offset++] = 0;
813
+ this.uniformArray[offset++] = n[3];
814
+ this.uniformArray[offset++] = n[4];
815
+ this.uniformArray[offset++] = n[5];
816
+ this.uniformArray[offset++] = 0;
817
+ this.uniformArray[offset++] = n[6];
818
+ this.uniformArray[offset++] = n[7];
819
+ this.uniformArray[offset++] = n[8];
820
+ this.uniformArray[offset++] = 0;
821
+ device2.queue.writeBuffer(this.buffer, 0, this.uniformArray);
822
+ this.needsUpdate = false;
823
+ }
824
+ pipeline = null;
825
+ createPipeline(format) {
826
+ this.pipeline = this.material.createPipeline(this.geometry.gpuAttributes, format);
827
+ }
828
+ update(format) {
829
+ this.geometry.update();
830
+ this.material.update();
831
+ if (!this.pipeline) this.createPipeline(format);
832
+ if (this.needsUpdate) this._updateBuffer();
833
+ }
834
+ }
835
+ const layoutCache$1 = /* @__PURE__ */ new WeakMap();
836
+ const CAMERA_UNIFORM_SIZE = 224;
837
+ class PerspectiveCamera extends THREE.PerspectiveCamera {
838
+ static get bindGroupLayout() {
839
+ const device2 = getDevice();
840
+ let layout = layoutCache$1.get(device2);
841
+ if (layout) return layout;
842
+ layout = device2.createBindGroupLayout({
843
+ entries: [
844
+ {
845
+ binding: 0,
846
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
847
+ buffer: {
848
+ type: "uniform"
849
+ }
850
+ }
851
+ ]
852
+ });
853
+ layoutCache$1.set(device2, layout);
854
+ return layout;
855
+ }
856
+ buffer;
857
+ bindGroup;
858
+ _needsUpdate = true;
859
+ get needsUpdate() {
860
+ return this._needsUpdate;
861
+ }
862
+ set needsUpdate(v) {
863
+ this._needsUpdate = v;
864
+ }
865
+ uniformArray = new Float32Array(CAMERA_UNIFORM_SIZE / 4);
866
+ constructor(fov, aspect, near, far) {
867
+ super(fov, aspect, near, far);
868
+ const device2 = getDevice();
869
+ this.buffer = device2.createBuffer({
870
+ size: 256,
871
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
872
+ });
873
+ this.bindGroup = device2.createBindGroup({
874
+ layout: PerspectiveCamera.bindGroupLayout,
875
+ entries: [
876
+ {
877
+ binding: 0,
878
+ resource: {
879
+ buffer: this.buffer
880
+ }
881
+ }
882
+ ]
883
+ });
884
+ }
885
+ _updateBuffer() {
886
+ if (!this.needsUpdate) return;
887
+ const device2 = getDevice();
888
+ this.updateMatrixWorld();
889
+ this.updateProjectionMatrix();
890
+ const view = this.matrixWorldInverse;
891
+ const proj = this.projectionMatrix.clone();
892
+ const vp = new THREE.Matrix4().multiplyMatrices(proj, view);
893
+ const pos = this.position;
894
+ let offset = 0;
895
+ const a = this.uniformArray;
896
+ a.set(view.elements, offset);
897
+ offset += 16;
898
+ a.set(proj.elements, offset);
899
+ offset += 16;
900
+ a.set(vp.elements, offset);
901
+ offset += 16;
902
+ a[offset++] = pos.x;
903
+ a[offset++] = pos.y;
904
+ a[offset++] = pos.z;
905
+ a[offset++] = 0;
906
+ a[offset++] = this.near;
907
+ a[offset++] = this.far;
908
+ a[offset++] = 0;
909
+ a[offset++] = 0;
910
+ device2.queue.writeBuffer(
911
+ this.buffer,
912
+ 0,
913
+ a
914
+ );
915
+ this.needsUpdate = false;
916
+ }
917
+ updateMatrix() {
918
+ super.updateMatrix();
919
+ this.needsUpdate = true;
920
+ }
921
+ updateMatrixWorld() {
922
+ super.updateMatrixWorld();
923
+ this.needsUpdate = true;
924
+ }
925
+ updateProjectionMatrix() {
926
+ super.updateProjectionMatrix();
927
+ this.needsUpdate = true;
928
+ }
929
+ update() {
930
+ if (this.needsUpdate) this._updateBuffer();
931
+ }
932
+ }
933
+ const samplerCache = /* @__PURE__ */ new Map();
934
+ function samplerKey(desc) {
935
+ return [
936
+ desc.magFilter,
937
+ desc.minFilter,
938
+ desc.mipmapFilter,
939
+ desc.addressModeU,
940
+ desc.addressModeV,
941
+ desc.addressModeW,
942
+ desc.compare
943
+ ].join("_");
944
+ }
945
+ function getSampler(desc) {
946
+ const device2 = getDevice();
947
+ const key = samplerKey(desc);
948
+ let sampler = samplerCache.get(key);
949
+ if (!sampler) {
950
+ sampler = device2.createSampler(desc);
951
+ samplerCache.set(key, sampler);
952
+ }
953
+ return sampler;
954
+ }
955
+ class Texture {
956
+ depthTexture;
957
+ depthView;
958
+ texture;
959
+ view;
960
+ width;
961
+ height;
962
+ format;
963
+ sampler;
964
+ needDepthTexture;
965
+ depthOrArrayLayers;
966
+ dimension;
967
+ viewDimension;
968
+ constructor(width, height = width, des) {
969
+ const {
970
+ format = navigator.gpu.getPreferredCanvasFormat(),
971
+ sampler,
972
+ depthOrArrayLayers = 1,
973
+ dimension = "2d",
974
+ viewDimension = "2d",
975
+ init = true,
976
+ needDepthTexture = false
977
+ } = des ?? {};
978
+ this.format = format;
979
+ this.depthOrArrayLayers = depthOrArrayLayers;
980
+ this.dimension = dimension;
981
+ this.viewDimension = viewDimension;
982
+ this.needDepthTexture = needDepthTexture;
983
+ this.sampler = getSampler({
984
+ magFilter: "linear",
985
+ minFilter: "linear",
986
+ mipmapFilter: "linear",
987
+ ...sampler
988
+ });
989
+ init && this.init(width, height);
990
+ }
991
+ init(width, height = width) {
992
+ if (width === this.width && height === this.height) return;
993
+ this.destroy();
994
+ const device2 = getDevice();
995
+ this.width = width;
996
+ this.height = height;
997
+ if (this.needDepthTexture) {
998
+ this.depthTexture = device2.createTexture({
999
+ size: [width, height],
1000
+ format: "depth24plus",
1001
+ label: "depthTexture",
1002
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
1003
+ });
1004
+ this.depthView = this.depthTexture.createView();
1005
+ }
1006
+ this.texture = device2.createTexture({
1007
+ size: {
1008
+ width,
1009
+ height,
1010
+ depthOrArrayLayers: this.depthOrArrayLayers
1011
+ },
1012
+ format: this.format,
1013
+ dimension: this.dimension,
1014
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
1015
+ });
1016
+ this.view = this.texture.createView({
1017
+ dimension: this.viewDimension
1018
+ });
1019
+ }
1020
+ destroy() {
1021
+ this.texture?.destroy();
1022
+ this.depthTexture?.destroy();
1023
+ }
1024
+ static async fromByImage(bitmap) {
1025
+ const device2 = getDevice();
1026
+ const texture = new Texture(bitmap.width, bitmap.height);
1027
+ device2.queue.copyExternalImageToTexture(
1028
+ { source: bitmap },
1029
+ { texture: texture.texture },
1030
+ { width: bitmap.width, height: bitmap.height }
1031
+ );
1032
+ return texture;
1033
+ }
1034
+ static async fromURL(url) {
1035
+ const img = await fetch(url).then((r) => r.blob());
1036
+ const bitmap = await createImageBitmap(img);
1037
+ return await this.fromByImage(bitmap);
1038
+ }
1039
+ }
1040
+ class CubeTexture extends Texture {
1041
+ get cubeView() {
1042
+ return this.view;
1043
+ }
1044
+ faceViews = new Array(6);
1045
+ constructor(width, height = width, des) {
1046
+ super(width, height, {
1047
+ viewDimension: "cube",
1048
+ dimension: "2d",
1049
+ depthOrArrayLayers: 6,
1050
+ ...des
1051
+ });
1052
+ this.buildFaceViews();
1053
+ }
1054
+ getFaceView(face) {
1055
+ return this.faceViews[face];
1056
+ }
1057
+ buildFaceViews() {
1058
+ for (let i = 0; i < 6; i++) {
1059
+ this.faceViews[i] = this.texture.createView({
1060
+ dimension: "2d",
1061
+ baseArrayLayer: i,
1062
+ arrayLayerCount: 1
1063
+ });
1064
+ }
1065
+ }
1066
+ }
1067
+ function preprocessShaderFSM(source, defs) {
1068
+ let State;
1069
+ ((State2) => {
1070
+ State2[State2["TEXT"] = 0] = "TEXT";
1071
+ State2[State2["DIRECTIVE"] = 1] = "DIRECTIVE";
1072
+ })(State || (State = {}));
1073
+ let state = 0;
1074
+ let i = 0;
1075
+ const len = source.length;
1076
+ let output = "";
1077
+ const condStack = [];
1078
+ let enabled = true;
1079
+ function readUntilLineEnd() {
1080
+ let start = i;
1081
+ while (i < len && source[i] !== "\n") i++;
1082
+ return source.slice(start, i);
1083
+ }
1084
+ while (i < len) {
1085
+ const ch = source[i];
1086
+ switch (state) {
1087
+ case 0:
1088
+ if (ch === "#") {
1089
+ state = 1;
1090
+ i++;
1091
+ } else {
1092
+ if (enabled) output += ch;
1093
+ i++;
1094
+ }
1095
+ break;
1096
+ case 1:
1097
+ const line = "#" + readUntilLineEnd();
1098
+ i++;
1099
+ state = 0;
1100
+ let m = line.match(/^#if\s*\(\s*([A-Za-z0-9_]+)\s*\)/);
1101
+ if (m) {
1102
+ const key = m[1];
1103
+ const cond = !!defs[key];
1104
+ condStack.push(enabled);
1105
+ enabled = enabled && cond;
1106
+ break;
1107
+ }
1108
+ if (/^#else/.test(line)) {
1109
+ const parent = condStack[condStack.length - 1] ?? true;
1110
+ enabled = parent && !enabled;
1111
+ break;
1112
+ }
1113
+ if (/^#endif/.test(line)) {
1114
+ enabled = condStack.pop() ?? true;
1115
+ break;
1116
+ }
1117
+ if (enabled) {
1118
+ output += line + "\n";
1119
+ }
1120
+ break;
1121
+ }
1122
+ }
1123
+ return output;
1124
+ }
1125
+ const MATERIAL_UNIFORM_SIZE = 256;
1126
+ const pipelineCache = /* @__PURE__ */ new Map();
1127
+ const layoutCache = /* @__PURE__ */ new Map();
1128
+ function getUniformType(v, atomic = false) {
1129
+ if (v instanceof Matrix4) return "mat4";
1130
+ if (v instanceof Matrix3) return "mat3";
1131
+ if (v instanceof Color) return "vec3";
1132
+ if (v instanceof Vector2) return "vec2";
1133
+ if (v instanceof Vector3) return "vec3";
1134
+ if (v instanceof Vector4) return "vec4";
1135
+ if (typeof v === "number") return "f32";
1136
+ if (v instanceof CubeTexture) return "texture_cube";
1137
+ if (v instanceof Texture) return "texture_2d";
1138
+ if (Array.isArray(v) || v instanceof Float32Array) return `array<${atomic ? "atomic<f32>" : "f32"}>`;
1139
+ if (v instanceof Uint32Array || v instanceof Uint8Array) return `array<${atomic ? "atomic<u32>" : "u32"}>`;
1140
+ return "f32";
1141
+ }
1142
+ function getArray(v) {
1143
+ if (Array.isArray(v)) return new Float32Array(v);
1144
+ if (v instanceof Float32Array || v instanceof Uint32Array || v instanceof Uint8Array) return v;
1145
+ return new Float32Array();
1146
+ }
1147
+ const vertexFormatToWGSL = {
1148
+ uint8x2: "vec2<u32>",
1149
+ uint8x4: "vec4<u32>",
1150
+ sint8x2: "vec2<i32>",
1151
+ sint8x4: "vec4<i32>",
1152
+ unorm8x2: "vec2<f32>",
1153
+ unorm8x4: "vec4<f32>",
1154
+ snorm8x2: "vec2<f32>",
1155
+ snorm8x4: "vec4<f32>",
1156
+ uint16x2: "vec2<u32>",
1157
+ uint16x4: "vec4<u32>",
1158
+ sint16x2: "vec2<i32>",
1159
+ sint16x4: "vec4<i32>",
1160
+ unorm16x2: "vec2<f32>",
1161
+ unorm16x4: "vec4<f32>",
1162
+ snorm16x2: "vec2<f32>",
1163
+ snorm16x4: "vec4<f32>",
1164
+ float16x2: "vec2<f16>",
1165
+ float16x4: "vec4<f16>",
1166
+ float32: "f32",
1167
+ float32x2: "vec2<f32>",
1168
+ float32x3: "vec3<f32>",
1169
+ float32x4: "vec4<f32>",
1170
+ uint32: "u32",
1171
+ uint32x2: "vec2<u32>",
1172
+ uint32x3: "vec3<u32>",
1173
+ uint32x4: "vec4<u32>",
1174
+ sint32: "i32",
1175
+ sint32x2: "vec2<i32>",
1176
+ sint32x3: "vec3<i32>",
1177
+ sint32x4: "vec4<i32>"
1178
+ };
1179
+ const vertexCommon = (
1180
+ /* wgsl */
1181
+ `
1182
+ struct Camera {
1183
+ view : mat4x4<f32>,
1184
+ projection : mat4x4<f32>,
1185
+ viewProjection : mat4x4<f32>,
1186
+ position : vec3<f32>,
1187
+ _pad0 : f32,
1188
+ near : f32,
1189
+ far : f32,
1190
+ _pad1 : vec2<f32>,
1191
+ };
1192
+
1193
+ @group(0) @binding(0)
1194
+ var<uniform> camera : Camera;
1195
+
1196
+ struct Object {
1197
+ modelMatrix : mat4x4<f32>,
1198
+ normalMatrix : mat3x3<f32>,
1199
+ };
1200
+
1201
+ @group(1) @binding(0)
1202
+ var<uniform> object : Object;
1203
+ `
1204
+ );
1205
+ class ShaderMaterial {
1206
+ vertex;
1207
+ fragment;
1208
+ pipeline;
1209
+ bindGroup;
1210
+ buffer;
1211
+ uniforms;
1212
+ uniformArray = new Float32Array(MATERIAL_UNIFORM_SIZE / 4);
1213
+ _bufferUniforms = {};
1214
+ _textureUniforms = {};
1215
+ _storageUniforms = {};
1216
+ topology;
1217
+ side;
1218
+ blend;
1219
+ writeMask;
1220
+ depthStencil;
1221
+ env;
1222
+ _needsUniformUpdate = true;
1223
+ get needsUniformUpdate() {
1224
+ return this._needsUniformUpdate;
1225
+ }
1226
+ set needsUniformUpdate(v) {
1227
+ this._needsUniformUpdate = v;
1228
+ }
1229
+ _needsBindGroupUpdate = true;
1230
+ get needsBindGroupUpdate() {
1231
+ return this._needsBindGroupUpdate;
1232
+ }
1233
+ set needsBindGroupUpdate(v) {
1234
+ this._needsBindGroupUpdate = v;
1235
+ }
1236
+ constructor(options) {
1237
+ const device2 = getDevice();
1238
+ this.vertex = vertexCommon + options.vertex;
1239
+ this.fragment = options.fragment;
1240
+ this.topology = options.topology;
1241
+ this.side = options.side ?? "front";
1242
+ this.blend = options.blend;
1243
+ this.writeMask = options.writeMask;
1244
+ this.depthStencil = options.depthStencil;
1245
+ this.env = options.env;
1246
+ this.buffer = device2.createBuffer({
1247
+ size: MATERIAL_UNIFORM_SIZE,
1248
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1249
+ });
1250
+ this._initUniform(options.uniforms || {});
1251
+ }
1252
+ /**
1253
+ * 初始化uniform
1254
+ * @param uniforms
1255
+ */
1256
+ _initUniform(uniforms) {
1257
+ const device2 = getDevice();
1258
+ this.uniforms = {};
1259
+ let binding = 1;
1260
+ const createObjectListener = (key, value, type, set) => {
1261
+ const uniform = {
1262
+ get value() {
1263
+ return value;
1264
+ },
1265
+ set value(v) {
1266
+ if (getUniformType(v) !== type) return;
1267
+ value = set(v, value);
1268
+ }
1269
+ };
1270
+ this.uniforms[key] = uniform;
1271
+ };
1272
+ const defaultListener = (key, value, type) => {
1273
+ createObjectListener(key, value, type, (newValue) => {
1274
+ this._bufferUniforms[key].value = newValue;
1275
+ this.needsUniformUpdate = true;
1276
+ return newValue;
1277
+ });
1278
+ this._bufferUniforms[key] = { type, value };
1279
+ };
1280
+ const arrayListener = (key, value, type) => {
1281
+ createObjectListener(key, value, type, (newValue, oldValue) => {
1282
+ const data = getArray(newValue);
1283
+ if (newValue.length !== oldValue.length) {
1284
+ this._storageUniforms[key].buffer = createBuffer(device2, {
1285
+ data,
1286
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
1287
+ });
1288
+ this.needsBindGroupUpdate = true;
1289
+ } else {
1290
+ device2.queue.writeBuffer(this._storageUniforms[key].buffer, 0, data);
1291
+ }
1292
+ this._storageUniforms[key].value = newValue;
1293
+ return newValue;
1294
+ });
1295
+ this._storageUniforms[key] = {
1296
+ type,
1297
+ value,
1298
+ binding,
1299
+ buffer: createBuffer(device2, {
1300
+ data: getArray(value),
1301
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
1302
+ })
1303
+ };
1304
+ binding += 1;
1305
+ };
1306
+ const textureListener = (key, value, type) => {
1307
+ createObjectListener(key, value, type, (newValue) => {
1308
+ this._textureUniforms[key].value = newValue;
1309
+ this._createBindGroup();
1310
+ this.needsUniformUpdate = true;
1311
+ return newValue;
1312
+ });
1313
+ this._textureUniforms[key] = { type, value, binding };
1314
+ binding += 2;
1315
+ };
1316
+ for (const key in uniforms) {
1317
+ let value = uniforms[key].value;
1318
+ const type = getUniformType(value, uniforms[key]?.atomic);
1319
+ if (Array.isArray(value) || value instanceof Float32Array || value instanceof Uint32Array || value instanceof Uint8Array) {
1320
+ arrayListener(key, value, type);
1321
+ this._storageUniforms[key].write = uniforms[key].write ?? false;
1322
+ } else if (type === "texture_cube" || type === "texture_2d") textureListener(key, value, type);
1323
+ else defaultListener(key, value, type);
1324
+ }
1325
+ this.needsBindGroupUpdate = true;
1326
+ this.needsUniformUpdate = true;
1327
+ }
1328
+ /**
1329
+ * 获取绑定组布局
1330
+ * @returns
1331
+ */
1332
+ _getBindGroupLayout() {
1333
+ const device2 = getDevice(), textureKeys = Object.keys(this._textureUniforms), storageUniformsKey = Object.keys(this._storageUniforms), key = "layout_" + JSON.stringify({
1334
+ textures: textureKeys.map((k) => ({
1335
+ name: k,
1336
+ binding: this._textureUniforms[k].binding,
1337
+ type: this._textureUniforms[k].type
1338
+ })),
1339
+ storages: storageUniformsKey.map((k) => ({
1340
+ name: k,
1341
+ binding: this._storageUniforms[k].binding,
1342
+ write: this._storageUniforms[k].write
1343
+ }))
1344
+ });
1345
+ if (layoutCache.has(key)) return layoutCache.get(key);
1346
+ const entries = [{
1347
+ binding: 0,
1348
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
1349
+ buffer: {}
1350
+ }];
1351
+ textureKeys.forEach((key2) => {
1352
+ const item = this._textureUniforms[key2];
1353
+ entries.push({
1354
+ binding: item.binding,
1355
+ visibility: GPUShaderStage.FRAGMENT,
1356
+ texture: item.type === "texture_cube" ? {
1357
+ "viewDimension": "cube"
1358
+ } : {}
1359
+ });
1360
+ entries.push({
1361
+ binding: item.binding + 1,
1362
+ visibility: GPUShaderStage.FRAGMENT,
1363
+ sampler: {}
1364
+ });
1365
+ });
1366
+ storageUniformsKey.forEach((key2) => {
1367
+ const item = this._storageUniforms[key2];
1368
+ entries.push({
1369
+ binding: item.binding,
1370
+ visibility: item.write ? GPUShaderStage.FRAGMENT : GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX,
1371
+ buffer: {
1372
+ type: item.write ? "storage" : "read-only-storage"
1373
+ }
1374
+ });
1375
+ });
1376
+ const layout = device2.createBindGroupLayout({ entries });
1377
+ layoutCache.set(key, layout);
1378
+ return layout;
1379
+ }
1380
+ /**
1381
+ * 创建绑定组
1382
+ */
1383
+ _createBindGroup() {
1384
+ const device2 = getDevice();
1385
+ const entries = [{
1386
+ binding: 0,
1387
+ resource: { buffer: this.buffer }
1388
+ }];
1389
+ for (const key in this._textureUniforms) {
1390
+ const meta = this._textureUniforms[key];
1391
+ const tex = meta.value;
1392
+ entries.push({
1393
+ binding: meta.binding,
1394
+ resource: tex.view
1395
+ });
1396
+ entries.push({
1397
+ binding: meta.binding + 1,
1398
+ resource: tex.sampler
1399
+ });
1400
+ }
1401
+ for (const key in this._storageUniforms) {
1402
+ const meta = this._storageUniforms[key];
1403
+ entries.push({
1404
+ binding: meta.binding,
1405
+ resource: meta.buffer
1406
+ });
1407
+ }
1408
+ this.bindGroup = device2.createBindGroup({
1409
+ layout: this._getBindGroupLayout(),
1410
+ entries
1411
+ });
1412
+ this.needsBindGroupUpdate = false;
1413
+ }
1414
+ /** 生成uniform 代码
1415
+ * @param type
1416
+ * @returns
1417
+ */
1418
+ _generateUniformWGSL(type) {
1419
+ let code = "";
1420
+ if (Object.keys(this._bufferUniforms).length) {
1421
+ code += "struct MaterialUniforms {\n";
1422
+ for (const key in this._bufferUniforms) {
1423
+ const type2 = this._bufferUniforms[key].type;
1424
+ if (type2 === "mat4")
1425
+ code += ` ${key} : mat4x4<f32>,
1426
+ `;
1427
+ else if (type2 === "mat3")
1428
+ code += ` ${key} : mat3x3<f32>,
1429
+ `;
1430
+ else if (type2 === "vec2")
1431
+ code += ` ${key} : vec2<f32>,
1432
+ `;
1433
+ else if (type2 === "vec3")
1434
+ code += ` ${key} : vec3<f32>,
1435
+ `;
1436
+ else if (type2 === "vec4")
1437
+ code += ` ${key} : vec4<f32>,
1438
+ `;
1439
+ else if (type2 === "f32")
1440
+ code += ` ${key} : f32,
1441
+ `;
1442
+ }
1443
+ code += "};\n";
1444
+ code += ` @group(2) @binding(0) var<uniform> uniforms : MaterialUniforms; `;
1445
+ }
1446
+ if (type === "fragment") {
1447
+ for (const key in this._textureUniforms) {
1448
+ const meta = this._textureUniforms[key];
1449
+ const b = meta.binding;
1450
+ if (meta.type === "texture_2d") {
1451
+ code += `
1452
+ @group(2) @binding(${b})
1453
+ var ${key} : texture_2d<f32>;
1454
+ @group(2) @binding(${b + 1})
1455
+ var ${key}Sampler : sampler;
1456
+ `;
1457
+ }
1458
+ if (meta.type === "texture_cube") {
1459
+ code += `
1460
+ @group(2) @binding(${b})
1461
+ var ${key} : texture_cube<f32>;
1462
+ @group(2) @binding(${b + 1})
1463
+ var ${key}Sampler : sampler;
1464
+ `;
1465
+ }
1466
+ }
1467
+ }
1468
+ return code;
1469
+ }
1470
+ /** 生成 顶点着色器入参类型代码
1471
+ * @param attrMap
1472
+ * @returns
1473
+ */
1474
+ _generateVertexInputWGSL(attrMap) {
1475
+ if (!attrMap.size) return "";
1476
+ let code = `struct VertexInput { `;
1477
+ attrMap.forEach((v, name) => code += `@location(${v.shaderLocation}) ${name}: ${vertexFormatToWGSL[v.format]}, `);
1478
+ code += `};`;
1479
+ return code;
1480
+ }
1481
+ /**
1482
+ * 生成 Storage 类型代码
1483
+ */
1484
+ _generateStorageWGSL(type) {
1485
+ return Object.keys(this._storageUniforms).map((name) => {
1486
+ const item = this._storageUniforms[name];
1487
+ if (item.write && type === "vertex") return "";
1488
+ return `@group(2) @binding(${item.binding}) var<storage, ${item.write ? "read_write" : "read"}> ${name}: ${item.type}; `;
1489
+ }).join("");
1490
+ }
1491
+ /** 创建渲染管线
1492
+ * @param attrMap
1493
+ * @param colorFormat
1494
+ * @param depthFormat
1495
+ * @returns
1496
+ */
1497
+ createPipeline(attrMap, colorFormat = "rgba8unorm", depthFormat = "depth24plus") {
1498
+ const vertexBuffers = [];
1499
+ attrMap.forEach((v) => {
1500
+ vertexBuffers.push({
1501
+ arrayStride: v.stride,
1502
+ attributes: [
1503
+ {
1504
+ shaderLocation: v.shaderLocation,
1505
+ offset: 0,
1506
+ format: v.format
1507
+ }
1508
+ ]
1509
+ });
1510
+ });
1511
+ const device2 = getDevice();
1512
+ const key = this.vertex + this.fragment + colorFormat + depthFormat + this.topology + this.side + Object.keys(this._textureUniforms).length + JSON.stringify(vertexBuffers);
1513
+ if (pipelineCache.has(key)) {
1514
+ this.pipeline = pipelineCache.get(key);
1515
+ return this.pipeline;
1516
+ }
1517
+ const env = { ...this.env };
1518
+ for (const key2 of attrMap.keys()) env[key2] = true;
1519
+ const moduleVertex = device2.createShaderModule({
1520
+ code: `${this._generateUniformWGSL("vertex")} ${this._generateStorageWGSL("vertex")} ${this._generateVertexInputWGSL(attrMap)} ${preprocessShaderFSM(this.vertex, env)}`
1521
+ });
1522
+ const moduleFragment = device2.createShaderModule({
1523
+ code: `${this._generateUniformWGSL("fragment")} ${this._generateStorageWGSL("fragment")} ${preprocessShaderFSM(this.fragment, env)}`
1524
+ });
1525
+ const pipeline = device2.createRenderPipeline({
1526
+ layout: device2.createPipelineLayout({
1527
+ bindGroupLayouts: [
1528
+ PerspectiveCamera.bindGroupLayout,
1529
+ Object3D.bindGroupLayout,
1530
+ this._getBindGroupLayout()
1531
+ ]
1532
+ }),
1533
+ vertex: {
1534
+ module: moduleVertex,
1535
+ entryPoint: "main",
1536
+ buffers: vertexBuffers
1537
+ },
1538
+ fragment: {
1539
+ module: moduleFragment,
1540
+ entryPoint: "main",
1541
+ targets: [{
1542
+ format: colorFormat,
1543
+ blend: this.blend,
1544
+ writeMask: this.writeMask
1545
+ }]
1546
+ },
1547
+ primitive: {
1548
+ topology: this.topology,
1549
+ cullMode: this.side
1550
+ },
1551
+ depthStencil: {
1552
+ depthWriteEnabled: true,
1553
+ format: depthFormat,
1554
+ depthCompare: "less",
1555
+ ...this.depthStencil
1556
+ }
1557
+ });
1558
+ pipelineCache.set(key, pipeline);
1559
+ this.pipeline = pipeline;
1560
+ return pipeline;
1561
+ }
1562
+ /**
1563
+ * 更新uniform 数据
1564
+ */
1565
+ _updateUniform() {
1566
+ const device2 = getDevice();
1567
+ let offset = 0;
1568
+ const align4 = (n) => n + 3 & -4;
1569
+ for (const key in this._bufferUniforms) {
1570
+ const value = this._bufferUniforms[key].value;
1571
+ offset = align4(offset);
1572
+ if (typeof value === "number") {
1573
+ this.uniformArray[offset++] = value;
1574
+ continue;
1575
+ }
1576
+ if (value instanceof Vector2) {
1577
+ this.uniformArray.set([value.x, value.y], offset);
1578
+ offset += 2;
1579
+ continue;
1580
+ }
1581
+ if (value instanceof Vector3) {
1582
+ this.uniformArray.set([value.x, value.y, value.z], offset);
1583
+ offset += 3;
1584
+ continue;
1585
+ }
1586
+ if (value instanceof Vector4) {
1587
+ this.uniformArray.set([value.x, value.y, value.z, value.w], offset);
1588
+ offset += 4;
1589
+ continue;
1590
+ }
1591
+ if (value.isColor) {
1592
+ const c = value;
1593
+ this.uniformArray[offset++] = c.r;
1594
+ this.uniformArray[offset++] = c.g;
1595
+ this.uniformArray[offset++] = c.b;
1596
+ continue;
1597
+ }
1598
+ if (value.isMatrix4) {
1599
+ this.uniformArray.set(value.elements, offset);
1600
+ offset += 16;
1601
+ continue;
1602
+ }
1603
+ if (value.isMatrix3) {
1604
+ const m = value.elements;
1605
+ this.uniformArray[offset + 0] = m[0];
1606
+ this.uniformArray[offset + 1] = m[1];
1607
+ this.uniformArray[offset + 2] = m[2];
1608
+ this.uniformArray[offset + 4] = m[3];
1609
+ this.uniformArray[offset + 5] = m[4];
1610
+ this.uniformArray[offset + 6] = m[5];
1611
+ this.uniformArray[offset + 8] = m[6];
1612
+ this.uniformArray[offset + 9] = m[7];
1613
+ this.uniformArray[offset + 10] = m[8];
1614
+ offset += 12;
1615
+ continue;
1616
+ }
1617
+ }
1618
+ device2.queue.writeBuffer(this.buffer, 0, this.uniformArray);
1619
+ this.needsUniformUpdate = false;
1620
+ }
1621
+ update() {
1622
+ if (this.needsBindGroupUpdate) this._createBindGroup();
1623
+ if (this.needsUniformUpdate) this._updateUniform();
1624
+ }
1625
+ /** 获取buffer
1626
+ * @param name
1627
+ * @returns
1628
+ */
1629
+ getStorageBuffer(name) {
1630
+ return this._storageUniforms[name]?.buffer;
1631
+ }
1632
+ }
1633
+ const transparentOption = {
1634
+ blend: {
1635
+ color: {
1636
+ srcFactor: "src-alpha",
1637
+ dstFactor: "one-minus-src-alpha",
1638
+ operation: "add"
1639
+ },
1640
+ alpha: {
1641
+ srcFactor: "one",
1642
+ dstFactor: "one-minus-src-alpha",
1643
+ operation: "add"
1644
+ }
1645
+ }
1646
+ };
1647
+ const vertex = (
1648
+ /* wgsl */
1649
+ `
1650
+ struct VertexOutput {
1651
+ @builtin(position) Position : vec4<f32>,
1652
+ #if(uv)
1653
+ @location(0) uv: vec2<f32>
1654
+ #endif
1655
+ };
1656
+
1657
+ @vertex
1658
+ fn main(input: VertexInput) -> VertexOutput {
1659
+ var out: VertexOutput;
1660
+ let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
1661
+ out.Position = camera.viewProjection * worldPos;
1662
+
1663
+ #if(uv)
1664
+ out.uv = input.uv; #endif
1665
+
1666
+ return out;
1667
+ }
1668
+ `
1669
+ );
1670
+ const fragment = (
1671
+ /* wgsl */
1672
+ `
1673
+ @fragment
1674
+ fn main(
1675
+ #if(uv)
1676
+ @location(0) uv: vec2<f32>
1677
+ #endif
1678
+ ) -> @location(0) vec4<f32> {
1679
+ #if(uv)
1680
+ let color = textureSample(map, mapSampler, uv);
1681
+ return vec4<f32>(uniforms.color, uniforms.opacity) * color;
1682
+ #else
1683
+ return vec4<f32>(uniforms.color, uniforms.opacity);
1684
+ #endif
1685
+ }
1686
+ `
1687
+ );
1688
+ class BaseMaterial extends ShaderMaterial {
1689
+ constructor(opt = {}) {
1690
+ super({
1691
+ vertex,
1692
+ fragment,
1693
+ side: opt.side ?? "front",
1694
+ topology: opt.topology ?? "triangle-list",
1695
+ uniforms: {
1696
+ opacity: { value: opt.opacity ?? 1 },
1697
+ color: { value: new Color(opt.color ?? 16777215) },
1698
+ map: { value: opt.map ?? new Texture(128, 128) }
1699
+ },
1700
+ ...opt.transparent && {
1701
+ ...transparentOption,
1702
+ writeMask: GPUColorWrite.ALL,
1703
+ depthStencil: {
1704
+ depthWriteEnabled: false,
1705
+ depthCompare: "less"
1706
+ }
1707
+ }
1708
+ });
1709
+ }
1710
+ }
1711
+ class DepthMaterial extends ShaderMaterial {
1712
+ constructor(opt = {}) {
1713
+ super({
1714
+ vertex: (
1715
+ /* wgsl */
1716
+ `
1717
+ struct VertexOutput {
1718
+ @builtin(position) Position : vec4<f32>,
1719
+ @location(0) worldDir: vec3<f32>,
1720
+ @location(1) near: f32,
1721
+ @location(2) far: f32,
1722
+ };
1723
+
1724
+ @vertex
1725
+ fn main(input: VertexInput) -> VertexOutput {
1726
+ var out: VertexOutput;
1727
+ let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
1728
+
1729
+ out.Position = camera.viewProjection * worldPos;
1730
+ out.worldDir = worldPos.xyz - camera.position;
1731
+ out.near = camera.near;
1732
+ out.far = camera.far;
1733
+
1734
+ return out;
1735
+ }
1736
+ `
1737
+ ),
1738
+ fragment: (
1739
+ /* wgsl */
1740
+ `
1741
+ fn packDepth(v: f32) -> vec4<f32> {
1742
+ var bitShift: vec4<f32> = vec4<f32>( 256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0 );
1743
+ var bitMask: vec4<f32> = vec4<f32>( 0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0 );
1744
+ var res: vec4<f32> = fract(v * bitShift);
1745
+ res -= res.xxyz * bitMask;
1746
+ return res;
1747
+ }
1748
+
1749
+ @fragment
1750
+ fn main(
1751
+ @location(0) worldDir: vec3<f32>,
1752
+ @location(1) near: f32,
1753
+ @location(2) far: f32
1754
+ ) -> @location(0) vec4<f32> {
1755
+ var depth = length(worldDir);
1756
+ depth = (depth - near) / (far - near);
1757
+ depth = clamp(depth, 0.0, 1.0);
1758
+ return packDepth(depth);
1759
+ }
1760
+ `
1761
+ ),
1762
+ side: opt.side ?? "none",
1763
+ topology: opt.topology ?? "triangle-list",
1764
+ depthStencil: {
1765
+ depthWriteEnabled: true,
1766
+ depthCompare: "greater"
1767
+ }
1768
+ });
1769
+ }
1770
+ }
1771
+ class OcclusionMaterial extends ShaderMaterial {
1772
+ constructor(opt) {
1773
+ super({
1774
+ vertex: (
1775
+ /* wgsl */
1776
+ `
1777
+ struct VertexOutput {
1778
+ @builtin(position) Position : vec4<f32>,
1779
+ @location(0) worldDir: vec3<f32>,
1780
+ @location(1) near: f32,
1781
+ @location(2) far: f32,
1782
+ @location(3) @interpolate(flat) vId: vec4<f32>,
1783
+ @location(4) @interpolate(flat) id: u32,
1784
+ };
1785
+
1786
+ fn packUint(v: u32) -> vec4<f32>{
1787
+ return vec4<f32>(
1788
+ f32(v & 255u),
1789
+ f32((v >> 8) & 255u),
1790
+ f32((v >> 16) & 255u),
1791
+ f32((v >> 24) & 255u)
1792
+ ) / 255.0;
1793
+ }
1794
+
1795
+ @vertex
1796
+ fn main(input: VertexInput) -> VertexOutput {
1797
+ var out: VertexOutput;
1798
+ let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
1799
+
1800
+ out.Position = camera.viewProjection * worldPos;
1801
+ out.worldDir = worldPos.xyz - camera.position;
1802
+ out.near = camera.near;
1803
+ out.far = camera.far;
1804
+ out.vId = packUint( input.id );
1805
+ out.id = input.id;
1806
+
1807
+ return out;
1808
+ }
1809
+ `
1810
+ ),
1811
+ fragment: (
1812
+ /* wgsl */
1813
+ `
1814
+ fn unpackDepth(v: vec4<f32>) -> f32 {
1815
+ var bitShift: vec4<f32> = vec4<f32>( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );
1816
+ return dot(v, 1.0 / bitShift);
1817
+ }
1818
+
1819
+ @fragment
1820
+ fn main(
1821
+ @location(0) worldDir: vec3<f32>,
1822
+ @location(1) near: f32,
1823
+ @location(2) far: f32,
1824
+ @location(3) @interpolate(flat) vId: vec4<f32>,
1825
+ @location(4) @interpolate(flat) id: u32
1826
+ ) -> @location(0) vec4<f32> {
1827
+ var dist = length(worldDir);
1828
+
1829
+ // 偏移
1830
+ var diff = 0.1;
1831
+
1832
+ var dir = normalize(worldDir);
1833
+ dir.x = -dir.x;
1834
+
1835
+ var depthColor = textureSample(cubeTexture, cubeTextureSampler, dir);
1836
+ var maxDepth = unpackDepth( depthColor );
1837
+ var maxDist = maxDepth * (far - near) + near;
1838
+
1839
+ if(depthColor.a != 1.0 && maxDist - dist > diff) {
1840
+ atomicOr(&results[id], 1);
1841
+ return vId;
1842
+ }
1843
+ return vec4<f32>(0);
1844
+ }
1845
+ `
1846
+ ),
1847
+ side: opt.side ?? "none",
1848
+ topology: opt.topology ?? "triangle-list",
1849
+ uniforms: {
1850
+ cubeTexture: { value: opt.cubeTexture },
1851
+ results: { value: opt.results ?? new Uint32Array([0, 0, 0]), write: true, atomic: true }
1852
+ }
1853
+ });
1854
+ }
1855
+ static packUint(v) {
1856
+ const vec4 = new Vector4();
1857
+ vec4.set(
1858
+ v & 255,
1859
+ v >> 8 & 255,
1860
+ v >> 16 & 255,
1861
+ v >> 24 & 255
1862
+ );
1863
+ return vec4.divideScalar(255);
1864
+ }
1865
+ static unpackUint(r, g, b, a) {
1866
+ return (r | g << 8 | b << 16 | a << 24) >>> 0;
1867
+ }
1868
+ static unpackUintByUint8(buff, i) {
1869
+ const r = buff[i * 4], g = buff[i * 4 + 1], b = buff[i * 4 + 2], a = buff[i * 4 + 3];
1870
+ if ((r | g | b) === 0 && a === 255) return null;
1871
+ return this.unpackUint(r, g, b, a);
1872
+ }
1873
+ }
1874
+ const directions = [
1875
+ new THREE.Vector3(-1, 0, 0),
1876
+ // -X
1877
+ new THREE.Vector3(1, 0, 0),
1878
+ // +X
1879
+ new THREE.Vector3(0, 1, 0),
1880
+ // +Y
1881
+ new THREE.Vector3(0, -1, 0),
1882
+ // -Y
1883
+ new THREE.Vector3(0, 0, 1),
1884
+ // +Z
1885
+ new THREE.Vector3(0, 0, -1)
1886
+ // -Z
1887
+ ];
1888
+ const ups = [
1889
+ new THREE.Vector3(0, -1, 0),
1890
+ // -X
1891
+ new THREE.Vector3(0, -1, 0),
1892
+ // +X
1893
+ new THREE.Vector3(0, 0, 1),
1894
+ // +Y
1895
+ new THREE.Vector3(0, 0, -1),
1896
+ // -Y
1897
+ new THREE.Vector3(0, -1, 0),
1898
+ // +Z
1899
+ new THREE.Vector3(0, -1, 0)
1900
+ // -Z
1901
+ ];
1902
+ class CubeCamera {
1903
+ cameras = [];
1904
+ depthClearValue = 1;
1905
+ constructor(near = 0.1, far = 1e3, depthClearValue = 1) {
1906
+ this.depthClearValue = depthClearValue;
1907
+ for (let i = 0; i < 6; i++) {
1908
+ const cam = new PerspectiveCamera(-90, 1, near, far);
1909
+ cam.up.copy(ups[i]);
1910
+ cam.lookAt(directions[i]);
1911
+ this.cameras.push(cam);
1912
+ }
1913
+ }
1914
+ setPosition(x, y, z) {
1915
+ for (let i = 0; i < 6; i++) {
1916
+ const cam = this.cameras[i];
1917
+ cam.position.set(x, y, z);
1918
+ cam.lookAt(
1919
+ x + directions[i].x,
1920
+ y + directions[i].y,
1921
+ z + directions[i].z
1922
+ );
1923
+ cam.updateMatrixWorld();
1924
+ }
1925
+ }
1926
+ updateDirections() {
1927
+ for (let i = 0; i < 6; i++) {
1928
+ const cam = this.cameras[i];
1929
+ cam.up.copy(ups[i]);
1930
+ cam.lookAt(
1931
+ cam.position.x + directions[i].x,
1932
+ cam.position.y + directions[i].y,
1933
+ cam.position.z + directions[i].z
1934
+ );
1935
+ cam.updateMatrixWorld();
1936
+ }
1937
+ }
1938
+ render(meshList, renderer, cubeTexture) {
1939
+ for (let i = 0; i < 6; i++) {
1940
+ const camera = this.cameras[i];
1941
+ const view = cubeTexture.getFaceView(i);
1942
+ renderer.renderTarget = view;
1943
+ renderer.depthTarget = cubeTexture.depthView;
1944
+ renderer.render(meshList, camera, this.depthClearValue);
1945
+ }
1946
+ renderer.renderTarget = null;
1947
+ renderer.depthTarget = null;
1948
+ }
1949
+ }
1950
+ class Renderer {
1951
+ canvas;
1952
+ context;
1953
+ depthTexture;
1954
+ depthTextureView;
1955
+ format;
1956
+ renderTarget = null;
1957
+ depthTarget = null;
1958
+ constructor(width = 800, height = 800, format = navigator.gpu.getPreferredCanvasFormat()) {
1959
+ const device2 = getDevice();
1960
+ this.format = format;
1961
+ if (typeof window !== "undefined") {
1962
+ this.canvas = document.createElement("canvas");
1963
+ this.canvas.width = width;
1964
+ this.canvas.height = height;
1965
+ this.context = this.canvas.getContext("webgpu");
1966
+ this.context.configure({
1967
+ device: device2,
1968
+ format,
1969
+ alphaMode: "opaque"
1970
+ });
1971
+ }
1972
+ this.depthTexture = device2.createTexture({
1973
+ size: [width, height],
1974
+ format: "depth24plus",
1975
+ label: "cubeDepthDepthTexture",
1976
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
1977
+ });
1978
+ this.depthTextureView = this.depthTexture.createView();
1979
+ }
1980
+ render(meshList, camera, depthClearValue = 1) {
1981
+ const device2 = getDevice();
1982
+ camera.update();
1983
+ const encoder = device2.createCommandEncoder();
1984
+ const view = this.renderTarget ?? this.context?.getCurrentTexture().createView();
1985
+ if (!view) return;
1986
+ const pass = encoder.beginRenderPass({
1987
+ colorAttachments: [{
1988
+ view,
1989
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
1990
+ loadOp: "clear",
1991
+ storeOp: "store"
1992
+ }],
1993
+ depthStencilAttachment: {
1994
+ view: this.depthTarget ?? this.depthTextureView,
1995
+ depthClearValue,
1996
+ depthLoadOp: "clear",
1997
+ depthStoreOp: "store"
1998
+ }
1999
+ });
2000
+ pass.setBindGroup(0, camera.bindGroup);
2001
+ meshList.forEach((mesh) => {
2002
+ mesh.update(this.format);
2003
+ pass.setPipeline(mesh.pipeline);
2004
+ pass.setBindGroup(1, mesh.bindGroup);
2005
+ pass.setBindGroup(2, mesh.material.bindGroup);
2006
+ let index = 0;
2007
+ mesh.geometry.gpuAttributes.forEach((v) => {
2008
+ pass.setVertexBuffer(index, v.buffer);
2009
+ index++;
2010
+ });
2011
+ if (mesh.geometry.indexBuffer) {
2012
+ pass.setIndexBuffer(mesh.geometry.indexBuffer, mesh.geometry.indexFormat);
2013
+ pass.drawIndexed(mesh.geometry.index.count);
2014
+ } else {
2015
+ pass.draw(mesh.geometry.count);
2016
+ }
2017
+ });
2018
+ pass.end();
2019
+ device2.queue.submit([encoder.finish()]);
2020
+ }
2021
+ }
2022
+ class PCD {
2023
+ header;
2024
+ positions;
2025
+ intensity;
2026
+ rgb;
2027
+ static async load(url) {
2028
+ const buffer = await readFile(url);
2029
+ const pcd = new PCD();
2030
+ pcd.parse(buffer);
2031
+ return pcd;
2032
+ }
2033
+ parse(buffer) {
2034
+ this.header = this.parseHeader(buffer);
2035
+ if (this.header.data === "binary") {
2036
+ this.parseBinary(buffer);
2037
+ } else if (this.header.data === "ascii") {
2038
+ this.parseASCII(buffer);
2039
+ } else {
2040
+ throw new Error("binary_compressed 暂未支持");
2041
+ }
2042
+ }
2043
+ parseHeader(buffer) {
2044
+ const text = new TextDecoder().decode(buffer.slice(0, 5e3));
2045
+ const lines = text.split("\n");
2046
+ const header = {
2047
+ fields: [],
2048
+ size: [],
2049
+ type: [],
2050
+ count: [],
2051
+ width: 0,
2052
+ height: 1,
2053
+ points: 0,
2054
+ data: "ascii",
2055
+ headerLen: 0
2056
+ };
2057
+ let offset = 0;
2058
+ for (const line of lines) {
2059
+ offset += line.length + 1;
2060
+ const parts = line.trim().split(/\s+/);
2061
+ const key = parts[0];
2062
+ if (key === "FIELDS") header.fields = parts.slice(1);
2063
+ else if (key === "SIZE") header.size = parts.slice(1).map(Number);
2064
+ else if (key === "TYPE") header.type = parts.slice(1);
2065
+ else if (key === "COUNT") header.count = parts.slice(1).map(Number);
2066
+ else if (key === "WIDTH") header.width = Number(parts[1]);
2067
+ else if (key === "HEIGHT") header.height = Number(parts[1]);
2068
+ else if (key === "POINTS") header.points = Number(parts[1]);
2069
+ else if (key === "DATA") {
2070
+ header.data = parts[1];
2071
+ header.headerLen = offset;
2072
+ break;
2073
+ }
2074
+ }
2075
+ if (!header.points) {
2076
+ header.points = header.width * header.height;
2077
+ }
2078
+ return header;
2079
+ }
2080
+ parseBinary(buffer) {
2081
+ const { header } = this;
2082
+ const body = buffer.slice(header.headerLen);
2083
+ const dv = new DataView(body);
2084
+ const stride = header.size.reduce((a, b) => a + b, 0);
2085
+ const points = header.points;
2086
+ const fieldOffset = {};
2087
+ let offset = 0;
2088
+ for (let i = 0; i < header.fields.length; i++) {
2089
+ fieldOffset[header.fields[i]] = offset;
2090
+ offset += header.size[i];
2091
+ }
2092
+ this.positions = new Float32Array(points * 3);
2093
+ if (fieldOffset.intensity !== void 0) {
2094
+ this.intensity = new Float32Array(points);
2095
+ }
2096
+ if (fieldOffset.rgb !== void 0) {
2097
+ this.rgb = new Uint8Array(points * 3);
2098
+ }
2099
+ let pIndex = 0;
2100
+ let iIndex = 0;
2101
+ let cIndex = 0;
2102
+ for (let i = 0; i < points; i++) {
2103
+ const base = i * stride;
2104
+ const x = dv.getFloat32(base + fieldOffset.x, true);
2105
+ const y = dv.getFloat32(base + fieldOffset.y, true);
2106
+ const z = dv.getFloat32(base + fieldOffset.z, true);
2107
+ this.positions[pIndex++] = x;
2108
+ this.positions[pIndex++] = y;
2109
+ this.positions[pIndex++] = z;
2110
+ if (this.intensity) {
2111
+ this.intensity[iIndex++] = dv.getFloat32(
2112
+ base + fieldOffset.intensity,
2113
+ true
2114
+ );
2115
+ }
2116
+ if (this.rgb) {
2117
+ const rgb = dv.getUint32(base + fieldOffset.rgb, true);
2118
+ this.rgb[cIndex++] = rgb >> 16 & 255;
2119
+ this.rgb[cIndex++] = rgb >> 8 & 255;
2120
+ this.rgb[cIndex++] = rgb & 255;
2121
+ }
2122
+ }
2123
+ }
2124
+ parseASCII(buffer) {
2125
+ const { header } = this;
2126
+ const text = new TextDecoder().decode(buffer.slice(header.headerLen));
2127
+ const lines = text.trim().split("\n");
2128
+ const points = lines.length;
2129
+ this.positions = new Float32Array(points * 3);
2130
+ let pIndex = 0;
2131
+ for (let i = 0; i < points; i++) {
2132
+ const parts = lines[i].trim().split(/\s+/);
2133
+ this.positions[pIndex++] = parseFloat(parts[0]);
2134
+ this.positions[pIndex++] = parseFloat(parts[1]);
2135
+ this.positions[pIndex++] = parseFloat(parts[2]);
2136
+ }
2137
+ }
2138
+ }
2139
+ const IS_BROWSER = typeof window !== "undefined";
2140
+ async function readFile(path) {
2141
+ if (IS_BROWSER) {
2142
+ const data = await fetch(path).then((res) => res.arrayBuffer());
2143
+ return data;
2144
+ }
2145
+ const fs = await include("fs");
2146
+ const buf = fs.readFileSync(path);
2147
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2148
+ }
2149
+ async function loadJson(url) {
2150
+ const arrayBuffer = await readFile(url);
2151
+ const decoder = new TextDecoder("utf-8");
2152
+ const str = decoder.decode(arrayBuffer);
2153
+ return JSON.parse(str);
2154
+ }
2155
+ async function loadPCDCustom(path) {
2156
+ const data = await PCD.load(path);
2157
+ return data.positions;
2158
+ }
2159
+ async function loadTrajectory(base) {
2160
+ return await loadJson(base + "/data/trajectory.json");
2161
+ }
2162
+ async function loadLines(base) {
2163
+ return await loadJson(base + "/data/originSplitLines.json");
2164
+ }
2165
+ function linesToBuffer(lines) {
2166
+ const map = /* @__PURE__ */ new Map(), rectArray = [], indexArray = [];
2167
+ let count = 0;
2168
+ const rectIndex = [0, 1, 2, 2, 3, 0];
2169
+ for (let i = 0; i < lines.length; i++) {
2170
+ let num = 0;
2171
+ lines[i].checkResults?.forEach((checkResult, j) => {
2172
+ checkResult?.originalVertices.forEach((rect, k) => {
2173
+ const index = count++;
2174
+ map.set(index, [i, j, k, num++]);
2175
+ rectIndex.forEach((j2) => {
2176
+ const item = rect[j2];
2177
+ rectArray.push(item.x, item.y, item.z);
2178
+ indexArray.push(index);
2179
+ });
2180
+ });
2181
+ });
2182
+ }
2183
+ return {
2184
+ map,
2185
+ rectArray,
2186
+ indexArray
2187
+ };
2188
+ }
2189
+ async function trajectoryHandle(baseUrl, trajectoryData) {
2190
+ const keys = Object.keys(trajectoryData);
2191
+ const trajectoryList = new Array(keys.length);
2192
+ const pointClouds = new Array(keys.length);
2193
+ const pointCloudArray = [];
2194
+ await Promise.all(
2195
+ keys.map(async (key, i) => {
2196
+ const item = trajectoryData[key];
2197
+ const float32Array = await loadPCDCustom(baseUrl + "/pcd/" + key + ".pcd");
2198
+ trajectoryList[i] = { x: item.x, y: item.y, z: item.z };
2199
+ pointClouds[i] = float32Array;
2200
+ pointCloudArray.push(...float32Array);
2201
+ })
2202
+ );
2203
+ return {
2204
+ pointClouds,
2205
+ trajectoryList,
2206
+ pointCloudArray
2207
+ };
2208
+ }
2209
+ async function loadData(base) {
2210
+ const t = performance.now();
2211
+ const trajectory = await loadTrajectory(base), { pointClouds, pointCloudArray, trajectoryList } = await trajectoryHandle(base, trajectory), linesJson = await loadLines(base), { map, rectArray, indexArray } = linesToBuffer(linesJson);
2212
+ console.log("加载耗时", performance.now() - t);
2213
+ return {
2214
+ pointClouds,
2215
+ pointCloudArray,
2216
+ trajectoryList,
2217
+ map,
2218
+ rectArray,
2219
+ indexArray,
2220
+ linesJson
2221
+ };
2222
+ }
2223
+ async function obstacleDetection_(opt) {
2224
+ const {
2225
+ base,
2226
+ size = 512,
2227
+ near = 0.01,
2228
+ far = 100,
2229
+ canvasWidth = 1400,
2230
+ canvasHeight = 800,
2231
+ fov = 75
2232
+ } = opt;
2233
+ let t = performance.now();
2234
+ const data = await loadData(base), { indexArray, rectArray, trajectoryList, pointClouds, map } = data, renderer = new Renderer(canvasWidth, canvasHeight, "rgba8unorm"), camera = new PerspectiveCamera(fov, canvasWidth / canvasHeight, 1e-3, 100);
2235
+ console.log("数据加载耗时", performance.now() - t);
2236
+ t = performance.now();
2237
+ camera.up = new THREE.Vector3(0, 0, 1);
2238
+ camera.position.set(0, 0, 5);
2239
+ camera.lookAt(new THREE.Vector3(0, 0, 0));
2240
+ const depthCubeTexture = new CubeTexture(size, size, { needDepthTexture: true, format: "rgba8unorm", sampler: { minFilter: "nearest", magFilter: "nearest", mipmapFilter: "nearest" } }), resultCubeTexture = new CubeTexture(size, size, { needDepthTexture: true, format: "rgba8unorm", sampler: { minFilter: "nearest", magFilter: "nearest", mipmapFilter: "nearest" } }), cubeCamera = new CubeCamera(near, far), indexCount = indexArray.length / 6, quadObject3D = new Object3D(
2241
+ new BufferGeometry().setAttribute("position", new THREE.BufferAttribute(new Float32Array(rectArray), 3)).setAttribute("id", new THREE.Uint32BufferAttribute(new Uint32Array(indexArray), 1)),
2242
+ new OcclusionMaterial({
2243
+ cubeTexture: depthCubeTexture,
2244
+ results: new Uint32Array(indexCount)
2245
+ // indexCount 个 uint8
2246
+ })
2247
+ ), pointsObject3D = new Object3D(
2248
+ new BufferGeometry().setAttribute("position", new THREE.BufferAttribute(new Float32Array(), 3)),
2249
+ new DepthMaterial({ topology: "point-list" })
2250
+ );
2251
+ for (let i = 0; i < trajectoryList.length; i++) {
2252
+ const trajectory = trajectoryList[i];
2253
+ pointsObject3D.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(pointClouds[i]), 3));
2254
+ cubeCamera.setPosition(trajectory.x, trajectory.y, trajectory.z);
2255
+ cubeCamera.depthClearValue = 0;
2256
+ cubeCamera.render([pointsObject3D], renderer, depthCubeTexture);
2257
+ cubeCamera.depthClearValue = 1;
2258
+ cubeCamera.render([quadObject3D], renderer, resultCubeTexture);
2259
+ }
2260
+ const buffer = quadObject3D.material.getStorageBuffer("results");
2261
+ const resultTextureBuff = await readGPUBuffer(buffer, indexCount * 4, Uint32Array);
2262
+ const result = [];
2263
+ resultTextureBuff.forEach((v, index) => {
2264
+ if (v === 0) {
2265
+ const [i, j, k, l] = map.get(index);
2266
+ result.push(i, j, k, l);
2267
+ }
2268
+ });
2269
+ console.log("渲染耗时", performance.now() - t);
2270
+ console.log("轨迹点数量", trajectoryList.length, "长方形数量", indexArray.length / 6);
2271
+ return {
2272
+ renderer,
2273
+ camera,
2274
+ data,
2275
+ depthCubeTexture,
2276
+ resultCubeTexture,
2277
+ cubeCamera,
2278
+ quadObject3D,
2279
+ pointsObject3D,
2280
+ indexCount,
2281
+ resultTextureBuff,
2282
+ result
2283
+ };
2284
+ }
2285
+ const obstacleDetection = Object.assign(obstacleDetection_, {
2286
+ async getResult(base) {
2287
+ await initWebgpu();
2288
+ const { result } = await obstacleDetection_({ base });
2289
+ await destroyWebgpu();
2290
+ return result;
2291
+ }
2292
+ });
2293
+ export {
2294
+ AtomicUint32Array,
2295
+ BaseMaterial,
2296
+ BufferGeometry,
2297
+ CubeCamera,
2298
+ CubeTexture,
2299
+ DepthMaterial,
2300
+ GpuComputed,
2301
+ Object3D,
2302
+ OcclusionMaterial,
2303
+ PerspectiveCamera,
2304
+ Renderer,
2305
+ ShaderMaterial,
2306
+ Texture,
2307
+ createBuffer,
2308
+ createComputePipeline,
2309
+ createRenderPipeline,
2310
+ createTexture,
2311
+ destroyWebgpu,
2312
+ getAdapter,
2313
+ getDevice,
2314
+ getSampler,
2315
+ initWebgpu,
2316
+ loadData,
2317
+ obstacleDetection,
2318
+ readCubeTexture,
2319
+ readGPUBuffer,
2320
+ readTextureBuffer,
2321
+ shaderLocationMap,
2322
+ transparentOption
2323
+ };