@footgun/cobalt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/bundle.js +284 -0
- package/cobalt2.jpeg +0 -0
- package/esbuild.js +20 -0
- package/examples/01-primitives/Game.js +8 -0
- package/examples/01-primitives/component-animation.js +8 -0
- package/examples/01-primitives/component-transform.js +13 -0
- package/examples/01-primitives/constants.js +6 -0
- package/examples/01-primitives/deps.js +2 -0
- package/examples/01-primitives/entity-sprite.js +47 -0
- package/examples/01-primitives/index.html +191 -0
- package/examples/01-primitives/system-renderer.js +37 -0
- package/examples/02-sprites/Game.js +8 -0
- package/examples/02-sprites/assets/spritesheet.json +6276 -0
- package/examples/02-sprites/assets/spritesheet.png +0 -0
- package/examples/02-sprites/assets/spritesheet_emissive.png +0 -0
- package/examples/02-sprites/component-animation.js +8 -0
- package/examples/02-sprites/component-transform.js +13 -0
- package/examples/02-sprites/constants.js +6 -0
- package/examples/02-sprites/deps.js +2 -0
- package/examples/02-sprites/entity-sprite.js +47 -0
- package/examples/02-sprites/index.html +310 -0
- package/examples/02-sprites/system-renderer.js +38 -0
- package/examples/03-tiles/Game.js +8 -0
- package/examples/03-tiles/assets/spelunky-tiles.png +0 -0
- package/examples/03-tiles/assets/spelunky0.png +0 -0
- package/examples/03-tiles/assets/spelunky1.png +0 -0
- package/examples/03-tiles/component-animation.js +8 -0
- package/examples/03-tiles/component-transform.js +13 -0
- package/examples/03-tiles/constants.js +6 -0
- package/examples/03-tiles/deps.js +2 -0
- package/examples/03-tiles/entity-sprite.js +47 -0
- package/examples/03-tiles/index.html +309 -0
- package/examples/03-tiles/system-renderer.js +38 -0
- package/examples/04-overlay/assets/spritesheet.json +22 -0
- package/examples/04-overlay/assets/spritesheet.png +0 -0
- package/examples/04-overlay/assets/spritesheet_emissive.png +0 -0
- package/examples/04-overlay/constants.js +6 -0
- package/examples/04-overlay/deps.js +1 -0
- package/examples/04-overlay/index.html +133 -0
- package/examples/05-bloom/Game.js +8 -0
- package/examples/05-bloom/assets/spritesheet.json +6276 -0
- package/examples/05-bloom/assets/spritesheet.png +0 -0
- package/examples/05-bloom/assets/spritesheet_emissive.png +0 -0
- package/examples/05-bloom/component-animation.js +8 -0
- package/examples/05-bloom/component-transform.js +13 -0
- package/examples/05-bloom/constants.js +6 -0
- package/examples/05-bloom/deps.js +2 -0
- package/examples/05-bloom/entity-sprite.js +47 -0
- package/examples/05-bloom/index.html +357 -0
- package/examples/05-bloom/system-renderer.js +38 -0
- package/examples/06-displacement/Game.js +8 -0
- package/examples/06-displacement/assets/displacement_map_repeat.jpg +0 -0
- package/examples/06-displacement/assets/spelunky-tiles.png +0 -0
- package/examples/06-displacement/assets/spelunky0.png +0 -0
- package/examples/06-displacement/assets/spelunky1.png +0 -0
- package/examples/06-displacement/component-animation.js +8 -0
- package/examples/06-displacement/component-transform.js +13 -0
- package/examples/06-displacement/constants.js +6 -0
- package/examples/06-displacement/deps.js +2 -0
- package/examples/06-displacement/entity-sprite.js +47 -0
- package/examples/06-displacement/index.html +350 -0
- package/examples/06-displacement/system-renderer.js +38 -0
- package/examples/07-sdl/assets/spritesheet.json +22 -0
- package/examples/07-sdl/assets/spritesheet.png +0 -0
- package/examples/07-sdl/assets/spritesheet_emissive.png +0 -0
- package/examples/07-sdl/main.js +109 -0
- package/examples/07-sdl/package.json +19 -0
- package/examples/08-light/Game.js +8 -0
- package/examples/08-light/assets/spelunky-tiles.png +0 -0
- package/examples/08-light/assets/spelunky0.png +0 -0
- package/examples/08-light/assets/spelunky1.png +0 -0
- package/examples/08-light/constants.js +6 -0
- package/examples/08-light/deps.js +2 -0
- package/examples/08-light/index.html +477 -0
- package/package.json +34 -0
- package/src/bloom/bloom.js +467 -0
- package/src/bloom/bloom.wgsl +176 -0
- package/src/cobalt.js +231 -0
- package/src/create-texture-from-buffer.js +39 -0
- package/src/create-texture-from-url.js +35 -0
- package/src/create-texture.js +46 -0
- package/src/deps.js +3 -0
- package/src/displacement/composition.wgsl +58 -0
- package/src/displacement/displacement-composition.ts +161 -0
- package/src/displacement/displacement-parameters-buffer.ts +44 -0
- package/src/displacement/displacement-texture.ts +221 -0
- package/src/displacement/displacement.js +160 -0
- package/src/displacement/displacement.wgsl +31 -0
- package/src/displacement/triangles-buffer.ts +95 -0
- package/src/fb-blit/fb-blit.js +161 -0
- package/src/fb-blit/fb-blit.wgsl +40 -0
- package/src/fb-texture/fb-texture.js +56 -0
- package/src/light/README.md +61 -0
- package/src/light/light.js +148 -0
- package/src/light/lights-buffer.ts +98 -0
- package/src/light/lights-renderer.ts +278 -0
- package/src/light/public-api.js +20 -0
- package/src/light/readme/01_illumination.webp +0 -0
- package/src/light/readme/02_lights_texture.webp +0 -0
- package/src/light/readme/03_lights_texture_decomposed.webp +0 -0
- package/src/light/readme/04_lights_texture_mask.webp +0 -0
- package/src/light/readme/05_lights_obstacle_decomposition.webp +0 -0
- package/src/light/readme/06_lights_hard_cast_shadows.webp +0 -0
- package/src/light/texture/lights-texture-initializer.ts +191 -0
- package/src/light/texture/lights-texture-mask.ts +286 -0
- package/src/light/texture/lights-texture.ts +121 -0
- package/src/light/types.ts +23 -0
- package/src/light/viewport.ts +63 -0
- package/src/overlay/constants.js +1 -0
- package/src/overlay/overlay.js +341 -0
- package/src/overlay/overlay.wgsl +88 -0
- package/src/primitives/constants.js +1 -0
- package/src/primitives/primitives.js +252 -0
- package/src/primitives/primitives.wgsl +54 -0
- package/src/primitives/public-api.js +325 -0
- package/src/scene-composite/scene-composite.js +168 -0
- package/src/scene-composite/scene-composite.wgsl +94 -0
- package/src/sprite/constants.js +1 -0
- package/src/sprite/create-sprite-quads.js +60 -0
- package/src/sprite/public-api.js +215 -0
- package/src/sprite/read-spritesheet.js +103 -0
- package/src/sprite/sorted-binary-insert.js +45 -0
- package/src/sprite/sprite.js +268 -0
- package/src/sprite/sprite.wgsl +103 -0
- package/src/sprite/spritesheet.js +212 -0
- package/src/tile/atlas.js +193 -0
- package/src/tile/tile.js +171 -0
- package/src/tile/tile.wgsl +105 -0
- package/src/uuid.js +3 -0
package/src/cobalt.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
export { default as createTexture } from './create-texture.js'
|
|
2
|
+
export { default as createTextureFromUrl } from './create-texture-from-url.js'
|
|
3
|
+
export { default as createTextureFromBuffer } from './create-texture-from-buffer.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// built-in run nodes
|
|
7
|
+
import bloomNode from './bloom/bloom.js'
|
|
8
|
+
import compositeNode from './scene-composite/scene-composite.js'
|
|
9
|
+
import spriteNode from './sprite/sprite.js'
|
|
10
|
+
import tileNode from './tile/tile.js'
|
|
11
|
+
import displacementNode from './displacement/displacement.js'
|
|
12
|
+
import overlayNode from './overlay/overlay.js'
|
|
13
|
+
import fbBlitNode from './fb-blit/fb-blit.js'
|
|
14
|
+
import primitivesNode from './primitives/primitives.js'
|
|
15
|
+
import lightNode from './light/light.js'
|
|
16
|
+
|
|
17
|
+
// built-in resource nodes
|
|
18
|
+
import tileAtlasNode from './tile/atlas.js'
|
|
19
|
+
import spritesheetNode from './sprite/spritesheet.js'
|
|
20
|
+
import fbTextureNode from './fb-texture/fb-texture.js'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// create and initialize a WebGPU renderer for a given canvas
|
|
24
|
+
// returns the data structure containing all WebGPU related stuff
|
|
25
|
+
export async function init (ctx, viewportWidth, viewportHeight) {
|
|
26
|
+
|
|
27
|
+
let device, gpu, context, canvas
|
|
28
|
+
|
|
29
|
+
// determine if an sdl/gpu context was passed, or if this is a browser canvas
|
|
30
|
+
if (ctx.sdlWindow && ctx.gpu) {
|
|
31
|
+
// this is an sdl/gpu context
|
|
32
|
+
gpu = ctx.gpu
|
|
33
|
+
|
|
34
|
+
const instance = gpu.create([ 'verbose=1' ])
|
|
35
|
+
const adapter = await instance.requestAdapter()
|
|
36
|
+
device = await adapter.requestDevice()
|
|
37
|
+
context = gpu.renderGPUDeviceToWindow({ device, window: ctx.sdlWindow })
|
|
38
|
+
|
|
39
|
+
// gpu module doesn't expose these globals to node namespace so manually wire them up
|
|
40
|
+
global.GPUBufferUsage = gpu.GPUBufferUsage
|
|
41
|
+
global.GPUShaderStage = gpu.GPUShaderStage
|
|
42
|
+
global.GPUTextureUsage = gpu.GPUTextureUsage
|
|
43
|
+
|
|
44
|
+
} else {
|
|
45
|
+
// ctx is a canvas element
|
|
46
|
+
canvas = ctx
|
|
47
|
+
|
|
48
|
+
const adapter = await navigator.gpu?.requestAdapter({ powerPreference: 'high-performance' })
|
|
49
|
+
|
|
50
|
+
device = await adapter?.requestDevice()
|
|
51
|
+
gpu = navigator.gpu
|
|
52
|
+
|
|
53
|
+
context = canvas.getContext('webgpu')
|
|
54
|
+
|
|
55
|
+
context.configure({
|
|
56
|
+
device,
|
|
57
|
+
format: navigator.gpu?.getPreferredCanvasFormat(), // bgra8unorm
|
|
58
|
+
alphaMode: 'opaque'
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// all available node defintions the renderer knows about are defined here.
|
|
63
|
+
// the cobalt:* nodes are built in. New nodes can be defined at run time with Cobalt.defineNode(...)
|
|
64
|
+
const nodeDefs = {
|
|
65
|
+
// built in resource nodes
|
|
66
|
+
'cobalt:tileAtlas': tileAtlasNode,
|
|
67
|
+
'cobalt:spritesheet': spritesheetNode,
|
|
68
|
+
'cobalt:fbTexture': fbTextureNode,
|
|
69
|
+
|
|
70
|
+
// builtin run nodes
|
|
71
|
+
'cobalt:bloom': bloomNode,
|
|
72
|
+
'cobalt:composite': compositeNode,
|
|
73
|
+
'cobalt:sprite': spriteNode,
|
|
74
|
+
'cobalt:tile': tileNode,
|
|
75
|
+
'cobalt:displacement': displacementNode,
|
|
76
|
+
'cobalt:overlay': overlayNode,
|
|
77
|
+
'cobalt:fbBlit': fbBlitNode,
|
|
78
|
+
'cobalt:primitives': primitivesNode,
|
|
79
|
+
'cobalt:light': lightNode,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
nodeDefs,
|
|
84
|
+
// runnable nodes. ordering dictates render order (first to last)
|
|
85
|
+
nodes: [ ],
|
|
86
|
+
|
|
87
|
+
// keeps references to all node refs that need to access the per-frame default texture view
|
|
88
|
+
// these refs are updated on each invocation of Cobalt.draw(...)
|
|
89
|
+
defaultTextureViewRefs: [ ],
|
|
90
|
+
|
|
91
|
+
canvas,
|
|
92
|
+
device,
|
|
93
|
+
context,
|
|
94
|
+
gpu,
|
|
95
|
+
|
|
96
|
+
// used in the color attachments of renderpass
|
|
97
|
+
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
|
|
98
|
+
|
|
99
|
+
viewport: {
|
|
100
|
+
width: viewportWidth,
|
|
101
|
+
height: viewportHeight,
|
|
102
|
+
zoom: 1.0,
|
|
103
|
+
position: [ 0, 0 ], // top-left corner of the viewport
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export function defineNode (c, nodeDefinition) {
|
|
110
|
+
if (!nodeDefinition?.type)
|
|
111
|
+
throw new Error(`Can't define a new node missing a type.`)
|
|
112
|
+
|
|
113
|
+
c.nodeDefs[nodeDefinition.type] = nodeDefinition
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
export async function initNode (c, nodeData) {
|
|
118
|
+
const nodeDef = c.nodeDefs[nodeData?.type]
|
|
119
|
+
|
|
120
|
+
if (!nodeDef)
|
|
121
|
+
throw new Error(`Can't initialize a new node missing a type.`)
|
|
122
|
+
|
|
123
|
+
const node = {
|
|
124
|
+
type: nodeData.type,
|
|
125
|
+
refs: nodeData.refs || { },
|
|
126
|
+
options: nodeData.options || { },
|
|
127
|
+
data: { },
|
|
128
|
+
enabled: true, // when disabled, the node won't be run
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const refName in node.refs) {
|
|
132
|
+
if (node.refs[refName] === 'FRAME_TEXTURE_VIEW') {
|
|
133
|
+
c.defaultTextureViewRefs.push({ node, refName })
|
|
134
|
+
node.refs[refName] = getCurrentTextureView(c)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
node.data = await nodeDef.onInit(c, node)
|
|
139
|
+
|
|
140
|
+
// copy in all custom functions, and ensure the first parameter is the node itself
|
|
141
|
+
const customFunctions = nodeDef.customFunctions || { }
|
|
142
|
+
for (const fnName in customFunctions) {
|
|
143
|
+
node[fnName] = function (...args) {
|
|
144
|
+
return customFunctions[fnName](c, node, ...args)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
c.nodes.push(node)
|
|
149
|
+
return node
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
export function draw (c) {
|
|
154
|
+
const { device, context } = c
|
|
155
|
+
|
|
156
|
+
const commandEncoder = device.createCommandEncoder()
|
|
157
|
+
|
|
158
|
+
const v = getCurrentTextureView(c)
|
|
159
|
+
|
|
160
|
+
// some nodes may need a reference to the default texture view (the frame backing)
|
|
161
|
+
// this is generated each draw frame so we need to update the references
|
|
162
|
+
for (const r of c.defaultTextureViewRefs)
|
|
163
|
+
r.node.refs[r.refName] = v
|
|
164
|
+
|
|
165
|
+
// run all of the enabled nodes
|
|
166
|
+
for (const node of c.nodes) {
|
|
167
|
+
if (!node.enabled)
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
const nodeDef = c.nodeDefs[node.type]
|
|
171
|
+
nodeDef.onRun(c, node, commandEncoder)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
device.queue.submit([ commandEncoder.finish() ])
|
|
175
|
+
|
|
176
|
+
// for sdl + gpu setups, we need to do this swap() step
|
|
177
|
+
if (!c.canvas)
|
|
178
|
+
c.context.swap()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// clean up all the loaded data so we could re-load a level, etc.
|
|
183
|
+
export function reset (c) {
|
|
184
|
+
for (const n of c.nodes) {
|
|
185
|
+
const nodeDef = c.nodeDefs[n.type]
|
|
186
|
+
nodeDef.onDestroy(c, n)
|
|
187
|
+
}
|
|
188
|
+
c.nodes.length = 0
|
|
189
|
+
c.defaultTextureViewRefs.length = 0
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
export function setViewportDimensions (c, width, height) {
|
|
194
|
+
c.viewport.width = width
|
|
195
|
+
c.viewport.height = height
|
|
196
|
+
|
|
197
|
+
for (const n of c.nodes) {
|
|
198
|
+
const nodeDef = c.nodeDefs[n.type]
|
|
199
|
+
nodeDef.onResize(c, n)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
// @param Array pos 2D point the viewport is centered on
|
|
205
|
+
export function setViewportPosition (c, pos) {
|
|
206
|
+
c.viewport.position[0] = pos[0] - (c.viewport.width / 2 / c.viewport.zoom)
|
|
207
|
+
c.viewport.position[1] = pos[1] - (c.viewport.height / 2 / c.viewport.zoom)
|
|
208
|
+
|
|
209
|
+
for (const n of c.nodes) {
|
|
210
|
+
const nodeDef = c.nodeDefs[n.type]
|
|
211
|
+
nodeDef.onViewportPosition(c, n)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
export function getPreferredFormat (cobalt) {
|
|
217
|
+
if (cobalt.canvas)
|
|
218
|
+
return navigator.gpu.getPreferredCanvasFormat()
|
|
219
|
+
else
|
|
220
|
+
return cobalt.context.getPreferredFormat()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
export function getCurrentTextureView (cobalt) {
|
|
225
|
+
if (cobalt.canvas)
|
|
226
|
+
return cobalt.context.getCurrentTexture().createView()
|
|
227
|
+
else {
|
|
228
|
+
//return cobalt.context.getCurrentTexture().createView()
|
|
229
|
+
return cobalt.context.getCurrentTextureView()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import createTexture from './create-texture.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export default function createTextureFromBuffer (c, label, image, format='rgba8unorm') {
|
|
5
|
+
|
|
6
|
+
const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
7
|
+
const mip_count = 1
|
|
8
|
+
const t = createTexture(c.device, label, image.width, image.height, mip_count, format, usage)
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
c.device.queue.copyExternalImageToTexture(
|
|
12
|
+
{ source: image },
|
|
13
|
+
{ texture: t.texture },
|
|
14
|
+
{
|
|
15
|
+
width: image.width,
|
|
16
|
+
height: image.height
|
|
17
|
+
}
|
|
18
|
+
)*/
|
|
19
|
+
|
|
20
|
+
c.device.queue.writeTexture(
|
|
21
|
+
{ texture: t.texture },
|
|
22
|
+
image.data,
|
|
23
|
+
{ bytesPerRow: 4 * image.width },
|
|
24
|
+
{ width: image.width, height: image.height }
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// nearest neighbor filtering is good for da pixel art
|
|
28
|
+
const samplerDescriptor = {
|
|
29
|
+
addressModeU: 'repeat', // repeat | clamp-to-edge
|
|
30
|
+
addressModeV: 'repeat', // repeat | clamp-to-edge
|
|
31
|
+
magFilter: 'nearest',
|
|
32
|
+
minFilter: 'nearest',
|
|
33
|
+
mipmapFilter: 'nearest',
|
|
34
|
+
maxAnisotropy: 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
t.sampler = c.device.createSampler(samplerDescriptor)
|
|
38
|
+
return t
|
|
39
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import createTexture from './create-texture.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export default async function createTextureFromUrl (c, label, url, format='rgba8unorm') {
|
|
5
|
+
const response = await fetch(url)
|
|
6
|
+
const blob = await response.blob()
|
|
7
|
+
|
|
8
|
+
const imageData = await createImageBitmap(blob/*, { premultiplyAlpha: 'none', resizeQuality: 'pixelated' }*/)
|
|
9
|
+
|
|
10
|
+
const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
11
|
+
const mip_count = 1
|
|
12
|
+
const t = createTexture(c.device, label, imageData.width, imageData.height, mip_count, format, usage)
|
|
13
|
+
|
|
14
|
+
c.device.queue.copyExternalImageToTexture(
|
|
15
|
+
{ source: imageData },
|
|
16
|
+
{ texture: t.texture },
|
|
17
|
+
{
|
|
18
|
+
width: imageData.width,
|
|
19
|
+
height: imageData.height
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// nearest neighbor filtering is good for da pixel art
|
|
24
|
+
const samplerDescriptor = {
|
|
25
|
+
addressModeU: 'repeat', // repeat | clamp-to-edge
|
|
26
|
+
addressModeV: 'repeat', // repeat | clamp-to-edge
|
|
27
|
+
magFilter: 'nearest',
|
|
28
|
+
minFilter: 'nearest',
|
|
29
|
+
mipmapFilter: 'nearest',
|
|
30
|
+
maxAnisotropy: 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
t.sampler = c.device.createSampler(samplerDescriptor)
|
|
34
|
+
return t
|
|
35
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default function createTexture (device, label, width, height, mip_count, format, usage) {
|
|
2
|
+
|
|
3
|
+
const texture = device.createTexture({
|
|
4
|
+
label,
|
|
5
|
+
size: { width, height },
|
|
6
|
+
format,
|
|
7
|
+
usage,
|
|
8
|
+
mipLevelCount: mip_count,
|
|
9
|
+
sampleCount: 1,
|
|
10
|
+
dimension: '2d',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const view = texture.createView()
|
|
14
|
+
|
|
15
|
+
const mip_view = [ ]
|
|
16
|
+
|
|
17
|
+
for (let i=0; i < mip_count; i++)
|
|
18
|
+
mip_view.push(texture.createView({
|
|
19
|
+
label,
|
|
20
|
+
format,
|
|
21
|
+
dimension: '2d',
|
|
22
|
+
aspect: 'all',
|
|
23
|
+
baseMipLevel: i,
|
|
24
|
+
mipLevelCount: 1,
|
|
25
|
+
baseArrayLayer: 0,
|
|
26
|
+
arrayLayerCount: 1,
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
const sampler = device.createSampler({
|
|
30
|
+
label: `${label} sampler`,
|
|
31
|
+
addressModeU: 'clamp-to-edge',
|
|
32
|
+
addressModeV: 'clamp-to-edge',
|
|
33
|
+
addressModeW: 'clamp-to-edge',
|
|
34
|
+
magFilter: 'linear',
|
|
35
|
+
minFilter: 'linear',
|
|
36
|
+
mipmapFilter: 'linear',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
size: { width, height },
|
|
41
|
+
texture,
|
|
42
|
+
view,
|
|
43
|
+
mip_view,
|
|
44
|
+
sampler,
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/deps.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { default as removeArrayItems } from 'https://cdn.jsdelivr.net/gh/mreinstein/remove-array-items/src/remove-array-items.js'
|
|
2
|
+
export { default as round } from 'https://cdn.skypack.dev/pin/round-half-up-symmetric@v2.0.0-pfMZ4UGGs9FcqO8UiEHO/mode=imports,min/optimized/round-half-up-symmetric.js'
|
|
3
|
+
export { mat4, vec2, vec3, vec4 } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
struct DisplacementParameters { // align(16) size(16)
|
|
2
|
+
offset: vec2<f32>, // offset(0) align(8) size(8)
|
|
3
|
+
scale: f32, // offset(8) align(4) size(4)
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
@group(0) @binding(0) var<uniform> uniforms: DisplacementParameters;
|
|
7
|
+
@group(0) @binding(1) var colorTexture: texture_2d<f32>;
|
|
8
|
+
@group(0) @binding(2) var colorSampler: sampler;
|
|
9
|
+
@group(0) @binding(3) var noiseTexture: texture_2d<f32>;
|
|
10
|
+
@group(0) @binding(4) var noiseSampler: sampler;
|
|
11
|
+
@group(0) @binding(5) var displacementTexture: texture_2d<f32>;
|
|
12
|
+
|
|
13
|
+
struct VertexIn {
|
|
14
|
+
@builtin(vertex_index) vertexIndex: u32,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
struct VertexOut {
|
|
18
|
+
@builtin(position) position: vec4<f32>,
|
|
19
|
+
@location(0) uv: vec2<f32>,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
@vertex
|
|
23
|
+
fn main_vertex(in: VertexIn) -> VertexOut {
|
|
24
|
+
const corners = array<vec2<f32>, 4>(
|
|
25
|
+
vec2<f32>(-1, -1),
|
|
26
|
+
vec2<f32>(1, -1),
|
|
27
|
+
vec2<f32>(-1, 1),
|
|
28
|
+
vec2<f32>(1, 1),
|
|
29
|
+
);
|
|
30
|
+
let screenPosition = corners[in.vertexIndex];
|
|
31
|
+
|
|
32
|
+
var out: VertexOut;
|
|
33
|
+
out.position = vec4<f32>(screenPosition, 0, 1);
|
|
34
|
+
out.uv = (0.5 + 0.5 * screenPosition * vec2<f32>(1, -1));
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
struct FragmentOut {
|
|
39
|
+
@location(0) color: vec4<f32>,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
@fragment
|
|
43
|
+
fn main_fragment(in: VertexOut) -> FragmentOut {
|
|
44
|
+
let noiseTextureDimensions = vec2<f32>(textureDimensions(noiseTexture, 0));
|
|
45
|
+
let noiseUv = in.uv + uniforms.offset / noiseTextureDimensions;
|
|
46
|
+
var noise = textureSample(noiseTexture, noiseSampler, noiseUv).rg;
|
|
47
|
+
noise -= 0.5;
|
|
48
|
+
noise *= uniforms.scale / noiseTextureDimensions;
|
|
49
|
+
|
|
50
|
+
let displacement = textureSample(displacementTexture, colorSampler, in.uv).r;
|
|
51
|
+
noise *= displacement;
|
|
52
|
+
|
|
53
|
+
let colorUv = in.uv + noise;
|
|
54
|
+
|
|
55
|
+
var out: FragmentOut;
|
|
56
|
+
out.color = textureSample(colorTexture, colorSampler, colorUv);
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types"/>
|
|
2
|
+
|
|
3
|
+
import { DisplacementParametersBuffer } from "./displacement-parameters-buffer";
|
|
4
|
+
import compositionWGSL from "./composition.wgsl"
|
|
5
|
+
|
|
6
|
+
type Parameters = {
|
|
7
|
+
readonly device: GPUDevice;
|
|
8
|
+
readonly targetFormat: GPUTextureFormat;
|
|
9
|
+
|
|
10
|
+
readonly colorTextureView: GPUTextureView;
|
|
11
|
+
readonly noiseMapTextureView: GPUTextureView;
|
|
12
|
+
readonly displacementTextureView: GPUTextureView;
|
|
13
|
+
|
|
14
|
+
readonly displacementParametersBuffer: DisplacementParametersBuffer;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class DisplacementComposition {
|
|
18
|
+
private readonly device: GPUDevice;
|
|
19
|
+
private readonly targetFormat: GPUTextureFormat;
|
|
20
|
+
private readonly renderPipeline: GPURenderPipeline;
|
|
21
|
+
|
|
22
|
+
private readonly colorSampler: GPUSampler;
|
|
23
|
+
private readonly noiseSampler: GPUSampler;
|
|
24
|
+
|
|
25
|
+
private readonly displacementParametersBuffer: DisplacementParametersBuffer;
|
|
26
|
+
|
|
27
|
+
private renderBundle: GPURenderBundle | null = null;
|
|
28
|
+
|
|
29
|
+
private colorTextureView: GPUTextureView;
|
|
30
|
+
private noiseMapTextureView: GPUTextureView;
|
|
31
|
+
private displacementTextureView: GPUTextureView;
|
|
32
|
+
|
|
33
|
+
public constructor(params: Parameters) {
|
|
34
|
+
this.device = params.device;
|
|
35
|
+
|
|
36
|
+
this.targetFormat = params.targetFormat;
|
|
37
|
+
this.colorTextureView = params.colorTextureView;
|
|
38
|
+
this.noiseMapTextureView = params.noiseMapTextureView;
|
|
39
|
+
this.displacementTextureView = params.displacementTextureView;
|
|
40
|
+
|
|
41
|
+
this.displacementParametersBuffer = params.displacementParametersBuffer;
|
|
42
|
+
|
|
43
|
+
const shaderModule = this.device.createShaderModule({
|
|
44
|
+
label: "DisplacementComposition shader module",
|
|
45
|
+
code: compositionWGSL,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.renderPipeline = this.device.createRenderPipeline({
|
|
49
|
+
label: "DisplacementComposition renderpipeline",
|
|
50
|
+
layout: "auto",
|
|
51
|
+
vertex: {
|
|
52
|
+
module: shaderModule,
|
|
53
|
+
entryPoint: "main_vertex",
|
|
54
|
+
},
|
|
55
|
+
fragment: {
|
|
56
|
+
module: shaderModule,
|
|
57
|
+
entryPoint: "main_fragment",
|
|
58
|
+
targets: [{
|
|
59
|
+
format: params.targetFormat,
|
|
60
|
+
}],
|
|
61
|
+
},
|
|
62
|
+
primitive: {
|
|
63
|
+
cullMode: "none",
|
|
64
|
+
topology: "triangle-strip",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.noiseSampler = this.device.createSampler({
|
|
69
|
+
label: "DisplacementComposition noisesampler",
|
|
70
|
+
addressModeU: "repeat",
|
|
71
|
+
addressModeV: "repeat",
|
|
72
|
+
addressModeW: "repeat",
|
|
73
|
+
magFilter: "linear",
|
|
74
|
+
minFilter: "linear",
|
|
75
|
+
mipmapFilter: "linear",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.colorSampler = this.device.createSampler({
|
|
79
|
+
label: "DisplacementComposition colorSampler",
|
|
80
|
+
addressModeU: "clamp-to-edge",
|
|
81
|
+
addressModeV: "clamp-to-edge",
|
|
82
|
+
addressModeW: "clamp-to-edge",
|
|
83
|
+
magFilter: "linear",
|
|
84
|
+
minFilter: "linear",
|
|
85
|
+
mipmapFilter: "linear",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public getRenderBundle(): GPURenderBundle {
|
|
90
|
+
if (!this.renderBundle) {
|
|
91
|
+
this.renderBundle = this.buildRenderBundle();
|
|
92
|
+
}
|
|
93
|
+
return this.renderBundle;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public destroy(): void {
|
|
97
|
+
// nothing to do
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public setColorTextureView(textureView: GPUTextureView): void {
|
|
101
|
+
this.colorTextureView = textureView;
|
|
102
|
+
this.renderBundle = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public setNoiseMapTextureView(textureView: GPUTextureView): void {
|
|
106
|
+
this.noiseMapTextureView = textureView;
|
|
107
|
+
this.renderBundle = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public setDisplacementTextureView(textureView: GPUTextureView): void {
|
|
111
|
+
this.displacementTextureView = textureView;
|
|
112
|
+
this.renderBundle = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private buildRenderBundle(): GPURenderBundle {
|
|
116
|
+
const bindgroup = this.device.createBindGroup({
|
|
117
|
+
label: "DisplacementComposition bindgroup 0",
|
|
118
|
+
layout: this.renderPipeline.getBindGroupLayout(0),
|
|
119
|
+
entries: [
|
|
120
|
+
{
|
|
121
|
+
binding: 0,
|
|
122
|
+
resource: { buffer: this.displacementParametersBuffer.bufferGpu },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
binding: 1,
|
|
126
|
+
resource: this.colorTextureView,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
binding: 2,
|
|
130
|
+
resource: this.colorSampler,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
binding: 3,
|
|
134
|
+
resource: this.noiseMapTextureView,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
binding: 4,
|
|
138
|
+
resource: this.noiseSampler,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
binding: 5,
|
|
142
|
+
resource: this.displacementTextureView,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const renderBundleEncoder = this.device.createRenderBundleEncoder({
|
|
148
|
+
label: "DisplacementComposition renderbundle encoder",
|
|
149
|
+
colorFormats: [this.targetFormat],
|
|
150
|
+
});
|
|
151
|
+
renderBundleEncoder.setPipeline(this.renderPipeline);
|
|
152
|
+
renderBundleEncoder.setBindGroup(0, bindgroup);
|
|
153
|
+
renderBundleEncoder.draw(4);
|
|
154
|
+
return renderBundleEncoder.finish({ label: "DisplacementComposition renderbundle" });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
DisplacementComposition
|
|
160
|
+
};
|
|
161
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types"/>
|
|
2
|
+
|
|
3
|
+
type DisplacementParameters = {
|
|
4
|
+
readonly offsetX: number;
|
|
5
|
+
readonly offsetY: number;
|
|
6
|
+
readonly scale: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Parameters = {
|
|
10
|
+
readonly device: GPUDevice;
|
|
11
|
+
readonly initialParameters: DisplacementParameters;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
class DisplacementParametersBuffer {
|
|
15
|
+
private readonly device: GPUDevice;
|
|
16
|
+
|
|
17
|
+
public readonly bufferGpu: GPUBuffer;
|
|
18
|
+
private needsUpdate: boolean = true;
|
|
19
|
+
|
|
20
|
+
public constructor(params: Parameters) {
|
|
21
|
+
this.device = params.device;
|
|
22
|
+
|
|
23
|
+
this.bufferGpu = this.device.createBuffer({
|
|
24
|
+
label: "DisplacementParametersBuffer buffer",
|
|
25
|
+
size: 16,
|
|
26
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.setParameters(params.initialParameters);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public setParameters(params: DisplacementParameters): void {
|
|
33
|
+
this.device.queue.writeBuffer(this.bufferGpu, 0, new Float32Array([params.offsetX, params.offsetY, params.scale]));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public destroy(): void {
|
|
37
|
+
this.bufferGpu.destroy();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
DisplacementParametersBuffer
|
|
43
|
+
};
|
|
44
|
+
|