@footgun/cobalt 0.6.14 → 0.7.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/bundle.js +1318 -1304
  3. package/examples/01-primitives/index.html +1 -1
  4. package/examples/02-sprites/entity-sprite.js +10 -15
  5. package/examples/02-sprites/hdr.html +321 -0
  6. package/examples/02-sprites/index.html +21 -120
  7. package/examples/02-sprites/system-renderer.js +2 -2
  8. package/examples/03-tiles/index.html +87 -32
  9. package/examples/03-tiles/system-renderer.js +2 -2
  10. package/examples/04-overlay/index.html +178 -21
  11. package/examples/05-bloom/index.html +23 -23
  12. package/examples/05-bloom/system-renderer.js +2 -2
  13. package/examples/06-displacement/index.html +20 -112
  14. package/examples/06-displacement/system-renderer.js +2 -2
  15. package/examples/08-light/index.html +51 -123
  16. package/package.json +1 -1
  17. package/src/cobalt.js +8 -8
  18. package/src/sprite/public-api.js +57 -177
  19. package/src/sprite/sprite.js +301 -177
  20. package/src/sprite/sprite.wgsl +68 -87
  21. package/src/sprite-hdr/public-api.js +95 -0
  22. package/src/sprite-hdr/sprite.js +414 -0
  23. package/src/sprite-hdr/sprite.wgsl +101 -0
  24. package/src/{sprite → spritesheet}/create-sprite-quads.js +11 -11
  25. package/src/{sprite → spritesheet}/read-spritesheet.js +62 -28
  26. package/src/spritesheet/spritesheet.js +75 -0
  27. package/src/{tile → tile-hdr}/atlas.js +5 -3
  28. package/src/{tile → tile-hdr}/tile.js +15 -6
  29. package/examples/04-overlay/deps.js +0 -1
  30. package/src/overlay/constants.js +0 -1
  31. package/src/overlay/overlay.js +0 -343
  32. package/src/overlay/overlay.wgsl +0 -88
  33. package/src/sprite/constants.js +0 -1
  34. package/src/sprite/sorted-binary-insert.js +0 -45
  35. package/src/sprite/spritesheet.js +0 -215
  36. /package/examples/02-sprites/{Game.js → Global.js} +0 -0
  37. /package/examples/03-tiles/{Game.js → Global.js} +0 -0
  38. /package/examples/05-bloom/{Game.js → Global.js} +0 -0
  39. /package/examples/06-displacement/{Game.js → Global.js} +0 -0
  40. /package/examples/08-light/{Game.js → Global.js} +0 -0
  41. /package/src/{tile → tile-hdr}/tile.wgsl +0 -0
