simulationjsv2 0.10.6 → 0.11.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.
- package/TODO.md +4 -20
- package/dist/backend.d.ts +38 -0
- package/dist/backend.js +127 -0
- package/dist/backends/backend.d.ts +22 -0
- package/dist/backends/backend.js +21 -0
- package/dist/backends/webgl.d.ts +19 -0
- package/dist/backends/webgl.js +112 -0
- package/dist/backends/webgpu.d.ts +25 -0
- package/dist/backends/webgpu.js +134 -0
- package/dist/buffers/buffer.d.ts +15 -0
- package/dist/buffers/buffer.js +42 -0
- package/dist/buffers/webgl.d.ts +13 -0
- package/dist/buffers/webgl.js +46 -0
- package/dist/buffers/webgpu.d.ts +12 -0
- package/dist/buffers/webgpu.js +40 -0
- package/dist/buffers.d.ts +20 -6
- package/dist/buffers.js +54 -20
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/geometry.d.ts +3 -2
- package/dist/geometry.js +8 -4
- package/dist/globals.d.ts +6 -6
- package/dist/globals.js +7 -12
- package/dist/graphics.d.ts +18 -18
- package/dist/graphics.js +57 -59
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/internalUtils.d.ts +2 -2
- package/dist/internalUtils.js +3 -1
- package/dist/shaders/shader.d.ts +18 -0
- package/dist/shaders/shader.js +63 -0
- package/dist/shaders/utils.d.ts +33 -0
- package/dist/shaders/utils.js +25 -0
- package/dist/shaders/webgl.d.ts +74 -0
- package/dist/shaders/webgl.js +242 -0
- package/dist/shaders/webgpu.d.ts +40 -0
- package/dist/{shaders.js → shaders/webgpu.js} +73 -114
- package/dist/simulation.d.ts +11 -5
- package/dist/simulation.js +49 -86
- package/dist/types.d.ts +54 -35
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +6 -9
- package/package.json +26 -26
- package/dist/pipelineUtil.d.ts +0 -5
- package/dist/pipelineUtil.js +0 -22
- package/dist/shaders.d.ts +0 -36
package/TODO.md
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
+
- [ ] per object depth test settings on webgl backend
|
|
4
|
+
|
|
5
|
+
## later features
|
|
6
|
+
|
|
3
7
|
- [ ] hex to color
|
|
4
8
|
- [ ] Materials for planes
|
|
5
9
|
- [ ] Transform vertex colors on material
|
|
6
|
-
- [x] Morph objects into other objects
|
|
7
|
-
- [x] `transform` function taking another object and transitioning from current geometry's vertices (positions only)
|
|
8
|
-
- [x] Change render vertices to use index buffer
|
|
9
|
-
- [x] Add cull modes
|
|
10
|
-
- [x] Fix shaders
|
|
11
|
-
- [x] Clean up polygons
|
|
12
|
-
- [x] Fix transparency
|
|
13
|
-
- [x] Change absolute pos function to pos function, and pos function to relative pos
|
|
14
|
-
- [x] Update `updateModelMatrix2d`
|
|
15
|
-
- [x] Trace line element (wireframe strip for tracing paths)
|
|
16
|
-
- [x] Test new transform things on 3d stuff
|
|
17
|
-
- [x] Fix rotating nested children elements
|
|
18
|
-
- [x] Fix instancing
|
|
19
|
-
- [x] Remove SceneCollection and replace by elements with children
|
|
20
|
-
- [x] Change position/rotation to be matrix transform on gpu
|
|
21
|
-
- [x] Add update square center offset position in-place + not
|
|
22
|
-
- [x] Make getBuffer return cached Float32Array
|
|
23
|
-
- [x] Make input buffer position vec3 not vec4
|
|
24
|
-
- [x] Use line strip vertices for polygon buffers
|
|
25
|
-
- [x] Scene collection wireframe
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types" />
|
|
2
|
+
import { SimulationElement3d } from './graphics.js';
|
|
3
|
+
import { Shader } from './shaders/webgpu.js';
|
|
4
|
+
import { BackendType, Vector2 } from './types.js';
|
|
5
|
+
import { Color } from './utils.js';
|
|
6
|
+
export declare abstract class SimJsBackend {
|
|
7
|
+
readonly type: BackendType;
|
|
8
|
+
constructor(type: BackendType);
|
|
9
|
+
init(_canvas: HTMLCanvasElement): Promise<void>;
|
|
10
|
+
renderStart(_canvas: HTMLCanvasElement, _clearColor: Color): void;
|
|
11
|
+
updateTextures(_screenSize: Vector2): void;
|
|
12
|
+
preRender(_scene: SimulationElement3d[]): void;
|
|
13
|
+
finishRender(): void;
|
|
14
|
+
draw(_obj: SimulationElement3d, _vertexOffset: number, _vertices: Float32Array, _vertexByteOffset: number, _vertexByteLength: number, _indexOffset: number, _indices: Uint32Array, _indexByteOffset: number, _indexByteLength: number): void;
|
|
15
|
+
initShaders(_shaders: Shader[]): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class WebGPUBackend extends SimJsBackend {
|
|
18
|
+
private device;
|
|
19
|
+
private ctx;
|
|
20
|
+
private renderPassDescriptor;
|
|
21
|
+
private multisampleTexture;
|
|
22
|
+
private depthTexture;
|
|
23
|
+
private passEncoder;
|
|
24
|
+
private commandEncoder;
|
|
25
|
+
private buffers;
|
|
26
|
+
constructor();
|
|
27
|
+
getDevice(): GPUDevice | null;
|
|
28
|
+
init(canvas: HTMLCanvasElement): Promise<void>;
|
|
29
|
+
renderStart(canvas: HTMLCanvasElement, clearColor: Color): void;
|
|
30
|
+
updateTextures(screenSize: Vector2): void;
|
|
31
|
+
preRender(scene: SimulationElement3d[]): void;
|
|
32
|
+
finishRender(): void;
|
|
33
|
+
draw(obj: SimulationElement3d, vertexOffset: number, vertices: Float32Array, vertexByteOffset: number, vertexByteLength: number, indexOffset: number, indices: Uint32Array, indexByteOffset: number, indexByteLength: number): void;
|
|
34
|
+
initShaders(shaders: Shader[]): void;
|
|
35
|
+
}
|
|
36
|
+
export declare class WebGLBackend extends SimJsBackend {
|
|
37
|
+
constructor();
|
|
38
|
+
}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { WebGPUMemoBuffer } from './buffers.js';
|
|
2
|
+
import { logger } from './globals.js';
|
|
3
|
+
import { buildDepthTexture, buildMultisampleTexture, getVertexAndIndexSize } from './internalUtils.js';
|
|
4
|
+
export class SimJsBackend {
|
|
5
|
+
type;
|
|
6
|
+
constructor(type) {
|
|
7
|
+
this.type = type;
|
|
8
|
+
}
|
|
9
|
+
async init(_canvas) { }
|
|
10
|
+
renderStart(_canvas, _clearColor) { }
|
|
11
|
+
updateTextures(_screenSize) { }
|
|
12
|
+
preRender(_scene) { }
|
|
13
|
+
finishRender() { }
|
|
14
|
+
draw(_obj, _vertexOffset, _vertices, _vertexByteOffset, _vertexByteLength, _indexOffset, _indices, _indexByteOffset, _indexByteLength) { }
|
|
15
|
+
initShaders(_shaders) { }
|
|
16
|
+
}
|
|
17
|
+
export class WebGPUBackend extends SimJsBackend {
|
|
18
|
+
device = null;
|
|
19
|
+
ctx = null;
|
|
20
|
+
renderPassDescriptor = null;
|
|
21
|
+
multisampleTexture = null;
|
|
22
|
+
depthTexture = null;
|
|
23
|
+
passEncoder = null;
|
|
24
|
+
commandEncoder = null;
|
|
25
|
+
buffers = null;
|
|
26
|
+
constructor() {
|
|
27
|
+
super('webgpu');
|
|
28
|
+
}
|
|
29
|
+
getDevice() {
|
|
30
|
+
return this.device;
|
|
31
|
+
}
|
|
32
|
+
async init(canvas) {
|
|
33
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
34
|
+
if (!adapter)
|
|
35
|
+
throw logger.error('Adapter is null');
|
|
36
|
+
this.ctx = canvas.getContext('webgpu');
|
|
37
|
+
if (!this.ctx)
|
|
38
|
+
throw logger.error('Context is null');
|
|
39
|
+
this.device = await adapter.requestDevice();
|
|
40
|
+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
41
|
+
this.ctx.configure({
|
|
42
|
+
device: this.device,
|
|
43
|
+
format: presentationFormat,
|
|
44
|
+
alphaMode: 'opaque'
|
|
45
|
+
});
|
|
46
|
+
this.buffers = {
|
|
47
|
+
gpuVertexBuffer: new WebGPUMemoBuffer(this.device, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 0),
|
|
48
|
+
gpuIndexBuffer: new WebGPUMemoBuffer(this.device, GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, 0)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
renderStart(canvas, clearColor) {
|
|
52
|
+
if (!this.device || !this.ctx)
|
|
53
|
+
throw logger.error('Invalid render start state');
|
|
54
|
+
const colorAttachment = {
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
view: undefined, // Assigned later
|
|
58
|
+
clearValue: clearColor.toObject(),
|
|
59
|
+
loadOp: 'clear',
|
|
60
|
+
storeOp: 'store'
|
|
61
|
+
};
|
|
62
|
+
this.multisampleTexture = buildMultisampleTexture(this.device, this.ctx, canvas.width, canvas.height);
|
|
63
|
+
this.depthTexture = buildDepthTexture(this.device, canvas.width, canvas.height);
|
|
64
|
+
this.renderPassDescriptor = {
|
|
65
|
+
colorAttachments: [colorAttachment],
|
|
66
|
+
depthStencilAttachment: {
|
|
67
|
+
view: this.depthTexture.createView(),
|
|
68
|
+
depthClearValue: 1.0,
|
|
69
|
+
depthLoadOp: 'clear',
|
|
70
|
+
depthStoreOp: 'store'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
updateTextures(screenSize) {
|
|
75
|
+
if (!this.device || !this.ctx || !this.renderPassDescriptor)
|
|
76
|
+
return;
|
|
77
|
+
this.multisampleTexture = buildMultisampleTexture(this.device, this.ctx, screenSize[0], screenSize[1]);
|
|
78
|
+
this.depthTexture = buildDepthTexture(this.device, screenSize[0], screenSize[1]);
|
|
79
|
+
this.renderPassDescriptor.depthStencilAttachment.view = this.depthTexture.createView();
|
|
80
|
+
}
|
|
81
|
+
preRender(scene) {
|
|
82
|
+
if (!this.renderPassDescriptor || !this.ctx || !this.multisampleTexture || !this.device) {
|
|
83
|
+
throw logger.error('Invalid prerender state');
|
|
84
|
+
}
|
|
85
|
+
const attachment = this.renderPassDescriptor.colorAttachments[0];
|
|
86
|
+
attachment.view = this.multisampleTexture.createView();
|
|
87
|
+
attachment.resolveTarget = this.ctx.getCurrentTexture().createView();
|
|
88
|
+
this.commandEncoder = this.device.createCommandEncoder();
|
|
89
|
+
this.passEncoder = this.commandEncoder.beginRenderPass(this.renderPassDescriptor);
|
|
90
|
+
const [totalVerticesSize, totalIndexSize] = getVertexAndIndexSize(scene);
|
|
91
|
+
this.buffers.gpuVertexBuffer.ensureCapacity(totalVerticesSize * 4);
|
|
92
|
+
this.buffers.gpuIndexBuffer.ensureCapacity(totalIndexSize * 4);
|
|
93
|
+
}
|
|
94
|
+
finishRender() {
|
|
95
|
+
this.passEncoder.end();
|
|
96
|
+
this.device.queue.submit([this.commandEncoder.finish()]);
|
|
97
|
+
}
|
|
98
|
+
draw(obj, vertexOffset, vertices, vertexByteOffset, vertexByteLength, indexOffset, indices, indexByteOffset, indexByteLength) {
|
|
99
|
+
this.device.queue.writeBuffer(this.buffers.gpuVertexBuffer.getBuffer(), vertexOffset, vertices.buffer, vertexByteOffset, vertexByteLength);
|
|
100
|
+
this.device.queue.writeBuffer(this.buffers.gpuIndexBuffer.getBuffer(), indexOffset, indices.buffer, indexByteOffset, indexByteLength);
|
|
101
|
+
this.passEncoder.setVertexBuffer(0, this.buffers.gpuVertexBuffer.getBuffer(), vertexOffset, vertexByteLength);
|
|
102
|
+
this.passEncoder.setIndexBuffer(this.buffers.gpuIndexBuffer.getBuffer(), 'uint32', indexOffset, indexByteLength);
|
|
103
|
+
this.passEncoder.setPipeline(obj.getPipeline());
|
|
104
|
+
const shader = obj.getShader();
|
|
105
|
+
shader.writeBuffers(this.device, obj);
|
|
106
|
+
const bindGroups = obj.getShader().getBindGroups(this.device, obj);
|
|
107
|
+
for (let i = 0; i < bindGroups.length; i++) {
|
|
108
|
+
this.passEncoder.setBindGroup(i, bindGroups[i]);
|
|
109
|
+
}
|
|
110
|
+
const instances = obj.isInstance
|
|
111
|
+
? obj.getInstanceCount()
|
|
112
|
+
: 1;
|
|
113
|
+
this.passEncoder.drawIndexed(indices.length, instances);
|
|
114
|
+
}
|
|
115
|
+
initShaders(shaders) {
|
|
116
|
+
if (!this.device)
|
|
117
|
+
throw logger.error('Device is null');
|
|
118
|
+
for (let i = 0; i < shaders.length; i++) {
|
|
119
|
+
shaders[i].init(this.device);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export class WebGLBackend extends SimJsBackend {
|
|
124
|
+
constructor() {
|
|
125
|
+
super('webgl');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SimulationElement3d } from '../graphics.js';
|
|
2
|
+
import { SimJSShader } from '../shaders/shader.js';
|
|
3
|
+
import { BackendType, GPUBuffers, SpecificBackendType, Vector2 } from '../types.js';
|
|
4
|
+
import { Color } from '../utils.js';
|
|
5
|
+
export declare abstract class SimJsBackend {
|
|
6
|
+
private type;
|
|
7
|
+
protected abstract buffers: GPUBuffers<unknown> | null;
|
|
8
|
+
protected clearColor: Color;
|
|
9
|
+
constructor(type: BackendType);
|
|
10
|
+
getBackendType(): BackendType;
|
|
11
|
+
abstract init(canvas: HTMLCanvasElement): Promise<void>;
|
|
12
|
+
abstract renderStart(canvas: HTMLCanvasElement): void;
|
|
13
|
+
abstract updateTextures(screenSize: Vector2): void;
|
|
14
|
+
abstract preRender(scene: SimulationElement3d[]): void;
|
|
15
|
+
abstract finishRender(): void;
|
|
16
|
+
abstract draw(obj: SimulationElement3d, vertexCallOffset: number, vertexCallBuffer: Float32Array, indexOffset: number, indices: Uint32Array): void;
|
|
17
|
+
abstract initShaders(shaders: SimJSShader[]): void;
|
|
18
|
+
abstract destroy(): void;
|
|
19
|
+
abstract onClearColorChange(): void;
|
|
20
|
+
setClearColor(color: Color): void;
|
|
21
|
+
as<T extends BackendType>(type: T): SpecificBackendType<T>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { logger } from '../globals.js';
|
|
2
|
+
import { color } from '../utils.js';
|
|
3
|
+
export class SimJsBackend {
|
|
4
|
+
type;
|
|
5
|
+
clearColor = color();
|
|
6
|
+
constructor(type) {
|
|
7
|
+
this.type = type;
|
|
8
|
+
}
|
|
9
|
+
getBackendType() {
|
|
10
|
+
return this.type;
|
|
11
|
+
}
|
|
12
|
+
setClearColor(color) {
|
|
13
|
+
this.clearColor = color;
|
|
14
|
+
this.onClearColorChange();
|
|
15
|
+
}
|
|
16
|
+
as(type) {
|
|
17
|
+
if (this.type !== type)
|
|
18
|
+
throw logger.error('Incompatible backend cast');
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SimulationElement3d } from '../graphics.js';
|
|
2
|
+
import { SimJsBackend } from './backend.js';
|
|
3
|
+
import { SimJSShader } from '../shaders/shader.js';
|
|
4
|
+
import { GPUBuffers, Vector2 } from '../types.js';
|
|
5
|
+
export declare class WebGLBackend extends SimJsBackend {
|
|
6
|
+
private gl;
|
|
7
|
+
protected buffers: GPUBuffers<'webgl'> | null;
|
|
8
|
+
constructor();
|
|
9
|
+
init(canvas: HTMLCanvasElement): Promise<void>;
|
|
10
|
+
getContextOrError(): WebGL2RenderingContext;
|
|
11
|
+
renderStart(_canvas: HTMLCanvasElement): void;
|
|
12
|
+
updateTextures(screenSize: Vector2): void;
|
|
13
|
+
preRender(): void;
|
|
14
|
+
finishRender(): void;
|
|
15
|
+
initShaders(shaders: SimJSShader[]): void;
|
|
16
|
+
draw(obj: SimulationElement3d, vertexCallOffset: number, vertexCallBuffer: Float32Array, indexOffset: number, indices: Uint32Array): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
onClearColorChange(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { logger } from '../globals.js';
|
|
2
|
+
import { SimJsBackend } from './backend.js';
|
|
3
|
+
import { WebGLMemoBuffer } from '../buffers/webgl.js';
|
|
4
|
+
export class WebGLBackend extends SimJsBackend {
|
|
5
|
+
gl = null;
|
|
6
|
+
buffers = null;
|
|
7
|
+
constructor() {
|
|
8
|
+
super('webgl');
|
|
9
|
+
console.log('new webgl backend');
|
|
10
|
+
}
|
|
11
|
+
async init(canvas) {
|
|
12
|
+
this.gl = canvas.getContext('webgl2');
|
|
13
|
+
if (this.gl === null) {
|
|
14
|
+
throw logger.error('WebGL init error');
|
|
15
|
+
}
|
|
16
|
+
this.buffers = {
|
|
17
|
+
gpuVertexCallBuffer: new WebGLMemoBuffer(this.gl, this.gl.ARRAY_BUFFER, this.gl.DYNAMIC_DRAW, 0),
|
|
18
|
+
gpuIndexBuffer: new WebGLMemoBuffer(this.gl, this.gl.ELEMENT_ARRAY_BUFFER, this.gl.DYNAMIC_DRAW, 0)
|
|
19
|
+
};
|
|
20
|
+
this.gl.viewport(0, 0, canvas.width, canvas.height);
|
|
21
|
+
const clearColor = this.clearColor.toObject();
|
|
22
|
+
this.gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
23
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
|
24
|
+
}
|
|
25
|
+
getContextOrError() {
|
|
26
|
+
if (!this.gl)
|
|
27
|
+
throw logger.error('Backend not initialized');
|
|
28
|
+
return this.gl;
|
|
29
|
+
}
|
|
30
|
+
renderStart(_canvas) {
|
|
31
|
+
if (!this.gl)
|
|
32
|
+
throw logger.error('Invalid render start state');
|
|
33
|
+
const gl = this.gl;
|
|
34
|
+
gl.enable(gl.DEPTH_TEST);
|
|
35
|
+
gl.depthFunc(gl.LESS);
|
|
36
|
+
gl.enable(gl.BLEND);
|
|
37
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
38
|
+
const clearColor = this.clearColor.toObject();
|
|
39
|
+
gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
40
|
+
gl.clearDepth(1.0);
|
|
41
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
42
|
+
}
|
|
43
|
+
updateTextures(screenSize) {
|
|
44
|
+
const gl = this.gl;
|
|
45
|
+
if (!gl)
|
|
46
|
+
throw logger.error('Invalid update texture state');
|
|
47
|
+
gl.viewport(0, 0, screenSize[0], screenSize[1]);
|
|
48
|
+
const clearColor = this.clearColor.toObject();
|
|
49
|
+
gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
50
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
51
|
+
}
|
|
52
|
+
preRender() {
|
|
53
|
+
const gl = this.gl;
|
|
54
|
+
if (!gl)
|
|
55
|
+
throw logger.error('Backend not initialized');
|
|
56
|
+
const clearColor = this.clearColor.toObject();
|
|
57
|
+
gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
58
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
59
|
+
}
|
|
60
|
+
finishRender() { }
|
|
61
|
+
initShaders(shaders) {
|
|
62
|
+
if (!this.gl)
|
|
63
|
+
throw logger.error('WebGL context is null');
|
|
64
|
+
for (let i = 0; i < shaders.length; i++) {
|
|
65
|
+
const shader = shaders[i];
|
|
66
|
+
if (shader.compatableWith('webgl')) {
|
|
67
|
+
shader.as('webgl').init(this.gl);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
draw(obj, vertexCallOffset, vertexCallBuffer, indexOffset, indices) {
|
|
72
|
+
if (!this.gl || !this.buffers)
|
|
73
|
+
throw logger.error('Invalid draw state');
|
|
74
|
+
const gl = this.gl;
|
|
75
|
+
const shader = obj.getShader().as('webgl');
|
|
76
|
+
const shaderProgram = shader.getShaderProgram();
|
|
77
|
+
if (!shaderProgram)
|
|
78
|
+
throw logger.error('Null shader program');
|
|
79
|
+
const program = shader.getShaderProgram();
|
|
80
|
+
gl.useProgram(program);
|
|
81
|
+
this.buffers.gpuVertexCallBuffer.ensureCapacity(vertexCallBuffer.length);
|
|
82
|
+
shader.writeShaderProgramAttributes(this.buffers.gpuVertexCallBuffer, vertexCallOffset, vertexCallBuffer);
|
|
83
|
+
shader.writeUniformBuffers(obj);
|
|
84
|
+
this.buffers.gpuIndexBuffer.write(indices, indexOffset);
|
|
85
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.gpuIndexBuffer.getBuffer());
|
|
86
|
+
const topology = obj.getGeometryTopology();
|
|
87
|
+
const wireframe = obj.isWireframe();
|
|
88
|
+
const mode = wireframe
|
|
89
|
+
? gl.LINE_STRIP
|
|
90
|
+
: topology === 'list'
|
|
91
|
+
? gl.TRIANGLES
|
|
92
|
+
: gl.TRIANGLE_STRIP;
|
|
93
|
+
const type = gl.UNSIGNED_INT;
|
|
94
|
+
const instances = obj.isInstance
|
|
95
|
+
? obj.getInstanceCount()
|
|
96
|
+
: 1;
|
|
97
|
+
gl.drawElementsInstanced(mode, indices.length, type, indexOffset, instances);
|
|
98
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
99
|
+
}
|
|
100
|
+
destroy() {
|
|
101
|
+
if (!this.gl || !this.buffers)
|
|
102
|
+
return;
|
|
103
|
+
this.gl.deleteBuffer(this.buffers.gpuVertexCallBuffer);
|
|
104
|
+
this.gl.deleteBuffer(this.buffers.gpuIndexBuffer);
|
|
105
|
+
}
|
|
106
|
+
onClearColorChange() {
|
|
107
|
+
if (!this.gl)
|
|
108
|
+
return;
|
|
109
|
+
const clearColor = this.clearColor.toObject();
|
|
110
|
+
this.gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SimulationElement3d } from '../graphics.js';
|
|
2
|
+
import { SimJSShader } from '../shaders/shader.js';
|
|
3
|
+
import { GPUBuffers, Vector2 } from '../types.js';
|
|
4
|
+
import { SimJsBackend } from './backend.js';
|
|
5
|
+
export declare class WebGPUBackend extends SimJsBackend {
|
|
6
|
+
private device;
|
|
7
|
+
private ctx;
|
|
8
|
+
private renderPassDescriptor;
|
|
9
|
+
private multisampleTexture;
|
|
10
|
+
private depthTexture;
|
|
11
|
+
private passEncoder;
|
|
12
|
+
private commandEncoder;
|
|
13
|
+
protected buffers: GPUBuffers<'webgpu'> | null;
|
|
14
|
+
constructor();
|
|
15
|
+
getDeviceOrError(): GPUDevice;
|
|
16
|
+
init(canvas: HTMLCanvasElement): Promise<void>;
|
|
17
|
+
renderStart(canvas: HTMLCanvasElement): void;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
updateTextures(screenSize: Vector2): void;
|
|
20
|
+
preRender(scene: SimulationElement3d[]): void;
|
|
21
|
+
finishRender(): void;
|
|
22
|
+
draw(obj: SimulationElement3d, vertexCallOffset: number, vertexCallBuffer: Float32Array, indexOffset: number, indices: Uint32Array): void;
|
|
23
|
+
initShaders(shaders: SimJSShader[]): void;
|
|
24
|
+
onClearColorChange(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { WebGPUMemoBuffer } from '../buffers/webgpu.js';
|
|
2
|
+
import { logger } from '../globals.js';
|
|
3
|
+
import { buildDepthTexture, buildMultisampleTexture, getVertexAndIndexSize } from '../internalUtils.js';
|
|
4
|
+
import { SimJsBackend } from './backend.js';
|
|
5
|
+
export class WebGPUBackend extends SimJsBackend {
|
|
6
|
+
device = null;
|
|
7
|
+
ctx = null;
|
|
8
|
+
renderPassDescriptor = null;
|
|
9
|
+
multisampleTexture = null;
|
|
10
|
+
depthTexture = null;
|
|
11
|
+
passEncoder = null;
|
|
12
|
+
commandEncoder = null;
|
|
13
|
+
buffers = null;
|
|
14
|
+
constructor() {
|
|
15
|
+
super('webgpu');
|
|
16
|
+
}
|
|
17
|
+
getDeviceOrError() {
|
|
18
|
+
if (!this.device)
|
|
19
|
+
throw logger.error('Backend not initialized');
|
|
20
|
+
return this.device;
|
|
21
|
+
}
|
|
22
|
+
async init(canvas) {
|
|
23
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
24
|
+
if (!adapter)
|
|
25
|
+
throw logger.error('Adapter is null');
|
|
26
|
+
this.ctx = canvas.getContext('webgpu');
|
|
27
|
+
if (!this.ctx)
|
|
28
|
+
throw logger.error('Context is null');
|
|
29
|
+
this.device = await adapter.requestDevice();
|
|
30
|
+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
31
|
+
this.ctx.configure({
|
|
32
|
+
device: this.device,
|
|
33
|
+
format: presentationFormat,
|
|
34
|
+
alphaMode: 'opaque'
|
|
35
|
+
});
|
|
36
|
+
this.buffers = {
|
|
37
|
+
gpuVertexCallBuffer: new WebGPUMemoBuffer(this.device, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 0),
|
|
38
|
+
gpuIndexBuffer: new WebGPUMemoBuffer(this.device, GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, 0)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
renderStart(canvas) {
|
|
42
|
+
if (!this.device || !this.ctx)
|
|
43
|
+
throw logger.error('Invalid render start state');
|
|
44
|
+
const colorAttachment = {
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
view: undefined, // Assigned later
|
|
47
|
+
clearValue: this.clearColor.toObject(),
|
|
48
|
+
loadOp: 'clear',
|
|
49
|
+
storeOp: 'store'
|
|
50
|
+
};
|
|
51
|
+
this.multisampleTexture = buildMultisampleTexture(this.device, this.ctx, canvas.width, canvas.height);
|
|
52
|
+
this.depthTexture = buildDepthTexture(this.device, canvas.width, canvas.height);
|
|
53
|
+
this.renderPassDescriptor = {
|
|
54
|
+
colorAttachments: [colorAttachment],
|
|
55
|
+
depthStencilAttachment: {
|
|
56
|
+
view: this.depthTexture.createView(),
|
|
57
|
+
depthClearValue: 1.0,
|
|
58
|
+
depthLoadOp: 'clear',
|
|
59
|
+
depthStoreOp: 'store'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
destroy() {
|
|
64
|
+
this.device?.destroy();
|
|
65
|
+
this.multisampleTexture?.destroy();
|
|
66
|
+
this.depthTexture?.destroy();
|
|
67
|
+
this.buffers?.gpuVertexCallBuffer.destroy();
|
|
68
|
+
this.buffers?.gpuIndexBuffer.destroy();
|
|
69
|
+
}
|
|
70
|
+
updateTextures(screenSize) {
|
|
71
|
+
if (!this.device || !this.ctx || !this.renderPassDescriptor) {
|
|
72
|
+
throw logger.error('Invalid update texture state');
|
|
73
|
+
}
|
|
74
|
+
this.multisampleTexture = buildMultisampleTexture(this.device, this.ctx, screenSize[0], screenSize[1]);
|
|
75
|
+
this.depthTexture = buildDepthTexture(this.device, screenSize[0], screenSize[1]);
|
|
76
|
+
this.renderPassDescriptor.depthStencilAttachment.view = this.depthTexture.createView();
|
|
77
|
+
}
|
|
78
|
+
preRender(scene) {
|
|
79
|
+
if (!this.renderPassDescriptor || !this.ctx || !this.multisampleTexture || !this.device) {
|
|
80
|
+
throw logger.error('Invalid prerender state');
|
|
81
|
+
}
|
|
82
|
+
const colorAttachments = this.renderPassDescriptor
|
|
83
|
+
.colorAttachments;
|
|
84
|
+
const attachment = colorAttachments[0];
|
|
85
|
+
attachment.view = this.multisampleTexture.createView();
|
|
86
|
+
attachment.resolveTarget = this.ctx.getCurrentTexture().createView();
|
|
87
|
+
this.commandEncoder = this.device.createCommandEncoder();
|
|
88
|
+
this.passEncoder = this.commandEncoder.beginRenderPass(this.renderPassDescriptor);
|
|
89
|
+
const [totalVerticesSize, totalIndexSize] = getVertexAndIndexSize(scene);
|
|
90
|
+
this.buffers.gpuVertexCallBuffer.ensureCapacity(totalVerticesSize * 4);
|
|
91
|
+
this.buffers.gpuIndexBuffer.ensureCapacity(totalIndexSize * 4);
|
|
92
|
+
}
|
|
93
|
+
finishRender() {
|
|
94
|
+
this.passEncoder.end();
|
|
95
|
+
this.device.queue.submit([this.commandEncoder.finish()]);
|
|
96
|
+
}
|
|
97
|
+
draw(obj, vertexCallOffset, vertexCallBuffer, indexOffset, indices) {
|
|
98
|
+
if (!this.device || !this.buffers || !this.passEncoder) {
|
|
99
|
+
throw logger.error('Invalid draw state');
|
|
100
|
+
}
|
|
101
|
+
this.device.queue.writeBuffer(this.buffers.gpuVertexCallBuffer.getBuffer(), vertexCallOffset, vertexCallBuffer.buffer, vertexCallBuffer.byteOffset, vertexCallBuffer.byteLength);
|
|
102
|
+
this.device.queue.writeBuffer(this.buffers.gpuIndexBuffer.getBuffer(), indexOffset, indices.buffer, indices.byteOffset, indices.byteLength);
|
|
103
|
+
this.passEncoder.setVertexBuffer(0, this.buffers.gpuVertexCallBuffer.getBuffer(), vertexCallOffset, vertexCallBuffer.byteLength);
|
|
104
|
+
this.passEncoder.setIndexBuffer(this.buffers.gpuIndexBuffer.getBuffer(), 'uint32', indexOffset, indices.byteLength);
|
|
105
|
+
this.passEncoder.setPipeline(obj.getPipeline());
|
|
106
|
+
const shader = obj.getShader().as('webgpu');
|
|
107
|
+
shader.writeUniformBuffers(obj);
|
|
108
|
+
const bindGroups = shader.getBindGroups(this.device, obj);
|
|
109
|
+
for (let i = 0; i < bindGroups.length; i++) {
|
|
110
|
+
this.passEncoder.setBindGroup(i, bindGroups[i]);
|
|
111
|
+
}
|
|
112
|
+
const instances = obj.isInstance
|
|
113
|
+
? obj.getInstanceCount()
|
|
114
|
+
: 1;
|
|
115
|
+
this.passEncoder.drawIndexed(indices.length, instances);
|
|
116
|
+
}
|
|
117
|
+
initShaders(shaders) {
|
|
118
|
+
if (!this.device)
|
|
119
|
+
throw logger.error('WebGPU device is null');
|
|
120
|
+
for (let i = 0; i < shaders.length; i++) {
|
|
121
|
+
const shader = shaders[i];
|
|
122
|
+
if (shader.compatableWith('webgpu')) {
|
|
123
|
+
shader.as('webgpu').init(this.device);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
onClearColorChange() {
|
|
128
|
+
if (!this.renderPassDescriptor)
|
|
129
|
+
return;
|
|
130
|
+
const colorAttachments = this.renderPassDescriptor
|
|
131
|
+
.colorAttachments;
|
|
132
|
+
colorAttachments[0].clearValue = this.clearColor.toObject();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ArrayTypes, BackendType, BufferFromBackendType, SpecificMemoBufferType } from '../types.js';
|
|
2
|
+
export declare abstract class MemoBuffer {
|
|
3
|
+
protected abstract buffer: BufferFromBackendType<unknown> | null;
|
|
4
|
+
protected bufferCapacity: number;
|
|
5
|
+
private backendType;
|
|
6
|
+
constructor(backendType: BackendType, initCapacity: number);
|
|
7
|
+
abstract allocBuffer(): void;
|
|
8
|
+
abstract destroy(): void;
|
|
9
|
+
write(_buf: ArrayTypes, _offset?: number): void;
|
|
10
|
+
getBuffer(): {};
|
|
11
|
+
private growCapacity;
|
|
12
|
+
ensureCapacity(capacity: number): void;
|
|
13
|
+
setCapacityPrecise(capacity: number): void;
|
|
14
|
+
as<T extends BackendType>(type: T): SpecificMemoBufferType<T>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { logger } from '../globals.js';
|
|
2
|
+
export class MemoBuffer {
|
|
3
|
+
bufferCapacity;
|
|
4
|
+
backendType;
|
|
5
|
+
constructor(backendType, initCapacity) {
|
|
6
|
+
this.backendType = backendType;
|
|
7
|
+
this.bufferCapacity = initCapacity;
|
|
8
|
+
}
|
|
9
|
+
// cant be abstract because offset should be default param to 0
|
|
10
|
+
// which it wont allow (even though it could)
|
|
11
|
+
write(_buf, _offset = 0) { }
|
|
12
|
+
getBuffer() {
|
|
13
|
+
if (!this.buffer)
|
|
14
|
+
this.allocBuffer();
|
|
15
|
+
return this.buffer;
|
|
16
|
+
}
|
|
17
|
+
growCapacity(current, target) {
|
|
18
|
+
let res = Math.max(1, current);
|
|
19
|
+
while (res < target) {
|
|
20
|
+
res += Math.ceil(res / 2);
|
|
21
|
+
}
|
|
22
|
+
return res;
|
|
23
|
+
}
|
|
24
|
+
ensureCapacity(capacity) {
|
|
25
|
+
this.setCapacityPrecise(this.growCapacity(this.bufferCapacity, capacity));
|
|
26
|
+
}
|
|
27
|
+
setCapacityPrecise(capacity) {
|
|
28
|
+
if (!this.buffer) {
|
|
29
|
+
this.bufferCapacity = capacity;
|
|
30
|
+
this.allocBuffer();
|
|
31
|
+
}
|
|
32
|
+
if (capacity <= this.bufferCapacity)
|
|
33
|
+
return;
|
|
34
|
+
this.bufferCapacity = capacity;
|
|
35
|
+
this.allocBuffer();
|
|
36
|
+
}
|
|
37
|
+
as(type) {
|
|
38
|
+
if (type !== this.backendType)
|
|
39
|
+
throw logger.error('Incompatible memo buffer cast');
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ArrayTypes } from '../types.js';
|
|
2
|
+
import { MemoBuffer } from './buffer.js';
|
|
3
|
+
export declare class WebGLMemoBuffer extends MemoBuffer {
|
|
4
|
+
private gl;
|
|
5
|
+
protected buffer: WebGLBuffer | null;
|
|
6
|
+
private target;
|
|
7
|
+
private usage;
|
|
8
|
+
constructor(gl: WebGL2RenderingContext, target: GLenum, usage: GLenum, initCapacity: number);
|
|
9
|
+
allocBuffer(): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
getBuffer(): WebGLBuffer;
|
|
12
|
+
write(buf: ArrayTypes, offset?: number): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { logger } from '../globals.js';
|
|
2
|
+
import { MemoBuffer } from './buffer.js';
|
|
3
|
+
export class WebGLMemoBuffer extends MemoBuffer {
|
|
4
|
+
gl;
|
|
5
|
+
buffer = null;
|
|
6
|
+
target;
|
|
7
|
+
usage;
|
|
8
|
+
constructor(gl, target, usage, initCapacity) {
|
|
9
|
+
super('webgl', initCapacity);
|
|
10
|
+
this.gl = gl;
|
|
11
|
+
this.target = target;
|
|
12
|
+
this.usage = usage;
|
|
13
|
+
}
|
|
14
|
+
allocBuffer() {
|
|
15
|
+
const gl = this.gl;
|
|
16
|
+
if (this.buffer)
|
|
17
|
+
gl.deleteBuffer(this.buffer);
|
|
18
|
+
this.buffer = gl.createBuffer();
|
|
19
|
+
if (!this.buffer)
|
|
20
|
+
throw logger.error('WebGLMemoBuffer init error');
|
|
21
|
+
gl.bindBuffer(this.target, this.buffer);
|
|
22
|
+
gl.bufferData(this.target, this.bufferCapacity, this.usage);
|
|
23
|
+
gl.bindBuffer(this.target, null);
|
|
24
|
+
}
|
|
25
|
+
destroy() {
|
|
26
|
+
if (this.buffer) {
|
|
27
|
+
this.gl.deleteBuffer(this.buffer);
|
|
28
|
+
this.buffer = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
getBuffer() {
|
|
32
|
+
if (!this.buffer)
|
|
33
|
+
this.allocBuffer();
|
|
34
|
+
return this.buffer;
|
|
35
|
+
}
|
|
36
|
+
write(buf, offset = 0) {
|
|
37
|
+
const neededSize = offset + buf.byteLength;
|
|
38
|
+
if (!this.buffer || neededSize > this.bufferCapacity) {
|
|
39
|
+
this.bufferCapacity = neededSize;
|
|
40
|
+
this.allocBuffer();
|
|
41
|
+
}
|
|
42
|
+
this.gl.bindBuffer(this.target, this.buffer);
|
|
43
|
+
this.gl.bufferSubData(this.target, offset, buf);
|
|
44
|
+
this.gl.bindBuffer(this.target, null);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ArrayTypes } from '../types.js';
|
|
2
|
+
import { MemoBuffer } from './buffer.js';
|
|
3
|
+
export declare class WebGPUMemoBuffer extends MemoBuffer {
|
|
4
|
+
protected device: GPUDevice;
|
|
5
|
+
protected buffer: GPUBuffer | null;
|
|
6
|
+
private usage;
|
|
7
|
+
constructor(device: GPUDevice, usage: GPUBufferDescriptor['usage'], initCapacity: number);
|
|
8
|
+
allocBuffer(): void;
|
|
9
|
+
getBuffer(): GPUBuffer;
|
|
10
|
+
write(buf: ArrayTypes, offset?: number): void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
}
|