@footgun/cobalt 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cobalt.js CHANGED
@@ -1,40 +1,36 @@
1
- export { default as createTexture } from './create-texture.js'
2
- export { default as createTextureFromUrl } from './create-texture-from-url.js'
1
+ export { default as createTexture } from './create-texture.js'
3
2
  export { default as createTextureFromBuffer } from './create-texture-from-buffer.js'
4
-
3
+ export { default as createTextureFromUrl } from './create-texture-from-url.js'
5
4
 
6
5
  // built-in run nodes
7
- import bloomNode from './bloom/bloom.js'
8
- import compositeNode from './scene-composite/scene-composite.js'
9
- import spriteHDRNode from './sprite-hdr/sprite.js'
10
- import tileHDRNode from './tile-hdr/tile.js'
6
+ import bloomNode from './bloom/bloom.js'
11
7
  import displacementNode from './displacement/displacement.js'
12
- import fbBlitNode from './fb-blit/fb-blit.js'
13
- import primitivesNode from './primitives/primitives.js'
14
- import lightNode from './light/light.js'
15
- import spriteNode from './sprite/sprite.js'
16
-
8
+ import fbBlitNode from './fb-blit/fb-blit.js'
9
+ import fbTextureNode from './fb-texture/fb-texture.js'
10
+ import lightNode from './light/light.js'
11
+ import primitivesNode from './primitives/primitives.js'
12
+ import compositeNode from './scene-composite/scene-composite.js'
13
+ import spriteNode from './sprite/sprite.js'
14
+ import spriteHDRNode from './sprite-hdr/sprite.js'
15
+ import spritesheetNode from './spritesheet/spritesheet.js'
17
16
  // built-in resource nodes
18
- import tileAtlasNode from './tile-hdr/atlas.js'
19
- import spritesheetNode from './spritesheet/spritesheet.js'
20
- import fbTextureNode from './fb-texture/fb-texture.js'
21
-
17
+ import tileAtlasNode from './tile-hdr/atlas.js'
18
+ import tileHDRNode from './tile-hdr/tile.js'
22
19
 
23
20
  // create and initialize a WebGPU renderer for a given canvas
24
21
  // returns the data structure containing all WebGPU related stuff