@@ -0,0 +1,101 @@
1
+ struct ViewParams {
2
+ view : mat4x4<f32>,
3
+ proj : mat4x4<f32>
4
+ };
5
+
6
+ @group(0) @binding(0) var<uniform> uView : ViewParams;
7
+ @group(0) @binding(1) var uSampler : sampler;
8
+ @group(0) @binding(2) var uTex : texture_2d<f32>;
9
+
10
+
11
+ struct SpriteDesc {
12
+ uvOrigin : vec2<f32>,
13
+ uvSpan : vec2<f32>,
14
+ frameSize : vec2<f32>, // pixels
15
+ centerOffset : vec2<f32>, // pixels
16
+ };
17
+
18
+ @group(0) @binding(3) var<storage, read> Sprites : array<SpriteDesc>;
19
+
20
+ @group(0) @binding(4) var emissiveTexture: texture_2d<f32>;
21
+
22
+ struct VSOut {
23
+ @builtin(position) pos : vec4<f32>,
24
+ @location(0) uv : vec2<f32>,
25
+ @location(1) tint : vec4<f32>,
26
+ @location(2) opacity : f32,
27
+ };
28
+
29
+ // corners for a unit-centered quad in strip order
30
+ const corners = array<vec2<f32>, 4>(
31
+ vec2<f32>(-0.5, -0.5),
32
+ vec2<f32>( 0.5, -0.5),
33
+ vec2<f32>(-0.5, 0.5),
34
+ vec2<f32>( 0.5, 0.5),
35
+ );
36
+ const uvBase = array<vec2<f32>, 4>(
37
+ vec2<f32>(0.0, 0.0),
38
+ vec2<f32>(1.0, 0.0),
39
+ vec2<f32>(0.0, 1.0),
40
+ vec2<f32>(1.0, 1.0),
41
+ );
42
+
43
+ // multiple render targets
44
+ struct GBufferOutput {
45
+ @location(0) color : vec4<f32>,
46
+ @location(1) emissive : vec4<f32>,
47
+ }
48
+
49
+
50
+ @vertex
51
+ fn vs_main(@builtin(vertex_index) vid : u32,
52
+ // per-instance attributes (locations 0..4)
53
+ @location(0) i_pos : vec2<f32>,
54
+ @location(1) i_size : vec2<f32>, // scales descriptor frame size (1,1 means use descriptor size)
55
+ @location(2) i_scale : vec2<f32>, // per-axis scale
56
+ @location(3) i_tint : vec4<f32>,
57
+ @location(4) i_spriteId : u32,
58
+ @location(5) i_opacity : f32,
59
+ @location(6) i_rotation : f32
60
+ ) -> VSOut {
61
+
62
+ let rot = i_rotation;
63
+ let c = cos(rot);
64
+ let s = sin(rot);
65
+
66
+ let d = Sprites[i_spriteId];
67
+ let corner = corners[vid];
68
+
69
+ let sizePx = d.frameSize * i_size * i_scale; // per-axis scale applied on trimmed frame
70
+ var local = corner * sizePx;
71
+ local += d.centerOffset * i_scale; // compensate trimming // compensate trimming (so rotation is around original center)
72
+
73
+
74
+ let rotated = vec2<f32>(local.x * c - local.y * s, local.x * s + local.y * c);
75
+ let world = vec4<f32>(rotated + i_pos, 0.0, 1.0);
76
+
77
+ var out : VSOut;
78
+ out.pos = uView.proj * uView.view * world;
79
+ out.uv = d.uvOrigin + d.uvSpan * uvBase[vid];
80
+ out.tint = i_tint;
81
+ out.opacity = i_opacity;
82
+
83
+ return out;
84
+ }
85
+
86
+
87
+ @fragment
88
+ fn fs_main(in : VSOut) -> GBufferOutput {
89
+
90
+ var output : GBufferOutput;
91
+
92
+ let texel = textureSample(uTex, uSampler, in.uv);
93
+ output.color = vec4<f32>(texel.rgb * (1.0 - in.tint.a) + (in.tint.rgb * in.tint.a), texel.a * in.opacity);
94
+
95
+ let emissive = textureSample(emissiveTexture, uSampler, in.uv);
96
+
97
+ // the alpha channel in the emissive texture is used for emission strength
98
+ output.emissive = vec4(emissive.rgb, 1.0) * emissive.a;
99
+
100
+ return output;
101
+ }
@@ -5,15 +5,15 @@ export default function createSpriteQuads (device, spritesheet) {
5
5
  /*
6
6
  const vertices = new Float32Array([
7
7
  // position tex
8
- // x y z u v
9
- -0.5, -0.5, 0.0, 0.0, 0.0,
10
- -0.5, 0.5, 0.0, 0.0, 1.0,
11
- 0.5, 0.5, 0.0, 1.0, 1.0,
12
-
8
+ // x y u v
9
+ -0.5, -0.5, 0.0, 0.0,
10
+ -0.5, 0.5, 0.0, 1.0,
11
+ 0.5, 0.5, 1.0,
12
+
13
13
  // triangle 2 (2nd half of quad)
14
- -0.5, -0.5, 0.0, 0.0, 0.0,
15
- 0.5, 0.5, 0.0, 1.0, 1.0,
16
- 0.5, -0.5, 0.0, 1.0, 0.0,
14
+ -0.5, -0.5, 0.0, 0.0,
15
+ 0.5, 0.5, 1.0, 1.0,
16
+ 0.5, -0.5, 1.0, 0.0,
17
17
  ])
18
18
  */
19
19
 
@@ -35,20 +35,20 @@ export default function createSpriteQuads (device, spritesheet) {
35
35
  buffer.unmap()
36
36
 
37
37
  const bufferLayout = {
38
- arrayStride: 20,
38
+ arrayStride: 16,
39
39
  stepMode: 'vertex',
40
40
  attributes: [
41
41
  // position
42
42
  {
43
43
  shaderLocation: 0,
44
- format: 'float32x3',
44
+ format: 'float32x2',
45
45
  offset: 0
46
46
  },
47
47
  // uv
48
48
  {
49
49
  shaderLocation: 1,
50
50
  format: 'float32x2',
51
- offset: 12
51
+ offset: 8
52
52
  }
53
53
  ]
54
54
  }
@@ -2,26 +2,18 @@
2
2
  // copied into the renderer's vertex buffer
3
3
  //
4
4
  // @return Float32Array vertices (interleaved positions and uvs)
5
+ /*
5
6
  export default function readSpriteSheet (spritesheetJson) {
6
7
 
7
8
  // a sprite is a quad (2 triangles) so it has 6 vertices
8
- // each vertex has 5 float32 (interleaved vec3 position, vec2 uv)
9
- const spriteFloatCount = 5 * 6
9
+ // each vertex has 4 float32 (interleaved vec2 position, vec2 uv)
10
+ const spriteFloatCount = 4 * 6
10
11
 
11
12
  // each key in the spritesheet is a unique sprite type
12
13
  const spriteCount = Object.keys(spritesheetJson.frames).length
13
14
 
14
15
  const vertices = new Float32Array(spriteCount * spriteFloatCount)
15
16
 
16
- /*
17
- stores mapping between sprite name and first vertex index. e.g.,
18
- [
19
- 'hero_run-0', // 1st vertex index is at 0
20
- 'bullet_travel-0' // 1st vertex index is at 6
21
- 'bob_idle-1' // 1st vertex index is at 12
22
- ]
23
- these will alway be multiples of 6, because there are 6 vertices per sprite
24
- */
25
17
  const locations = [ ]
26
18
 
27
19
  const spriteMeta = { }
@@ -44,10 +36,10 @@ export default function readSpriteSheet (spritesheetJson) {
44
36
  const maxX = -0.5 + ((frame.spriteSourceSize.x + frame.spriteSourceSize.w) / frame.sourceSize.w)
45
37
  const maxY = -0.5 + ((frame.spriteSourceSize.y + frame.spriteSourceSize.h) / frame.sourceSize.h)
46
38
 
47
- const p0 = [ minX, minY, 0 ]
48
- const p1 = [ minX, maxY, 0 ]
49
- const p2 = [ maxX, maxY, 0 ]
50
- const p3 = [ maxX, minY, 0 ]
39
+ const p0 = [ minX, minY ]
40
+ const p1 = [ minX, maxY ]
41
+ const p2 = [ maxX, maxY ]
42
+ const p3 = [ maxX, minY ]
51
43
 
52
44
 
53
45
  // calculate uvs
@@ -64,29 +56,71 @@ export default function readSpriteSheet (spritesheetJson) {
64
56
 
65
57
 
66
58
  // quad triangles are [ p0, p1, p2 ] , [ p0, p2, p3 ]
67
- // vertex data is interleaved; a single vertex has a vec3 position followed immediately by vec2 uv
59
+ // vertex data is interleaved; a single vertex has a vec2 position followed immediately by vec2 uv
68
60
  vertices.set(p0, i)
69
- vertices.set(uv0, i + 3)
61
+ vertices.set(uv0, i + 2)
70
62
 
71
- vertices.set(p1, i + 5)
72
- vertices.set(uv1, i + 8)
63
+ vertices.set(p1, i + 4)
64
+ vertices.set(uv1, i + 6)
73
65
 
74
- vertices.set(p2, i + 10)
75
- vertices.set(uv2, i + 13)
66
+ vertices.set(p2, i + 8)
67
+ vertices.set(uv2, i + 10)
76
68
 
77
- vertices.set(p0, i + 15)
78
- vertices.set(uv0, i + 18)
69
+ vertices.set(p0, i + 12)
70
+ vertices.set(uv0, i + 14)
79
71
 
80
- vertices.set(p2, i + 20)
81
- vertices.set(uv2, i + 23)
72
+ vertices.set(p2, i + 16)
73
+ vertices.set(uv2, i + 18)
82
74
 
83
- vertices.set(p3, i + 25)
84
- vertices.set(uv3, i + 28)
75
+ vertices.set(p3, i + 20)
76
+ vertices.set(uv3, i + 22)
85
77
 
86
78
  i += spriteFloatCount
87
79
  }
88
80
 
89
- return { /*spriteCount, */ spriteMeta, locations, vertices }
81
+ return { spriteMeta, locations, vertices }
82
+ }
83
+ */
84
+
85
+
86
+ /**
87
+ * ------------------------------ TexturePacker (no rotation) ------------------------------
88
+ * Accepts the "Hash" JSON format from TexturePacker. Assumes rotated=false.
89
+ *
90
+ * texturepacker frame structure:
91
+ "f2.png":
92
+ {
93
+ "frame": {"x":15,"y":1,"w":10,"h":15},
94
+ "rotated": false,
95
+ "trimmed": true,
96
+ "spriteSourceSize": {"x":22,"y":17,"w":10,"h":15},
97
+ "sourceSize": {"w":32,"h":32}
98
+ },
99
+ */
100
+ export default function buildSpriteTableFromTexturePacker (doc) {
101
+ const atlasW = doc.meta.size.w;
102
+ const atlasH = doc.meta.size.h;
103
+ const names = Object.keys(doc.frames).sort();
104
+ const descs = new Array(names.length);
105
+
106
+ for (let i=0; i<names.length; i++) {
107
+ const fr = doc.frames[names[i]];
108
+
109
+ const fx = fr.frame.x, fy = fr.frame.y, fw = fr.frame.w, fh = fr.frame.h;
110
+ const offX = fx / atlasW, offY = fy / atlasH;
111
+ const spanX = fw / atlasW, spanY = fh / atlasH;
112
+ const sw = fr.sourceSize.w, sh = fr.sourceSize.h;
113
+ const ox = fr.spriteSourceSize.x, oy = fr.spriteSourceSize.y;
114
+ const cx = (ox + fw*0.5) - (sw*0.5);
115
+ const cy = (oy + fh*0.5) - (sh*0.5);
116
+ descs[i] = {
117
+ UvOrigin: [offX, offY],
118
+ UvSpan: [spanX, spanY],
119
+ FrameSize:[fw, fh],
120
+ CenterOffset:[cx, cy],
121
+ };
122
+ }
123
+ return { descs, names };
90
124
  }
91
125
 
92
126
 
@@ -0,0 +1,75 @@
1
+ import createTextureFromBuffer from '../create-texture-from-buffer.js'
2
+ import createTextureFromUrl from '../create-texture-from-url.js'
3
+ import readSpriteSheet from './read-spritesheet.js'
4
+
5
+
6
+ // shared spritesheet resource, used by each sprite render node
7
+
8
+ export default {
9
+ type: 'cobalt:spritesheet',
10
+ refs: [ ],
11
+
12
+ // @params Object cobalt renderer world object
13
+ // @params Object options optional data passed when initing this node
14
+ onInit: async function (cobalt, options={}) {
15
+ return init(cobalt, options)
16
+ },
17
+
18
+ onRun: function (cobalt, node, webGpuCommandEncoder) { },
19
+
20
+ onDestroy: function (cobalt, node) {
21
+ // any cleanup for your node should go here (releasing textures, etc.)
22
+ destroy(node)
23
+ },
24
+
25
+ onResize: function (cobalt, node) { },
26
+
27
+ onViewportPosition: function (cobalt, node) { },
28
+ }
29
+
30
+
31
+ // configure the common settings for sprite rendering
32
+ async function init (cobalt, node) {
33
+ const { canvas, device } = cobalt
34
+
35
+ let spritesheet, colorTexture, emissiveTexture
36
+
37
+ const format = node.options.format || 'rgba8unorm'
38
+
39
+ if (canvas) {
40
+ // browser (canvas) path
41
+ spritesheet = await fetch(node.options.spriteSheetJsonUrl)
42
+ spritesheet = await spritesheet.json()
43
+ spritesheet = readSpriteSheet(spritesheet)
44
+
45
+ colorTexture = await createTextureFromUrl(cobalt, 'sprite', node.options.colorTextureUrl, format)
46
+ emissiveTexture = await createTextureFromUrl(cobalt, 'emissive sprite', node.options.emissiveTextureUrl, format)
47
+
48
+ // for some reason this needs to be done _after_ creating the material, or the rendering will be blurry
49
+ canvas.style.imageRendering = 'pixelated'
50
+ }
51
+ else {
52
+ // sdl + gpu path
53
+ spritesheet = readSpriteSheet(node.options.spriteSheetJson)
54
+
55
+ colorTexture = await createTextureFromBuffer(cobalt, 'sprite', node.options.colorTexture, format)
56
+ emissiveTexture = await createTextureFromBuffer(cobalt, 'emissive sprite', node.options.emissiveTexture, format)
57
+ }
58
+
59
+ // Map sprite name → ID
60
+ const idByName = new Map(spritesheet.names.map((n,i)=>[n,i]))
61
+
62
+ return {
63
+ colorTexture,
64
+ emissiveTexture,
65
+ spritesheet,
66
+ idByName,
67
+ }
68
+ }
69
+
70
+
71
+ function destroy (node) {
72
+ node.data.quads.buffer.destroy()
73
+ node.data.colorTexture.buffer.destroy()
74
+ node.data.emissiveTexture.texture.destroy()
75
+ }
@@ -1,6 +1,8 @@
1
1
  import createTextureFromBuffer from '../create-texture-from-buffer.js'
2
2
  import createTextureFromUrl from '../create-texture-from-url.js'
3
+ import getPreferredFormat from '../get-preferred-format.js'
3
4
  import tileWGSL from './tile.wgsl'
5
+ import round from 'round-half-up-symmetric'
4
6
 
5
7
 
6
8
  const _buf = new Float32Array(8) //(136) // tile instance data stored in a UBO
@@ -139,7 +141,7 @@ async function init (cobalt, nodeData) {
139
141
  entryPoint: 'fs_main',
140
142
  targets: [
141
143
  {
142
- format: 'rgba16float',
144
+ format: nodeData.options.outputFormat || getPreferredFormat(cobalt),
143
145
  blend: {
144
146
  color: {
145
147
  srcFactor: 'src-alpha',
@@ -183,8 +185,8 @@ function destroy (data) {
183
185
 
184
186
  function _writeTileBuffer (c, nodeData) {
185
187
  // c.viewport.position is the top left visible corner of the level
186
- _buf[0] = c.viewport.position[0]
187
- _buf[1] = c.viewport.position[1]
188
+ _buf[0] = round(c.viewport.position[0])
189
+ _buf[1] = round(c.viewport.position[1])
188
190
 
189
191
  const tile = nodeData.data
190
192
  const { tileScale, tileSize } = tile
@@ -1,9 +1,10 @@
1
1
  import createTextureFromBuffer from '../create-texture-from-buffer.js'
2
2
  import createTextureFromUrl from '../create-texture-from-url.js'
3
+ import getPreferredFormat from '../get-preferred-format.js'
3
4
 
4
5
 
5
6
  /*
6
- Tile layers are totally static, and there are usually many of them in a grid, in several layers.
7
+ Tile layers are totally static, and there are usually many of them on several layers.
7
8
 
8
9
  These use a `TileRenderPass` data structure which provides 100% GPU hardware based tile rendering, making them _almost_ free CPU-wise.
9
10
 
@@ -17,9 +18,10 @@ Inspired by/ported from https://blog.tojicode.com/2012/07/sprite-tile-maps-on-gp
17
18
 
18
19
 
19
20
  export default {
20
- type: 'cobalt:tile',
21
+ type: 'cobalt:tileHDR',
21
22
  refs: [
22
- { name: 'tileAtlas', type: 'textureView', format: 'rgba8unorm', access: 'write' },
23
+ { name: 'tileAtlas', type: 'textureView', format: 'rgba8unorm', access: 'read' },
24
+ { name: "hdr", type: "textureView", format: "rgba16float", access: "write", },
23
25
  ],
24
26
 
25
27
  // @params Object cobalt renderer world object
@@ -47,12 +49,13 @@ export default {
47
49
 
48
50
  // optional
49
51
  customFunctions: {
52
+
50
53
  setTexture: async function (cobalt, node, texture) {
51
54
  const { canvas, device } = cobalt
52
55
 
53
56
  destroy(node)
54
57
 
55
- const format = node.options.format || 'rgba8unorm'
58
+ const format = node.options.format || getPreferredFormat(cobalt)
56
59
 
57
60
  let material
58
61
 
@@ -98,7 +101,7 @@ async function init (cobalt, nodeData) {
98
101
 
99
102
  let material
100
103
 
101
- const format = nodeData.options.format || 'rgba8unorm'
104
+ const format = nodeData.options.format || getPreferredFormat(cobalt)
102
105
 
103
106
  // build the tile layer and add it to the cobalt data structure
104
107
  if (canvas) {
@@ -156,6 +159,11 @@ async function init (cobalt, nodeData) {
156
159
 
157
160
  function draw (cobalt, nodeData, commandEncoder) {
158
161
 
162
+ // calling setTexture can cause the texture to be destroyed followed by this draw command
163
+ // so check for the undefined texture first and bail for a frame.
164
+ if (!nodeData.data.material.texture)
165
+ return
166
+
159
167
  const { device } = cobalt
160
168
 
161
169
  // on the first render, we should clear the color attachment.
@@ -166,7 +174,8 @@ function draw (cobalt, nodeData, commandEncoder) {
166
174
  label: 'tile',
167
175
  colorAttachments: [
168
176
  {
169
- view: nodeData.refs.hdr.data.view,
177
+ // hdr is passsed as a node || FRAME_TEXTURE_VIEW
178
+ view: nodeData.refs.hdr.data?.view || nodeData.refs.hdr,
170
179
  clearValue: cobalt.clearValue,
171
180
  loadOp,
172
181
  storeOp: 'store'
@@ -1 +0,0 @@
1
- export { mat4, vec2, vec3, vec4 } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'
@@ -1 +0,0 @@
1
- export const FLOAT32S_PER_SPRITE = 12 // vec2(translate) + vec2(scale) + vec4(tint) + opacity + rotation + emissiveIntensity + sortValue