glre 0.45.0 → 0.46.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/README.md +4 -26
- package/dist/addons.d.ts +35 -50
- package/dist/index.cjs +6 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +37 -90
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +1 -1
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.ts +45 -93
- package/dist/native.js +1 -1
- package/dist/native.js.map +1 -1
- package/dist/node.cjs +15 -15
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +35 -50
- package/dist/node.js +14 -14
- package/dist/node.js.map +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +37 -91
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +1 -1
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.ts +37 -91
- package/dist/solid.js +1 -1
- package/dist/solid.js.map +1 -1
- package/package.json +1 -1
- package/src/{utils/helpers.ts → helpers.ts} +10 -32
- package/src/index.ts +45 -42
- package/src/native.ts +6 -7
- package/src/node/build.ts +3 -19
- package/src/node/create.ts +2 -4
- package/src/node/index.ts +8 -20
- package/src/node/types.ts +2 -0
- package/src/node/utils/index.ts +1 -1
- package/src/node/utils/infer.ts +4 -12
- package/src/node/utils/parse.ts +18 -34
- package/src/node/utils/utils.ts +3 -3
- package/src/react.ts +9 -12
- package/src/solid.ts +3 -10
- package/src/types.ts +30 -22
- package/src/webgl/compute.ts +56 -0
- package/src/webgl/graphic.ts +65 -0
- package/src/webgl/index.ts +21 -0
- package/src/{utils/program.ts → webgl/utils.ts} +30 -8
- package/src/webgpu/compute.ts +39 -0
- package/src/webgpu/graphic.ts +89 -0
- package/src/webgpu/index.ts +42 -0
- package/src/{utils/pipeline.ts → webgpu/utils.ts} +75 -78
- package/src/utils/webgl.ts +0 -135
- package/src/utils/webgpu.ts +0 -178
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { nested } from 'reev'
|
|
2
|
+
import { cleanStorage, createAttachment, createProgram, createStorage, storageSize, updateUniform } from './utils'
|
|
3
|
+
import { GLSL_VS, is } from '../helpers'
|
|
4
|
+
import type { GL } from '../types'
|
|
5
|
+
|
|
6
|
+
export const compute = (gl: GL) => {
|
|
7
|
+
if (!gl.cs) return
|
|
8
|
+
const c = gl.gl
|
|
9
|
+
c.getExtension('EXT_color_buffer_float') // Enable high precision GPGPU by writing to float textures
|
|
10
|
+
|
|
11
|
+
let _texture = 0 // for texture active units
|
|
12
|
+
let _storage = 0 // for storage current num
|
|
13
|
+
|
|
14
|
+
const units = nested(() => _texture++)
|
|
15
|
+
const cs = is.str(gl.cs) ? gl.cs : gl.cs!.compute({ isWebGL: true, gl, units })
|
|
16
|
+
const pg = createProgram(c, cs, GLSL_VS, gl)!
|
|
17
|
+
const size = storageSize(gl.particleCount)
|
|
18
|
+
|
|
19
|
+
const uniforms = nested((key) => c.getUniformLocation(pg, key)!)
|
|
20
|
+
const storages = nested((key) => {
|
|
21
|
+
const array = new Float32Array(size.x * size.y * 4) // RGBA texture data
|
|
22
|
+
const ping = { texture: c.createTexture(), buffer: c.createFramebuffer() }
|
|
23
|
+
const pong = { texture: c.createTexture(), buffer: c.createFramebuffer() }
|
|
24
|
+
return { ping, pong, array, loc: uniforms(key), unit: units(key) }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
gl('_uniform', (key: string, value: number | number[]) => {
|
|
28
|
+
c.useProgram((gl.program = pg))
|
|
29
|
+
updateUniform(c, uniforms(key), value)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
gl('_storage', (key: string, value: number[]) => {
|
|
33
|
+
c.useProgram((gl.program = pg))
|
|
34
|
+
const { ping, pong, unit, array } = storages(key)
|
|
35
|
+
createStorage(c, value, size.x, size.y, ping, pong, unit, array)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
gl('clean', () => {
|
|
39
|
+
c.deleteProgram(pg)
|
|
40
|
+
cleanStorage(c, storages.map.values())
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
gl('render', () => {
|
|
44
|
+
c.useProgram((gl.program = pg))
|
|
45
|
+
const attachments = storages.map.values().map(({ ping, pong, loc, unit }, index) => {
|
|
46
|
+
const [i, o] = _storage % 2 ? [ping, pong] : [pong, ping]
|
|
47
|
+
return createAttachment(c, i, o, loc, unit, index)
|
|
48
|
+
})
|
|
49
|
+
c.drawBuffers(attachments)
|
|
50
|
+
c.drawArrays(c.TRIANGLES, 0, 3)
|
|
51
|
+
c.bindFramebuffer(c.FRAMEBUFFER, null)
|
|
52
|
+
_storage++
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type WebGLCompute = ReturnType<typeof compute>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { nested } from 'reev'
|
|
2
|
+
import { getStride, GLSL_FS, GLSL_VS, is, loadingTexture } from '../helpers'
|
|
3
|
+
import { createBuffer, createProgram, createTexture, updateAttrib, updateBuffer, updateInstance, updateUniform } from './utils'
|
|
4
|
+
import type { GL } from '../types'
|
|
5
|
+
|
|
6
|
+
export const graphic = (gl: GL) => {
|
|
7
|
+
const config = { isWebGL: true, gl }
|
|
8
|
+
const c = gl.gl
|
|
9
|
+
const fs = gl.fs ? (is.str(gl.fs) ? gl.fs : gl.fs.fragment(config)) : GLSL_FS
|
|
10
|
+
const vs = gl.vs ? (is.str(gl.vs) ? gl.vs : gl.vs.vertex(config)) : GLSL_VS
|
|
11
|
+
const pg = createProgram(c, fs, vs, gl)!
|
|
12
|
+
let activeUnit = 0
|
|
13
|
+
|
|
14
|
+
const units = nested(() => activeUnit++)
|
|
15
|
+
const uniforms = nested((key) => c.getUniformLocation(pg, key))
|
|
16
|
+
const attributes = nested((key, value: number[], isInstance = false) => {
|
|
17
|
+
const stride = getStride(value.length, isInstance ? gl.instanceCount : gl.triangleCount, gl.error)
|
|
18
|
+
return { stride, location: c.getAttribLocation(pg, key), ...createBuffer(c, value) }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
gl('_attribute', (key: string, value: number[]) => {
|
|
22
|
+
c.useProgram((gl.program = pg))
|
|
23
|
+
const a = attributes(key, value)
|
|
24
|
+
updateBuffer(c, a.array, a.buffer, value)
|
|
25
|
+
updateAttrib(c, a.location, a.stride, a.buffer)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
gl('_instance', (key: string, value: number[]) => {
|
|
29
|
+
c.useProgram((gl.program = pg))
|
|
30
|
+
const a = attributes(key, value, true)
|
|
31
|
+
updateBuffer(c, a.array, a.buffer, value)
|
|
32
|
+
updateInstance(c, a.location, a.stride, a.buffer)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
gl('_uniform', (key: string, value: number | number[]) => {
|
|
36
|
+
c.useProgram((gl.program = pg))
|
|
37
|
+
updateUniform(c, uniforms(key), value)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
gl('_texture', (key: string, src: string) => {
|
|
41
|
+
c.useProgram((gl.program = pg))
|
|
42
|
+
const location = uniforms(key)
|
|
43
|
+
const unit = units(key)
|
|
44
|
+
createTexture(c, null, location, unit, false)
|
|
45
|
+
loadingTexture(src, (source, isVideo) => {
|
|
46
|
+
c.useProgram((gl.program = pg))
|
|
47
|
+
const render = createTexture(c, source, location, unit, isVideo)
|
|
48
|
+
if (render) gl({ render })
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
gl('clean', () => {
|
|
53
|
+
c.deleteProgram(pg)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
gl('render', () => {
|
|
57
|
+
c.useProgram((gl.program = pg))
|
|
58
|
+
if (gl.instanceCount > 1) {
|
|
59
|
+
c.drawArraysInstanced(c.TRIANGLES, 0, gl.triangleCount, gl.instanceCount)
|
|
60
|
+
} else c.drawArrays(c.TRIANGLES, 0, gl.triangleCount)
|
|
61
|
+
c.bindFramebuffer(c.FRAMEBUFFER, null)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type WebGLGraphic = ReturnType<typeof graphic>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { compute } from './compute'
|
|
2
|
+
import { graphic } from './graphic'
|
|
3
|
+
import { enableDepth, enableWireframe, loseContext } from './utils'
|
|
4
|
+
import type { GL } from '../types'
|
|
5
|
+
|
|
6
|
+
export const webgl = (gl: GL) => {
|
|
7
|
+
const isInit = !gl.gl
|
|
8
|
+
if (isInit) {
|
|
9
|
+
const c = (gl.gl = gl.el.getContext('webgl2')!)
|
|
10
|
+
gl('render', () => c.viewport(0, 0, ...gl.size!)) // Run before other renderers' events to prevent flickering
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
compute(gl)
|
|
14
|
+
graphic(gl)
|
|
15
|
+
|
|
16
|
+
if (isInit) {
|
|
17
|
+
gl('clean', () => loseContext(gl.gl))
|
|
18
|
+
if (gl.isDepth) enableDepth(gl.gl)
|
|
19
|
+
if (gl.wireframe) enableWireframe(gl.gl)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { is } from '
|
|
1
|
+
import { is } from '../helpers'
|
|
2
2
|
import type { GL } from '../types'
|
|
3
3
|
|
|
4
4
|
const createShader = (c: WebGL2RenderingContext, source: string, type: number, onError = console.warn) => {
|
|
@@ -26,13 +26,13 @@ export const createProgram = (c: WebGL2RenderingContext, frag: string, vert: str
|
|
|
26
26
|
gl.error(`Could not link program: ${error}`)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export const
|
|
29
|
+
export const createBuffer = (c: WebGL2RenderingContext, data: number[]) => {
|
|
30
30
|
const array = new Float32Array(data)
|
|
31
31
|
const buffer = c.createBuffer()
|
|
32
32
|
return { array, buffer }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export const
|
|
35
|
+
export const updateBuffer = (c: WebGL2RenderingContext, array: Float32Array, buffer: WebGLBuffer, value: number[]) => {
|
|
36
36
|
array.set(value)
|
|
37
37
|
c.bindBuffer(c.ARRAY_BUFFER, buffer)
|
|
38
38
|
c.bufferData(c.ARRAY_BUFFER, array, c.STATIC_DRAW)
|
|
@@ -52,7 +52,8 @@ export const updateInstance = (c: WebGL2RenderingContext, loc: number, stride: n
|
|
|
52
52
|
c.vertexAttribDivisor(loc, 1) // divisor is 1
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export const updateUniform = (c: WebGL2RenderingContext, loc: WebGLUniformLocation, value: number | number[]) => {
|
|
55
|
+
export const updateUniform = (c: WebGL2RenderingContext, loc: WebGLUniformLocation | null, value: number | number[]) => {
|
|
56
|
+
if (is.nul(loc)) return
|
|
56
57
|
if (is.num(value)) return c.uniform1f(loc, value)
|
|
57
58
|
let l = value.length
|
|
58
59
|
if (l <= 4) return c[`uniform${l as 2}fv`](loc, value)
|
|
@@ -60,11 +61,15 @@ export const updateUniform = (c: WebGL2RenderingContext, loc: WebGLUniformLocati
|
|
|
60
61
|
c[`uniformMatrix${l as 2}fv`](loc, false, value)
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
export const createTexture = (c: WebGL2RenderingContext, el: HTMLImageElement | HTMLVideoElement, loc: WebGLUniformLocation, unit: number, isVideo = false) => {
|
|
64
|
+
export const createTexture = (c: WebGL2RenderingContext, el: HTMLImageElement | HTMLVideoElement | null, loc: WebGLUniformLocation | null, unit: number, isVideo = false) => {
|
|
64
65
|
const texture = c.createTexture()
|
|
65
66
|
c.bindTexture(c.TEXTURE_2D, texture)
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if (el) {
|
|
68
|
+
c.texImage2D(c.TEXTURE_2D, 0, c.RGBA, c.RGBA, c.UNSIGNED_BYTE, el)
|
|
69
|
+
if (!isVideo) c.generateMipmap(c.TEXTURE_2D)
|
|
70
|
+
} else {
|
|
71
|
+
c.texImage2D(c.TEXTURE_2D, 0, c.RGBA, 1, 1, 0, c.RGBA, c.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]))
|
|
72
|
+
}
|
|
68
73
|
c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MIN_FILTER, c.LINEAR)
|
|
69
74
|
c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MAG_FILTER, c.LINEAR)
|
|
70
75
|
c.texParameteri(c.TEXTURE_2D, c.TEXTURE_WRAP_S, c.CLAMP_TO_EDGE)
|
|
@@ -77,7 +82,7 @@ export const createTexture = (c: WebGL2RenderingContext, el: HTMLImageElement |
|
|
|
77
82
|
return () => {
|
|
78
83
|
c.activeTexture(c.TEXTURE0 + unit)
|
|
79
84
|
c.bindTexture(c.TEXTURE_2D, texture)
|
|
80
|
-
c.texImage2D(c.TEXTURE_2D, 0, c.RGBA, c.RGBA, c.UNSIGNED_BYTE, el)
|
|
85
|
+
c.texImage2D(c.TEXTURE_2D, 0, c.RGBA, c.RGBA, c.UNSIGNED_BYTE, el!)
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -149,3 +154,20 @@ export const storageSize = (particleCount: number | number[] = 1024) => {
|
|
|
149
154
|
}
|
|
150
155
|
return { x, y }
|
|
151
156
|
}
|
|
157
|
+
|
|
158
|
+
export const loseContext = (c: WebGL2RenderingContext) => {
|
|
159
|
+
const ext = c.getExtension('WEBGL_lose_context')
|
|
160
|
+
if (ext) ext.loseContext()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const enableDepth = (c: WebGL2RenderingContext) => {
|
|
164
|
+
c.enable(c.DEPTH_TEST)
|
|
165
|
+
c.depthFunc(c.LEQUAL)
|
|
166
|
+
c.enable(c.CULL_FACE)
|
|
167
|
+
c.cullFace(c.BACK)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const enableWireframe = (c: WebGL2RenderingContext) => {
|
|
171
|
+
const ext = c.getExtension('WEBGL_polygon_mode')
|
|
172
|
+
if (ext) ext.polygonModeWEBGL(c.FRONT_AND_BACK, ext.LINE_WEBGL)
|
|
173
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { nested } from 'reev'
|
|
2
|
+
import { createBuffer, updateBuffer, workgroupCount } from './utils'
|
|
3
|
+
import type { Binding } from './utils'
|
|
4
|
+
import type { GL } from '../types'
|
|
5
|
+
|
|
6
|
+
export const compute = (gl: GL, bindings: Binding) => {
|
|
7
|
+
let pipeline: GPUComputePipeline | undefined
|
|
8
|
+
let bindGroups: GPUBindGroup[] | undefined
|
|
9
|
+
|
|
10
|
+
const storages = nested((key, value: number[] | Float32Array) => {
|
|
11
|
+
return { ...bindings.storage(key), ...createBuffer(gl.device, value, 'storage') }
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
gl('_storage', (key: string, value: number[] | Float32Array) => {
|
|
15
|
+
const { array, buffer } = storages(key, value)
|
|
16
|
+
updateBuffer(gl.device, value, array, buffer)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
gl('render', () => {
|
|
20
|
+
if (!pipeline || !bindGroups) return
|
|
21
|
+
const pass = gl.encoder.beginComputePass()
|
|
22
|
+
pass.setPipeline(pipeline)
|
|
23
|
+
bindGroups.forEach((v, i) => pass.setBindGroup(i, v))
|
|
24
|
+
const { x, y, z } = workgroupCount(gl.particleCount)
|
|
25
|
+
pass.dispatchWorkgroups(x, y, z)
|
|
26
|
+
pass.end()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
gl('clean', () => {
|
|
30
|
+
for (const { buffer } of storages.map.values()) buffer.destroy()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const set = (_pipeline?: GPUComputePipeline, _bindGroups?: GPUBindGroup[]) => {
|
|
34
|
+
pipeline = _pipeline
|
|
35
|
+
bindGroups = _bindGroups
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { storages, set }
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { nested } from 'reev'
|
|
2
|
+
import { is, getStride, loadingTexture } from '../helpers'
|
|
3
|
+
import { createBuffer, createDepthTexture, createDescriptor, createTextureSampler, updateBuffer } from './utils'
|
|
4
|
+
import type { Binding } from './utils'
|
|
5
|
+
import type { GL } from '../types'
|
|
6
|
+
|
|
7
|
+
export const graphic = (gl: GL, bindings: Binding, update = () => {}) => {
|
|
8
|
+
let pipeline: GPURenderPipeline
|
|
9
|
+
let bindGroups: GPUBindGroup[]
|
|
10
|
+
let vertexBuffers: GPUBuffer[]
|
|
11
|
+
let depthTexture: GPUTexture
|
|
12
|
+
|
|
13
|
+
const attributes = nested((key, value: number[], isInstance = false, stride = getStride(value.length, isInstance ? gl.instanceCount : gl.triangleCount)) => {
|
|
14
|
+
update()
|
|
15
|
+
return { ...bindings.attrib(key), ...createBuffer(gl.device, value, 'attrib'), isInstance, stride }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const uniforms = nested((key, value: number[] | Float32Array) => {
|
|
19
|
+
update()
|
|
20
|
+
return { ...bindings.uniform(key), ...createBuffer(gl.device, value, 'uniform') }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const textures = nested((key, width = 1, height = 1) => {
|
|
24
|
+
update()
|
|
25
|
+
return { ...bindings.texture(key), ...createTextureSampler(gl.device, width, height) }
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
gl('_attribute', (key: string, value: number[] | Float32Array) => {
|
|
29
|
+
const a = attributes(key, value)
|
|
30
|
+
updateBuffer(gl.device, value, a.array, a.buffer)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
gl('_instance', (key: string, value: number[] | Float32Array) => {
|
|
34
|
+
const a = attributes(key, value, true)
|
|
35
|
+
updateBuffer(gl.device, value, a.array, a.buffer)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
gl('_uniform', (key: string, value: number | number[] | Float32Array) => {
|
|
39
|
+
if (is.num(value)) value = [value]
|
|
40
|
+
const u = uniforms(key, value)
|
|
41
|
+
updateBuffer(gl.device, value, u.array, u.buffer)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
gl('_texture', (key: string, src: string) => {
|
|
45
|
+
const t = textures(key)
|
|
46
|
+
loadingTexture(src, (source, isVideo) => {
|
|
47
|
+
const [width, height] = isVideo ? [source.videoWidth, source.videoHeight] : [source.width, source.height]
|
|
48
|
+
if (t.texture.width !== width || t.texture.height !== height) {
|
|
49
|
+
t.texture.destroy()
|
|
50
|
+
Object.assign(t, createTextureSampler(gl.device, width, height))
|
|
51
|
+
update() // Rebuilding BindGroups because the texture size has changed
|
|
52
|
+
}
|
|
53
|
+
const render = () => void gl.device.queue.copyExternalImageToTexture({ source }, { texture: t.texture }, { width, height })
|
|
54
|
+
if (isVideo) gl({ render })
|
|
55
|
+
else render()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
gl('render', () => {
|
|
60
|
+
if (!pipeline || !bindGroups || !vertexBuffers) return
|
|
61
|
+
const pass = gl.encoder.beginRenderPass(createDescriptor(gl.gpu, depthTexture))
|
|
62
|
+
pass.setPipeline(pipeline)
|
|
63
|
+
bindGroups.forEach((v, i) => pass.setBindGroup(i, v))
|
|
64
|
+
vertexBuffers.forEach((v, i) => pass.setVertexBuffer(i, v))
|
|
65
|
+
pass.draw(gl.triangleCount, gl.instanceCount, 0, 0)
|
|
66
|
+
pass.end()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
gl('resize', () => {
|
|
70
|
+
const canvas = gl.el as HTMLCanvasElement
|
|
71
|
+
depthTexture?.destroy()
|
|
72
|
+
depthTexture = createDepthTexture(gl.device, canvas.width, canvas.height)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
gl('clean', () => {
|
|
76
|
+
depthTexture?.destroy()
|
|
77
|
+
for (const { buffer } of attributes.map.values()) buffer.destroy()
|
|
78
|
+
for (const { texture } of textures.map.values()) texture.destroy()
|
|
79
|
+
for (const { buffer } of uniforms.map.values()) buffer.destroy()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const set = (_pipeline: GPURenderPipeline, _bindGroups: GPUBindGroup[], _vertexBuffers: GPUBuffer[]) => {
|
|
83
|
+
pipeline = _pipeline
|
|
84
|
+
bindGroups = _bindGroups
|
|
85
|
+
vertexBuffers = _vertexBuffers
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { uniforms, textures, attributes, set }
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { compute } from './compute'
|
|
2
|
+
import { graphic } from './graphic'
|
|
3
|
+
import { createBinding, createDevice, updatePipeline } from './utils'
|
|
4
|
+
import { is, WGSL_FS, WGSL_VS } from '../helpers'
|
|
5
|
+
import type { GL } from '../types'
|
|
6
|
+
|
|
7
|
+
export const webgpu = async (gl: GL) => {
|
|
8
|
+
let isUpdate = false
|
|
9
|
+
const isInit = !gl.gl
|
|
10
|
+
if (isInit) {
|
|
11
|
+
const gpu = gl.el!.getContext('webgpu') as GPUCanvasContext
|
|
12
|
+
const { device, format } = await createDevice(gpu, gl.error)
|
|
13
|
+
gl({ device, format, gpu })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
gl('render', () => {
|
|
17
|
+
if (isUpdate) update()
|
|
18
|
+
gl.encoder = gl.device.createCommandEncoder()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const binding = createBinding()
|
|
22
|
+
const c = compute(gl, binding)
|
|
23
|
+
const g = graphic(gl, binding, () => (isUpdate = true))
|
|
24
|
+
const update = () => {
|
|
25
|
+
isUpdate = false
|
|
26
|
+
const config = { isWebGL: false, gl, binding }
|
|
27
|
+
const fs = gl.fs ? (is.str(gl.fs) ? gl.fs : gl.fs.fragment(config)) : WGSL_FS
|
|
28
|
+
const vs = gl.vs ? (is.str(gl.vs) ? gl.vs : gl.vs.vertex(config)) : WGSL_VS
|
|
29
|
+
const cs = gl.cs ? (is.str(gl.cs) ? gl.cs : gl.cs.compute(config)) : ''
|
|
30
|
+
const p = updatePipeline(gl.device, gl.format, g.attributes.map.values(), g.uniforms.map.values(), g.textures.map.values(), c.storages.map.values(), fs, cs, vs)
|
|
31
|
+
c.set(p.computePipeline, p.bindGroups)
|
|
32
|
+
g.set(p.graphicPipeline, p.bindGroups, p.vertexBuffers)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
gl('render', () => {
|
|
36
|
+
if (gl.encoder) gl.device.queue.submit([gl.encoder.finish()])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
gl('clean', () => {
|
|
40
|
+
gl.device.destroy()
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -1,53 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { nested } from 'reev'
|
|
2
|
+
import { is, isFloat32 } from '../helpers'
|
|
2
3
|
import type { AttribData, TextureData, UniformData, StorageData } from '../types'
|
|
3
4
|
|
|
5
|
+
type IAttribs = Iterable<AttribData & { isInstance?: boolean }>
|
|
6
|
+
type IUniforms = Iterable<UniformData>
|
|
7
|
+
type ITextures = Iterable<TextureData>
|
|
8
|
+
type IStorages = Iterable<StorageData>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* binding
|
|
12
|
+
*/
|
|
13
|
+
export const createBinding = () => {
|
|
14
|
+
let _uniform = 0
|
|
15
|
+
let _texture = 0
|
|
16
|
+
let _storage = 0
|
|
17
|
+
let _attrib = 0
|
|
18
|
+
const uniform = nested(() => {
|
|
19
|
+
const group = Math.floor(_uniform / 12)
|
|
20
|
+
const binding = _uniform % 12
|
|
21
|
+
_uniform++
|
|
22
|
+
return { group, binding }
|
|
23
|
+
})
|
|
24
|
+
const texture = nested(() => {
|
|
25
|
+
const baseGroup = Math.floor(_uniform / 12) + 1
|
|
26
|
+
const group = baseGroup + Math.floor(_texture / 6)
|
|
27
|
+
const binding = (_texture % 6) * 2
|
|
28
|
+
_texture++
|
|
29
|
+
return { group, binding }
|
|
30
|
+
})
|
|
31
|
+
const storage = nested(() => {
|
|
32
|
+
const baseGroup = Math.floor(_uniform / 12) + Math.floor(_texture / 6) + 2
|
|
33
|
+
const group = baseGroup + Math.floor(_storage / 12)
|
|
34
|
+
const binding = _storage % 12
|
|
35
|
+
_storage++
|
|
36
|
+
return { group, binding }
|
|
37
|
+
})
|
|
38
|
+
const attrib = nested(() => {
|
|
39
|
+
const location = _attrib
|
|
40
|
+
_attrib++
|
|
41
|
+
return { location }
|
|
42
|
+
})
|
|
43
|
+
return { uniform, texture, storage, attrib }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type Binding = ReturnType<typeof createBinding>
|
|
47
|
+
|
|
4
48
|
/**
|
|
5
49
|
* initialize
|
|
6
50
|
*/
|
|
7
|
-
export const createDevice = async (c: GPUCanvasContext, log = console.log) => {
|
|
51
|
+
export const createDevice = async (c: GPUCanvasContext, log = console.log, signal?: AbortSignal) => {
|
|
8
52
|
const gpu = navigator.gpu
|
|
9
53
|
const format = gpu.getPreferredCanvasFormat()
|
|
10
54
|
const adapter = await gpu.requestAdapter()
|
|
55
|
+
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
|
|
11
56
|
const device = await adapter!.requestDevice()
|
|
57
|
+
if (signal?.aborted) {
|
|
58
|
+
device.destroy()
|
|
59
|
+
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
|
|
60
|
+
}
|
|
12
61
|
device.onuncapturederror = (e) => log(e.error.message)
|
|
13
|
-
c.configure({ device, format, alphaMode: '
|
|
62
|
+
c.configure({ device, format, alphaMode: 'premultiplied' })
|
|
14
63
|
return { device, format }
|
|
15
64
|
}
|
|
16
65
|
|
|
17
|
-
export const createBindings = () => {
|
|
18
|
-
let uniform = 0
|
|
19
|
-
let texture = 0
|
|
20
|
-
let storage = 0
|
|
21
|
-
let attrib = 0
|
|
22
|
-
return {
|
|
23
|
-
uniform: () => {
|
|
24
|
-
const group = Math.floor(uniform / 12)
|
|
25
|
-
const binding = uniform % 12
|
|
26
|
-
uniform++
|
|
27
|
-
return { group, binding }
|
|
28
|
-
},
|
|
29
|
-
texture: () => {
|
|
30
|
-
const baseGroup = Math.floor(uniform / 12) + 1
|
|
31
|
-
const group = baseGroup + Math.floor(texture / 6)
|
|
32
|
-
const binding = (texture % 6) * 2
|
|
33
|
-
texture++
|
|
34
|
-
return { group, binding }
|
|
35
|
-
},
|
|
36
|
-
storage: () => {
|
|
37
|
-
const baseGroup = Math.floor(uniform / 12) + Math.floor(texture / 6) + 2
|
|
38
|
-
const group = baseGroup + Math.floor(storage / 12)
|
|
39
|
-
const binding = storage % 12
|
|
40
|
-
storage++
|
|
41
|
-
return { group, binding }
|
|
42
|
-
},
|
|
43
|
-
attrib: () => {
|
|
44
|
-
const location = attrib
|
|
45
|
-
attrib++
|
|
46
|
-
return { location }
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
66
|
/**
|
|
52
67
|
* pipeline update
|
|
53
68
|
*/
|
|
@@ -58,7 +73,7 @@ const getVertexFormat = (stride: number): GPUVertexFormat => {
|
|
|
58
73
|
return 'float32'
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
const createVertexBuffers = (attribs: IAttribs) => {
|
|
62
77
|
const vertexBuffers: GPUBuffer[] = []
|
|
63
78
|
const bufferLayouts: GPUVertexBufferLayout[] = []
|
|
64
79
|
for (const { buffer, location, stride, isInstance } of attribs) {
|
|
@@ -80,12 +95,7 @@ export const createVertexBuffers = (attribs: Iterable<AttribData & { isInstance?
|
|
|
80
95
|
return { vertexBuffers, bufferLayouts }
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
device: GPUDevice,
|
|
85
|
-
uniforms: Iterable<UniformData>,
|
|
86
|
-
textures: Iterable<TextureData>,
|
|
87
|
-
storages: Iterable<StorageData> = []
|
|
88
|
-
) => {
|
|
98
|
+
const createBindGroup = (device: GPUDevice, uniforms: IUniforms, textures: ITextures, storages: IStorages = []) => {
|
|
89
99
|
const groups = new Map<number, { layouts: GPUBindGroupLayoutEntry[]; bindings: GPUBindGroupEntry[] }>()
|
|
90
100
|
const ret = { bindGroups: [] as GPUBindGroup[], bindGroupLayouts: [] as GPUBindGroupLayout[] }
|
|
91
101
|
const add = (i: number, layout: GPUBindGroupLayoutEntry, binding: GPUBindGroupEntry) => {
|
|
@@ -111,14 +121,7 @@ export const createBindGroup = (
|
|
|
111
121
|
return ret
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
device: GPUDevice,
|
|
116
|
-
format: GPUTextureFormat,
|
|
117
|
-
bufferLayouts: GPUVertexBufferLayout[],
|
|
118
|
-
bindGroupLayouts: GPUBindGroupLayout[],
|
|
119
|
-
vs: string,
|
|
120
|
-
fs: string
|
|
121
|
-
) => {
|
|
124
|
+
const createPipeline = (device: GPUDevice, format: GPUTextureFormat, bufferLayouts: GPUVertexBufferLayout[], bindGroupLayouts: GPUBindGroupLayout[], vs: string, fs: string) => {
|
|
122
125
|
return device.createRenderPipeline({
|
|
123
126
|
vertex: {
|
|
124
127
|
module: device.createShaderModule({ label: 'vert', code: vs.trim() }),
|
|
@@ -140,7 +143,8 @@ export const createPipeline = (
|
|
|
140
143
|
})
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
const createComputePipeline = (device: GPUDevice, bindGroupLayouts: GPUBindGroupLayout[], cs: string) => {
|
|
147
|
+
if (!cs) return
|
|
144
148
|
return device.createComputePipeline({
|
|
145
149
|
compute: {
|
|
146
150
|
module: device.createShaderModule({ label: 'compute', code: cs.trim() }),
|
|
@@ -150,6 +154,14 @@ export const createComputePipeline = (device: GPUDevice, bindGroupLayouts: GPUBi
|
|
|
150
154
|
})
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
export const updatePipeline = (device: GPUDevice, format: GPUTextureFormat, attribs: IAttribs, uniforms: IUniforms, textures: ITextures, storages: IStorages, fs: string, cs: string, vs: string) => {
|
|
158
|
+
const { vertexBuffers, bufferLayouts } = createVertexBuffers(attribs)
|
|
159
|
+
const { bindGroups, bindGroupLayouts } = createBindGroup(device, uniforms, textures, storages)
|
|
160
|
+
const computePipeline = createComputePipeline(device, bindGroupLayouts, cs)
|
|
161
|
+
const graphicPipeline = createPipeline(device, format, bufferLayouts, bindGroupLayouts, vs, fs)
|
|
162
|
+
return { bindGroups, vertexBuffers, computePipeline, graphicPipeline }
|
|
163
|
+
}
|
|
164
|
+
|
|
153
165
|
/**
|
|
154
166
|
* buffers
|
|
155
167
|
*/
|
|
@@ -159,11 +171,7 @@ const bufferUsage = (type: 'uniform' | 'storage' | 'attrib') => {
|
|
|
159
171
|
return 140 // GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
|
|
160
172
|
}
|
|
161
173
|
|
|
162
|
-
export const
|
|
163
|
-
device: GPUDevice,
|
|
164
|
-
array: number[] | Float32Array,
|
|
165
|
-
type: 'uniform' | 'storage' | 'attrib'
|
|
166
|
-
) => {
|
|
174
|
+
export const createBuffer = (device: GPUDevice, array: number[] | Float32Array, type: 'uniform' | 'storage' | 'attrib') => {
|
|
167
175
|
if (!isFloat32(array)) array = new Float32Array(array)
|
|
168
176
|
const usage = bufferUsage(type)
|
|
169
177
|
const size = type === 'uniform' ? Math.ceil(array.byteLength / 256) * 256 : array.byteLength
|
|
@@ -171,22 +179,15 @@ export const createArrayBuffer = (
|
|
|
171
179
|
return { array, buffer }
|
|
172
180
|
}
|
|
173
181
|
|
|
182
|
+
export const updateBuffer = (device: GPUDevice, value: number[] | Float32Array, array: Float32Array, buffer: GPUBuffer) => {
|
|
183
|
+
array.set(value)
|
|
184
|
+
device.queue.writeBuffer(buffer, 0, array as GPUAllowSharedBufferSource)
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
export const createDescriptor = (c: GPUCanvasContext, depthTexture: GPUTexture) => {
|
|
175
188
|
return {
|
|
176
|
-
colorAttachments: [
|
|
177
|
-
|
|
178
|
-
view: c.getCurrentTexture().createView(),
|
|
179
|
-
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
180
|
-
loadOp: 'clear' as GPULoadOp,
|
|
181
|
-
storeOp: 'store' as GPUStoreOp,
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
depthStencilAttachment: {
|
|
185
|
-
view: depthTexture.createView(),
|
|
186
|
-
depthClearValue: 1.0,
|
|
187
|
-
depthLoadOp: 'clear' as GPULoadOp,
|
|
188
|
-
depthStoreOp: 'store' as GPUStoreOp,
|
|
189
|
-
},
|
|
189
|
+
colorAttachments: [{ view: c.getCurrentTexture().createView(), clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store' }],
|
|
190
|
+
depthStencilAttachment: { view: depthTexture.createView(), depthClearValue: 1.0, depthLoadOp: 'clear', depthStoreOp: 'store' },
|
|
190
191
|
} as GPURenderPassDescriptor
|
|
191
192
|
}
|
|
192
193
|
|
|
@@ -196,15 +197,11 @@ export const createDescriptor = (c: GPUCanvasContext, depthTexture: GPUTexture)
|
|
|
196
197
|
export const createTextureSampler = (device: GPUDevice, width = 1280, height = 800) => {
|
|
197
198
|
const texture = device.createTexture({ size: [width, height], format: 'rgba8unorm', usage: 22 }) // 22 is GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
198
199
|
const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' })
|
|
199
|
-
return { texture, sampler }
|
|
200
|
+
return { texture, sampler, view: texture.createView() }
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
export const createDepthTexture = (device: GPUDevice, width: number, height: number) => {
|
|
203
|
-
return device.createTexture({
|
|
204
|
-
size: [width, height],
|
|
205
|
-
format: 'depth24plus',
|
|
206
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
207
|
-
})
|
|
204
|
+
return device.createTexture({ size: [width, height], format: 'depth24plus', usage: 16 }) // 16 is GPUTextureUsage.RENDER_ATTACHMENT
|
|
208
205
|
}
|
|
209
206
|
|
|
210
207
|
/**
|