25
- export async function init (ctx, viewportWidth, viewportHeight) {
26
-
22
+ export async function init(ctx, viewportWidth, viewportHeight) {
27
23
  let device, gpu, context, canvas
28
24
 
29
25
  // determine if an sdl/gpu context was passed, or if this is a browser canvas
30
26
  if (ctx.sdlWindow && ctx.gpu) {
31
27
  // this is an sdl/gpu context
32
28
  gpu = ctx.gpu
33
-
34
- const instance = gpu.create([ 'verbose=1', 'enable-dawn-features=allow_unsafe_apis' ])
29
+
30
+ const instance = gpu.create(['verbose=1', 'enable-dawn-features=allow_unsafe_apis'])
35
31
  const adapter = await instance.requestAdapter()
36
32
  device = await adapter.requestDevice({
37
- requiredFeatures: [ 'texture-component-swizzle' ],
33
+ requiredFeatures: ['texture-component-swizzle'],
38
34
  })
39
35
  context = gpu.renderGPUDeviceToWindow({ device, window: ctx.sdlWindow })
40
36
 
@@ -42,7 +38,6 @@ export async function init (ctx, viewportWidth, viewportHeight) {
42
38
  global.GPUBufferUsage = gpu.GPUBufferUsage
43
39
  global.GPUShaderStage = gpu.GPUShaderStage
44
40
  global.GPUTextureUsage = gpu.GPUTextureUsage
45
-
46
41
  } else {
47
42
  // ctx is a canvas element
48
43
  canvas = ctx
@@ -57,7 +52,7 @@ export async function init (ctx, viewportWidth, viewportHeight) {
57
52
  context.configure({
58
53
  device,
59
54
  format: navigator.gpu?.getPreferredCanvasFormat(), // bgra8unorm
60
- alphaMode: 'opaque'
55
+ alphaMode: 'opaque',
61
56
  })
62
57
  }
63
58
 
@@ -84,15 +79,15 @@ export async function init (ctx, viewportWidth, viewportHeight) {
84
79
  return {
85
80
  nodeDefs,
86
81
  // runnable nodes. ordering dictates render order (first to last)
87
- nodes: [ ],
82
+ nodes: [],
88
83
 
89
84
  // keeps references to all node refs that need to access the per-frame default texture view
90
85
  // these refs are updated on each invocation of Cobalt.draw(...)
91
- defaultTextureViewRefs: [ ],
86
+ defaultTextureViewRefs: [],
92
87
 
93
- canvas,
94
- device,
95
- context,
88
+ canvas,
89
+ device,
90
+ context,
96
91
  gpu,
97
92
 
98
93
  // used in the color attachments of renderpass
@@ -102,31 +97,27 @@ export async function init (ctx, viewportWidth, viewportHeight) {
102
97
  width: viewportWidth,
103
98
  height: viewportHeight,
104
99
  zoom: 1.0,
105
- position: [ 0, 0 ], // top-left corner of the viewport
100
+ position: [0, 0], // top-left corner of the viewport
106
101
  },
107
102
  }
108
103
  }
109
104
 
110
-
111
- export function defineNode (c, nodeDefinition) {
112
- if (!nodeDefinition?.type)
113
- throw new Error(`Can't define a new node missing a type.`)
105
+ export function defineNode(c, nodeDefinition) {
106
+ if (!nodeDefinition?.type) throw new Error(`Can't define a new node missing a type.`)
114
107
 
115
108
  c.nodeDefs[nodeDefinition.type] = nodeDefinition
116
109
  }
117
110
 
118
-
119
- export async function initNode (c, nodeData) {
111
+ export async function initNode(c, nodeData) {
120
112
  const nodeDef = c.nodeDefs[nodeData?.type]
121
113
 
122
- if (!nodeDef)
123
- throw new Error(`Can't initialize a new node missing a type.`)
114
+ if (!nodeDef) throw new Error(`Can't initialize a new node missing a type.`)
124
115
 
125
116
  const node = {
126
117
  type: nodeData.type,
127
- refs: nodeData.refs || { },
128
- options: nodeData.options || { },
129
- data: { },
118
+ refs: nodeData.refs || {},
119
+ options: nodeData.options || {},
120
+ data: {},
130
121
  enabled: true, // when disabled, the node won't be run
131
122
  }
132
123
 
@@ -139,8 +130,8 @@ export async function initNode (c, nodeData) {
139
130
 
140
131
  node.data = await nodeDef.onInit(c, node)
141
132
 
142
- // copy in all custom functions, and ensure the first parameter is the node itself
143
- const customFunctions = nodeDef.customFunctions || { }
133
+ // copy in all custom functions, and ensure the first parameter is the node itself
134
+ const customFunctions = nodeDef.customFunctions || {}
144
135
  for (const fnName in customFunctions) {
145
136
  node[fnName] = function (...args) {
146
137
  return customFunctions[fnName](c, node, ...args)
@@ -151,9 +142,8 @@ export async function initNode (c, nodeData) {
151
142
  return node
152
143
  }
153
144
 
154
-
155
- export function draw (c) {
156
- const { device, context } = c
145
+ export function draw(c) {
146
+ const { device, context } = c
157
147
 
158
148
  const commandEncoder = device.createCommandEncoder()
159
149
 
@@ -161,28 +151,24 @@ export function draw (c) {
161
151
 
162
152
  // some nodes may need a reference to the default texture view (the frame backing)
163
153
  // this is generated each draw frame so we need to update the references
164
- for (const r of c.defaultTextureViewRefs)
165
- r.node.refs[r.refName] = v
154
+ for (const r of c.defaultTextureViewRefs) r.node.refs[r.refName] = v
166
155
 
167
156
  // run all of the enabled nodes
168
157
  for (const node of c.nodes) {
169
- if (!node.enabled)
170
- continue
158
+ if (!node.enabled) continue
171
159
 
172
160
  const nodeDef = c.nodeDefs[node.type]
173
161
  nodeDef.onRun(c, node, commandEncoder)
174
162
  }
175
163
 
176
- device.queue.submit([ commandEncoder.finish() ])
164
+ device.queue.submit([commandEncoder.finish()])
177
165
 
178
166
  // for sdl + gpu setups, we need to do this swap() step
179
- if (!c.canvas)
180
- c.context.swap()
167
+ if (!c.canvas) c.context.swap()
181
168
  }
182
169
 
183
-
184
170
  // clean up all the loaded data so we could re-load a level, etc.
185
- export function reset (c) {
171
+ export function reset(c) {
186
172
  for (const n of c.nodes) {
187
173
  const nodeDef = c.nodeDefs[n.type]
188
174
  nodeDef.onDestroy(c, n)
@@ -191,10 +177,9 @@ export function reset (c) {
191
177
  c.defaultTextureViewRefs.length = 0
192
178
  }
193
179
 
194
-
195
- export function setViewportDimensions (c, width, height) {
196
- c.viewport.width = width
197
- c.viewport.height = height
180
+ export function setViewportDimensions(c, width, height) {
181
+ c.viewport.width = width
182
+ c.viewport.height = height
198
183
 
199
184
  for (const n of c.nodes) {
200
185
  const nodeDef = c.nodeDefs[n.type]
@@ -202,11 +187,10 @@ export function setViewportDimensions (c, width, height) {
202
187
  }
203
188
  }
204
189
 
205
-
206
190
  // @param Array pos 2D point the viewport is centered on
207
- export function setViewportPosition (c, pos) {
208
- c.viewport.position[0] = pos[0] - (c.viewport.width / 2 / c.viewport.zoom)
209
- c.viewport.position[1] = pos[1] - (c.viewport.height / 2 / c.viewport.zoom)
191
+ export function setViewportPosition(c, pos) {
192
+ c.viewport.position[0] = pos[0] - c.viewport.width / 2 / c.viewport.zoom
193
+ c.viewport.position[1] = pos[1] - c.viewport.height / 2 / c.viewport.zoom
210
194
 
211
195
  for (const n of c.nodes) {
212
196
  const nodeDef = c.nodeDefs[n.type]
@@ -214,10 +198,8 @@ export function setViewportPosition (c, pos) {
214
198
  }
215
199
  }
216
200
 
217
-
218
- export function getCurrentTextureView (cobalt) {
219
- if (cobalt.canvas)
220
- return cobalt.context.getCurrentTexture().createView()
201
+ export function getCurrentTextureView(cobalt) {
202
+ if (cobalt.canvas) return cobalt.context.getCurrentTexture().createView()
221
203
  else {
222
204
  //return cobalt.context.getCurrentTexture().createView()
223
205
  return cobalt.context.getCurrentTextureView()
@@ -1,12 +1,13 @@
1
- import createTexture from './create-texture.js'
1
+ import createTexture from './create-texture.js'
2
2
  import getPreferredFormat from './get-preferred-format.js'
3
3
 
4
-
5
- export default function createTextureFromBuffer (c, label, image, format) {
6
-
4
+ export default function createTextureFromBuffer(c, label, image, format) {
7
5
  format = format || getPreferredFormat(c)
8
6
 
9
- const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
7
+ const usage =
8
+ GPUTextureUsage.TEXTURE_BINDING |
9
+ GPUTextureUsage.COPY_DST |
10
+ GPUTextureUsage.RENDER_ATTACHMENT
10
11
  const mip_count = 1
11
12
  const t = createTexture(c.device, label, image.width, image.height, mip_count, format, usage)
12
13
 
@@ -24,8 +25,8 @@ export default function createTextureFromBuffer (c, label, image, format) {
24
25
  { texture: t.texture },
25
26
  image.data,
26
27
  { bytesPerRow: 4 * image.width },
27
- { width: image.width, height: image.height }
28
- )
28
+ { width: image.width, height: image.height },
29
+ )
29
30
 
30
31
  // nearest neighbor filtering is good for da pixel art
31
32
  const samplerDescriptor = {
@@ -34,7 +35,7 @@ export default function createTextureFromBuffer (c, label, image, format) {
34
35
  magFilter: 'nearest',
35
36
  minFilter: 'nearest',
36
37
  mipmapFilter: 'nearest',
37
- maxAnisotropy: 1
38
+ maxAnisotropy: 1,
38
39
  }
39
40
 
40
41
  t.sampler = c.device.createSampler(samplerDescriptor)
@@ -1,26 +1,38 @@
1
- import createTexture from './create-texture.js'
1
+ import createTexture from './create-texture.js'
2
2
  import getPreferredFormat from './get-preferred-format.js'
3
3
 
4
-
5
- export default async function createTextureFromUrl (c, label, url, format) {
4
+ export default async function createTextureFromUrl(c, label, url, format) {
6
5
  const response = await fetch(url)
7
6
  const blob = await response.blob()
8
7
 
9
8
  format = format || getPreferredFormat(c)
10
9
 
11
- const imageData = await createImageBitmap(blob/*, { premultiplyAlpha: 'none', resizeQuality: 'pixelated' }*/)
12
-
13
- const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
10
+ const imageData = await createImageBitmap(
11
+ blob /*, { premultiplyAlpha: 'none', resizeQuality: 'pixelated' }*/,
12
+ )
13
+
14
+ const usage =
15
+ GPUTextureUsage.TEXTURE_BINDING |
16
+ GPUTextureUsage.COPY_DST |
17
+ GPUTextureUsage.RENDER_ATTACHMENT
14
18
  const mip_count = 1
15
- const t = createTexture(c.device, label, imageData.width, imageData.height, mip_count, format, usage)
19
+ const t = createTexture(
20
+ c.device,
21
+ label,
22
+ imageData.width,
23
+ imageData.height,
24
+ mip_count,
25
+ format,
26
+ usage,
27
+ )
16
28
 
17
29
  c.device.queue.copyExternalImageToTexture(
18
30
  { source: imageData },
19
31
  { texture: t.texture },
20
32
  {
21
33
  width: imageData.width,
22
- height: imageData.height
23
- }
34
+ height: imageData.height,
35
+ },
24
36
  )
25
37
 
26
38
  // nearest neighbor filtering is good for da pixel art
@@ -30,7 +42,7 @@ export default async function createTextureFromUrl (c, label, url, format) {
30
42
  magFilter: 'nearest',
31
43
  minFilter: 'nearest',
32
44
  mipmapFilter: 'nearest',
33
- maxAnisotropy: 1
45
+ maxAnisotropy: 1,
34
46
  }
35
47
 
36
48
  t.sampler = c.device.createSampler(samplerDescriptor)
@@ -1,5 +1,4 @@
1
- export default function createTexture (device, label, width, height, mip_count, format, usage) {
2
-
1
+ export default function createTexture(device, label, width, height, mip_count, format, usage) {
3
2
  const texture = device.createTexture({
4
3
  label,
5
4
  size: { width, height },
@@ -12,19 +11,21 @@ export default function createTexture (device, label, width, height, mip_count,
12
11
 
13
12
  const view = texture.createView()
14
13
 
15
- const mip_view = [ ]
14
+ const mip_view = []
16
15
 
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
- }))
16
+ for (let i = 0; i < mip_count; i++)
17
+ mip_view.push(
18
+ 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
+ )
28
29
 
29
30
  const sampler = device.createSampler({
30
31
  label: `${label} sampler`,
@@ -1,20 +1,18 @@
1
- import getPreferredFormat from '../get-preferred-format.js'
2
- import { TrianglesBuffer } from './triangles-buffer.js'
1
+ import getPreferredFormat from '../get-preferred-format.js'
2
+ import { DisplacementComposition } from './displacement-composition.js'
3
3
  import { DisplacementParametersBuffer } from './displacement-parameters-buffer.js'
4
- import { DisplacementComposition } from './displacement-composition.js'
5
- import { DisplacementTexture } from './displacement-texture.js'
6
-
4
+ import { DisplacementTexture } from './displacement-texture.js'
5
+ import { TrianglesBuffer } from './triangles-buffer.js'
7
6
 
8
7
  // adapted to webgpu from https://github.com/pixijs/pixijs/tree/dev/packages/filter-displacement
9
8
 
10
9
  export default {
11
10
  type: 'cobalt:displacement',
12
11
  refs: [
12
+ // input framebuffer texture with the scene drawn
13
+ { name: 'color', type: 'textureView', format: 'bgra8unorm', access: 'read' },
13
14
 
14
- // input framebuffer texture with the scene drawn
15
- { name: 'color', type: 'textureView', format: 'bgra8unorm', access: 'read' },
16
-
17
- // displacement map (perlin noise texture works well here)
15
+ // displacement map (perlin noise texture works well here)
18
16
  { name: 'map', type: 'cobaltTexture', format: 'bgra8unorm', access: 'read' },
19
17
 
20
18
  // result we're writing to
@@ -25,7 +23,7 @@ export default {
25
23
 
26
24
  // @params Object cobalt renderer world object
27
25
  // @params Object options optional data passed when initing this node
28
- onInit: async function (cobalt, options={}) {
26
+ onInit: async function (cobalt, options = {}) {
29
27
  return init(cobalt, options)
30
28
  },
31
29
 
@@ -41,37 +39,37 @@ export default {
41
39
 
42
40
  onResize: function (cobalt, node) {
43
41
  // do whatever you need when the dimensions of the renderer change (resize textures, etc.)
44
- node.data.displacementTexture.resize(cobalt.viewport.width, cobalt.viewport.height);
42
+ node.data.displacementTexture.resize(cobalt.viewport.width, cobalt.viewport.height)
45
43
 
46
- node.data.displacementComposition.setColorTextureView(node.refs.color.data.view);
47
- node.data.displacementComposition.setNoiseMapTextureView(node.refs.map.view);
48
- node.data.displacementComposition.setDisplacementTextureView(node.data.displacementTexture.getView());
44
+ node.data.displacementComposition.setColorTextureView(node.refs.color.data.view)
45
+ node.data.displacementComposition.setNoiseMapTextureView(node.refs.map.view)
46
+ node.data.displacementComposition.setDisplacementTextureView(
47
+ node.data.displacementTexture.getView(),
48
+ )
49
49
  },
50
50
 
51
51
  onViewportPosition: function (cobalt, node) {
52
- node.data.displacementTexture.setViewport(cobalt.viewport);
52
+ node.data.displacementTexture.setViewport(cobalt.viewport)
53
53
  },
54
54
 
55
55
  // optional
56
56
  customFunctions: {
57
-
58
57
  addTriangle: function (cobalt, node, triangleVertices) {
59
- return node.data.trianglesBuffer.addTriangle(triangleVertices);
58
+ return node.data.trianglesBuffer.addTriangle(triangleVertices)
60
59
  },
61
60
 
62
61
  removeTriangle: function (cobalt, node, triangleId) {
63
- node.data.trianglesBuffer.removeTriangle(triangleId);
62
+ node.data.trianglesBuffer.removeTriangle(triangleId)
64
63
  },
65
64
 
66
65
  setPosition: function (cobalt, node, triangleId, triangleVertices) {
67
- node.data.trianglesBuffer.setTriangle(triangleId, triangleVertices);
66
+ node.data.trianglesBuffer.setTriangle(triangleId, triangleVertices)
68
67
  },
69
68
  },
70
69
  }
71
70
 
72
-
73
71
  // This corresponds to a WebGPU render pass. It handles 1 sprite layer.
74
- async function init (cobalt, node) {
72
+ async function init(cobalt, node) {
75
73
  const { device } = cobalt
76
74
 
77
75
  const displacementParameters = new DisplacementParametersBuffer({
@@ -80,15 +78,15 @@ async function init (cobalt, node) {
80
78
  offsetX: node.options.offseyX ?? 0,
81
79
  offsetY: node.options.offseyY ?? 0,
82
80
  scale: node.options.scale ?? 20,
83
- }
81
+ },
84
82
  })
85
83
 
86
- const MAX_SPRITE_COUNT = 256 // max number of displacement sprites in this render pass
84
+ const MAX_SPRITE_COUNT = 256 // max number of displacement sprites in this render pass
87
85
 
88
86
  const trianglesBuffer = new TrianglesBuffer({
89
87
  device,
90
88
  maxSpriteCount: MAX_SPRITE_COUNT,
91
- });
89
+ })
92
90
 
93
91
  const displacementTexture = new DisplacementTexture({
94
92
  device,
@@ -99,7 +97,7 @@ async function init (cobalt, node) {
99
97
  blurFactor: 8,
100
98
 
101
99
  trianglesBuffer,
102
- });
100
+ })
103
101
 
104
102
  const displacementComposition = new DisplacementComposition({
105
103
  device,
@@ -108,9 +106,9 @@ async function init (cobalt, node) {
108
106
  colorTextureView: node.refs.color.data.view,
109
107
  noiseMapTextureView: node.refs.map.view,
110
108
  displacementTextureView: displacementTexture.getView(),
111
-
109
+
112
110
  displacementParametersBuffer: displacementParameters,
113
- });
111
+ })
114
112
 
115
113
  return {
116
114
  displacementParameters,
@@ -120,16 +118,14 @@ async function init (cobalt, node) {
120
118
  }
121
119
  }
122
120
 
123
-
124
121
  function draw(cobalt, node, commandEncoder) {
125
- const spriteCount = node.data.trianglesBuffer.spriteCount;
122
+ const spriteCount = node.data.trianglesBuffer.spriteCount
126
123
 
127
- if (spriteCount === 0)
128
- return
124
+ if (spriteCount === 0) return
129
125
 
130
- node.data.trianglesBuffer.update();
126
+ node.data.trianglesBuffer.update()
131
127
 
132
- node.data.displacementTexture.update(commandEncoder);
128
+ node.data.displacementTexture.update(commandEncoder)
133
129
 
134
130
  const renderpass = commandEncoder.beginRenderPass({
135
131
  label: 'displacement',
@@ -138,25 +134,24 @@ function draw(cobalt, node, commandEncoder) {
138
134
  view: node.refs.out,
139
135
  clearValue: cobalt.clearValue,
140
136
  loadOp: 'load',
141
- storeOp: 'store'
142
- }
137
+ storeOp: 'store',
138
+ },
143
139
  ],
144
- });
145
- renderpass.executeBundles([node.data.displacementComposition.getRenderBundle()]);
140
+ })
141
+ renderpass.executeBundles([node.data.displacementComposition.getRenderBundle()])
146
142
  renderpass.end()
147
143
  }
148
144
 
145
+ function destroy(node) {
146
+ node.data.trianglesBuffer.destroy()
147
+ node.data.trianglesBuffer = null
149
148
 
150
- function destroy (node) {
151
- node.data.trianglesBuffer.destroy();
152
- node.data.trianglesBuffer = null;
153
-
154
- node.data.displacementParameters.destroy();
155
- node.data.displacementParameters = null;
149
+ node.data.displacementParameters.destroy()
150
+ node.data.displacementParameters = null
156
151
 
157
- node.data.displacementTexture.destroy();
158
- node.data.displacementTexture = null;
152
+ node.data.displacementTexture.destroy()
153
+ node.data.displacementTexture = null
159
154
 
160
- node.data.displacementComposition.destroy();
161
- node.data.displacementComposition = null;
155
+ node.data.displacementComposition.destroy()
156
+ node.data.displacementComposition = null
162
157
  }