@footgun/cobalt 0.6.15 → 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 +16 -0
  2. package/bundle.js +1313 -1301
  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 +33 -35
  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 +9 -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
@@ -1,343 +0,0 @@
1
- import * as publicAPI from '../sprite/public-api.js'
2
- import createSpriteQuads from '../sprite/create-sprite-quads.js'
3
- import getPreferredFormat from '../get-preferred-format.js'
4
- import overlayWGSL from './overlay.wgsl'
5
- import sortedBinaryInsert from '../sprite/sorted-binary-insert.js'
6
- import uuid from '../uuid.js'
7
- import { FLOAT32S_PER_SPRITE } from './constants.js'
8
- import { mat4, vec3, vec4 } from 'wgpu-matrix'
9
-
10
-
11
- // a sprite renderer with coordinates in screen space. useful for HUD/ui stuff
12
-
13
- const _tmpVec4 = vec4.create()
14
- const _tmpVec3 = vec3.create()
15
-
16
-
17
- export default {
18
- type: 'cobalt:overlay',
19
- refs: [
20
- { name: 'spritesheet', type: 'customResource', access: 'read' },
21
- { name: 'color', type: 'textView', format: 'rgba8unorm', access: 'write' },
22
- ],
23
-
24
- // cobalt event handling functions
25
-
26
- // @params Object cobalt renderer world object
27
- // @params Object options optional data passed when initing this node
28
- onInit: async function (cobalt, options={}) {
29
- return init(cobalt, options)
30
- },
31
-
32
- onRun: function (cobalt, node, webGpuCommandEncoder) {
33
- // do whatever you need for this node. webgpu renderpasses, etc.
34
- draw(cobalt, node, webGpuCommandEncoder)
35
- },
36
-
37
- onDestroy: function (cobalt, node) {
38
- // any cleanup for your node should go here (releasing textures, etc.)
39
- destroy(node)
40
- },
41
-
42
- onResize: function (cobalt, node) {
43
- _writeOverlayBuffer(cobalt, node)
44
- },
45
-
46
- onViewportPosition: function (cobalt, node) {
47
- _writeOverlayBuffer(cobalt, node)
48
- },
49
-
50
- // optional
51
- customFunctions: { ...publicAPI },
52
- }
53
-
54
-
55
- // This corresponds to a WebGPU render pass. It handles 1 sprite layer.
56
- async function init (cobalt, nodeData) {
57
- const { device } = cobalt
58
-
59
- const MAX_SPRITE_COUNT = 16192 // max number of sprites in a single sprite render pass
60
-
61
- const numInstances = MAX_SPRITE_COUNT
62
-
63
- const translateFloatCount = 2 // vec2
64
- const translateSize = Float32Array.BYTES_PER_ELEMENT * translateFloatCount // in bytes
65
-
66
- const scaleFloatCount = 2 // vec2
67
- const scaleSize = Float32Array.BYTES_PER_ELEMENT * scaleFloatCount // in bytes
68
-
69
- const tintFloatCount = 4 // vec4
70
- const tintSize = Float32Array.BYTES_PER_ELEMENT * tintFloatCount // in bytes
71
-
72
- const opacityFloatCount = 4 // vec4. technically we only need 3 floats (opacity, rotation, emissiveIntensity) but that screws up data alignment in the shader
73
- const opacitySize = Float32Array.BYTES_PER_ELEMENT * opacityFloatCount // in bytes
74
-
75
- // instanced sprite data (scale, translation, tint, opacity)
76
- const spriteBuffer = device.createBuffer({
77
- size: (translateSize + scaleSize + tintSize + opacitySize) * numInstances, // 4x4 matrix with 4 bytes per float32, per instance
78
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
79
- })
80
-
81
- // the view and project matrices
82
- const uniformBuffer = device.createBuffer({
83
- size: 64 * 2, // 4x4 matrix with 4 bytes per float32, times 2 matrices (view, projection)
84
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
85
- })
86
-
87
- const bindGroupLayout = device.createBindGroupLayout({
88
- entries: [
89
- {
90
- binding: 0,
91
- visibility: GPUShaderStage.VERTEX,
92
- buffer: { }
93
- },
94
- {
95
- binding: 1,
96
- visibility: GPUShaderStage.FRAGMENT,
97
- texture: { }
98
- },
99
- {
100
- binding: 2,
101
- visibility: GPUShaderStage.FRAGMENT,
102
- sampler: { }
103
- },
104
- {
105
- binding: 3,
106
- visibility: GPUShaderStage.VERTEX,
107
- buffer: {
108
- type: 'read-only-storage'
109
- }
110
- },
111
- ],
112
- })
113
-
114
- const bindGroup = device.createBindGroup({
115
- layout: bindGroupLayout,
116
- entries: [
117
- {
118
- binding: 0,
119
- resource: {
120
- buffer: uniformBuffer,
121
- }
122
- },
123
- {
124
- binding: 1,
125
- resource: nodeData.refs.spritesheet.data.colorTexture.view
126
- },
127
- {
128
- binding: 2,
129
- resource: nodeData.refs.spritesheet.data.colorTexture.sampler
130
- },
131
- {
132
- binding: 3,
133
- resource: {
134
- buffer: spriteBuffer
135
- }
136
- }
137
- ]
138
- })
139
-
140
-
141
- const pipelineLayout = device.createPipelineLayout({
142
- bindGroupLayouts: [ bindGroupLayout ]
143
- })
144
-
145
- const pipeline = device.createRenderPipeline({
146
- label: 'overlaysprite',
147
- vertex: {
148
- module: device.createShaderModule({
149
- code: overlayWGSL
150
- }),
151
- entryPoint: 'vs_main',
152
- buffers: [ nodeData.refs.spritesheet.data.quads.bufferLayout ]
153
- },
154
-
155
- fragment: {
156
- module: device.createShaderModule({
157
- code: overlayWGSL
158
- }),
159
- entryPoint: 'fs_main',
160
- targets: [
161
- // color
162
- {
163
- format: getPreferredFormat(cobalt),
164
- blend: {
165
- color: {
166
- srcFactor: 'src-alpha',
167
- dstFactor: 'one-minus-src-alpha',
168
- },
169
- alpha: {
170
- srcFactor: 'zero',
171
- dstFactor: 'one'
172
- }
173
- }
174
- }
175
- ]
176
- },
177
-
178
- primitive: {
179
- topology: 'triangle-list'
180
- },
181
-
182
- layout: pipelineLayout
183
- })
184
-
185
- return {
186
- // instancedDrawCalls is used to actually perform draw calls within the render pass
187
- // layout is interleaved with baseVtxIdx (the sprite type), and instanceCount (how many sprites)
188
- // [
189
- // baseVtxIdx0, instanceCount0,
190
- // baseVtxIdx1, instanceCount1,
191
- // ...
192
- // ]
193
- instancedDrawCalls: new Uint32Array(MAX_SPRITE_COUNT * 2),
194
- instancedDrawCallCount: 0,
195
-
196
- spriteBuffer,
197
- uniformBuffer,
198
- pipeline,
199
- bindGroupLayout,
200
- bindGroup,
201
-
202
- // actual sprite instance data. ordered by layer, then sprite type
203
- // this is used to update the spriteBuffer.
204
- spriteData: new Float32Array(MAX_SPRITE_COUNT * FLOAT32S_PER_SPRITE),
205
- spriteCount: 0,
206
-
207
- spriteIndices: new Map(), // key is spriteId, value is insert index of the sprite. e.g., 0 means 1st sprite , 1 means 2nd sprite, etc.
208
-
209
- // when a sprite is changed the renderpass is dirty, and should have it's instance data copied to the gpu
210
- dirty: false,
211
- }
212
- }
213
-
214
-
215
- function draw (cobalt, node, commandEncoder) {
216
- const { device } = cobalt
217
-
218
- // on the first render, we should clear the color attachment.
219
- // otherwise load it, so multiple sprite passes can build up data in the color and emissive textures
220
- const loadOp = node.options.loadOp || 'load'
221
-
222
- if (node.data.dirty) {
223
- _rebuildSpriteDrawCalls(node.data)
224
- node.data.dirty = false
225
- }
226
-
227
- // TODO: somehow spriteCount can be come negative?! for now just guard against this
228
- if (node.data.spriteCount > 0) {
229
- const writeLength = node.data.spriteCount * FLOAT32S_PER_SPRITE * Float32Array.BYTES_PER_ELEMENT
230
- device.queue.writeBuffer(node.data.spriteBuffer, 0, node.data.spriteData.buffer, 0, writeLength)
231
- }
232
-
233
-
234
- const renderpass = commandEncoder.beginRenderPass({
235
- label: 'overlay',
236
- colorAttachments: [
237
- // color
238
- {
239
- view: node.refs.color,
240
- clearValue: cobalt.clearValue,
241
- loadOp: loadOp,
242
- storeOp: 'store'
243
- },
244
- ]
245
- })
246
-
247
- renderpass.setPipeline(node.data.pipeline)
248
- renderpass.setBindGroup(0, node.data.bindGroup)
249
- renderpass.setVertexBuffer(0, node.refs.spritesheet.data.quads.buffer)
250
-
251
- // write sprite instance data into the storage buffer, sorted by sprite type. e.g.,
252
- // renderpass.draw(6, 1, 0, 0) // 1 hero instance
253
- // renderpass.draw(6, 14, 6, 1) // 14 bat instances
254
- // renderpass.draw(6, 5, 12, 15) // 5 bullet instances
255
-
256
- // render each sprite type's instances
257
- const vertexCount = 6
258
- let baseInstanceIdx = 0
259
-
260
- for (let i=0; i < node.data.instancedDrawCallCount; i++) {
261
- // [
262
- // baseVtxIdx0, instanceCount0,
263
- // baseVtxIdx1, instanceCount1,
264
- // ...
265
- // ]
266
- const baseVertexIdx = node.data.instancedDrawCalls[i*2 ] * vertexCount
267
- const instanceCount = node.data.instancedDrawCalls[i*2+1]
268
- renderpass.draw(vertexCount, instanceCount, baseVertexIdx, baseInstanceIdx)
269
- baseInstanceIdx += instanceCount
270
- }
271
-
272
- renderpass.end()
273
- }
274
-
275
-
276
- // build instancedDrawCalls
277
- function _rebuildSpriteDrawCalls (renderPass) {
278
- let currentSpriteType = -1
279
- let instanceCount = 0
280
- renderPass.instancedDrawCallCount = 0
281
-
282
- for (let i=0; i < renderPass.spriteCount; i++) {
283
-
284
- // 12th float is order. lower bits 0-15 are spriteType, bits 16-23 are sprite Z index
285
- const spriteType = renderPass.spriteData[i * FLOAT32S_PER_SPRITE + 11] & 0xFFFF
286
-
287
- if (spriteType !== currentSpriteType) {
288
- if (instanceCount > 0) {
289
- renderPass.instancedDrawCalls[renderPass.instancedDrawCallCount * 2] = currentSpriteType
290
- renderPass.instancedDrawCalls[renderPass.instancedDrawCallCount * 2 + 1] = instanceCount
291
- renderPass.instancedDrawCallCount++
292
- }
293
-
294
- currentSpriteType = spriteType
295
- instanceCount = 0
296
- }
297
-
298
- instanceCount++
299
- }
300
-
301
- if (instanceCount > 0) {
302
- renderPass.instancedDrawCalls[renderPass.instancedDrawCallCount * 2] = currentSpriteType
303
- renderPass.instancedDrawCalls[renderPass.instancedDrawCallCount * 2 + 1] = instanceCount
304
- renderPass.instancedDrawCallCount++
305
- }
306
- }
307
-
308
-
309
- function _writeOverlayBuffer (cobalt, nodeData) {
310
- // TODO: I think this buffer can be written just once since the overlays never change. (0,0 always top left corner)
311
- const zoom = 1.0 // cobalt.viewport.zoom
312
-
313
- // TODO: is rounding really needed here?
314
- const GAME_WIDTH = Math.round(cobalt.viewport.width / zoom)
315
- const GAME_HEIGHT = Math.round(cobalt.viewport.height / zoom)
316
-
317
- const projection = mat4.ortho(0, GAME_WIDTH, GAME_HEIGHT, 0, -10.0, 10.0)
318
-
319
- // set x,y,z camera position
320
- vec3.set(0, 0, 0, _tmpVec3)
321
- const view = mat4.translation(_tmpVec3)
322
-
323
- cobalt.device.queue.writeBuffer(nodeData.data.uniformBuffer, 0, view.buffer)
324
- cobalt.device.queue.writeBuffer(nodeData.data.uniformBuffer, 64, projection.buffer)
325
- }
326
-
327
-
328
- function destroy (nodeData) {
329
- nodeData.data.instancedDrawCalls = null
330
-
331
- nodeData.data.bindGroup = null
332
-
333
- nodeData.data.spriteBuffer.destroy()
334
- nodeData.data.spriteBuffer = null
335
-
336
- nodeData.data.uniformBuffer.destroy()
337
- nodeData.data.uniformBuffer = null
338
-
339
- nodeData.data.spriteData = null
340
- nodeData.data.spriteIndices.clear()
341
- nodeData.data.spriteIndices = null
342
- }
343
-
@@ -1,88 +0,0 @@
1
- struct TransformData {
2
- view: mat4x4<f32>,
3
- projection: mat4x4<f32>
4
- };
5
-
6
- struct Sprite {
7
- translate: vec2<f32>,
8
- scale: vec2<f32>,
9
- tint: vec4<f32>,
10
- opacity: f32,
11
- rotation: f32,
12
- //emissiveIntensity: f32,
13
- //sortValue: f32,
14
- };
15
-
16
- struct SpritesBuffer {
17
- models: array<Sprite>,
18
- };
19
-
20
- @binding(0) @group(0) var<uniform> transformUBO: TransformData;
21
- @binding(1) @group(0) var myTexture: texture_2d<f32>;
22
- @binding(2) @group(0) var mySampler: sampler;
23
- @binding(3) @group(0) var<storage, read> sprites : SpritesBuffer;
24
-
25
-
26
- struct Fragment {
27
- @builtin(position) Position : vec4<f32>,
28
- @location(0) TexCoord : vec2<f32>,
29
- @location(1) Tint : vec4<f32>,
30
- @location(2) Opacity: f32,
31
- };
32
-
33
-
34
- @vertex
35
- fn vs_main (@builtin(instance_index) i_id : u32,
36
- @location(0) vertexPosition: vec3<f32>,
37
- @location(1) vertexTexCoord: vec2<f32>) -> Fragment {
38
-
39
- var output : Fragment;
40
-
41
- var sx: f32 = sprites.models[i_id].scale.x;
42
- var sy: f32 = sprites.models[i_id].scale.y;
43
- var sz: f32 = 1.0;
44
-
45
- var rot: f32 = sprites.models[i_id].rotation;
46
-
47
- var tx: f32 = sprites.models[i_id].translate.x;
48
- var ty: f32 = sprites.models[i_id].translate.y;
49
- var tz: f32 = 0;
50
-
51
- var s = sin(rot);
52
- var c = cos(rot);
53
-
54
- // TODO: can probably hardcode a view and projection matrix since this doesn't change
55
-
56
- // https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
57
-
58
- var scaleM: mat4x4<f32> = mat4x4<f32>(sx, 0.0, 0.0, 0.0,
59
- 0.0, sy, 0.0, 0.0,
60
- 0.0, 0.0, sz, 0.0,
61
- 0, 0, 0, 1.0);
62
-
63
- // rotation and translation
64
- var modelM: mat4x4<f32> = mat4x4<f32>(c, s, 0.0, 0.0,
65
- -s, c, 0.0, 0.0,
66
- 0.0, 0.0, 1.0, 0.0,
67
- tx, ty, tz, 1.0) * scaleM;
68
-
69
- output.Position = transformUBO.projection * transformUBO.view * modelM * vec4<f32>(vertexPosition, 1.0);
70
-
71
- output.TexCoord = vertexTexCoord;
72
- output.Tint = sprites.models[i_id].tint;
73
- output.Opacity = sprites.models[i_id].opacity;
74
-
75
- return output;
76
- }
77
-
78
- @fragment
79
- fn fs_main (@location(0) TexCoord: vec2<f32>,
80
- @location(1) Tint: vec4<f32>,
81
- @location(2) Opacity: f32) -> @location(0) vec4<f32> {
82
-
83
- var outColor: vec4<f32> = textureSample(myTexture, mySampler, TexCoord);
84
- var output = vec4<f32>(outColor.rgb * (1.0 - Tint.a) + (Tint.rgb * Tint.a), outColor.a * Opacity);
85
-
86
- return output;
87
- //return vec4<f32>(1.0, 0.0, 1.0, 1.0);
88
- }
@@ -1 +0,0 @@
1
- export const FLOAT32S_PER_SPRITE = 12 // vec2(translate) + vec2(scale) + vec4(tint) + opacity + rotation + emissiveIntensity + sortValue
@@ -1,45 +0,0 @@
1
- import { FLOAT32S_PER_SPRITE } from './constants.js'
2
-
3
-
4
- // return the index into the renderPass. array where the new sprite should be inserted
5
- export default function sortedBinaryInsert (spriteZIndex, spriteType, renderPass) {
6
-
7
- if (renderPass.spriteCount === 0)
8
- return 0
9
-
10
- let low = 0
11
- let high = renderPass.spriteCount - 1
12
-
13
- // order is used to sort the sprite by layer, then sprite type
14
- // zIndex 0-255 (8 bits)
15
- // spriteType 0-65,535 (16 bits)
16
- const order = (spriteZIndex << 16 & 0xFF0000) | (spriteType & 0xFFFF)
17
-
18
- // binary search through spriteData since it's already sorted low to high
19
- while (low <= high) {
20
-
21
- // the 12th float of each sprite stores the sortValue
22
-
23
- const lowOrder = renderPass.spriteData[low * FLOAT32S_PER_SPRITE + 11]
24
- if (order <= lowOrder)
25
- return low
26
-
27
- const highOrder = renderPass.spriteData[high * FLOAT32S_PER_SPRITE + 11]
28
- if (order >= highOrder)
29
- return high + 1
30
-
31
- const mid = Math.floor((low + high) / 2)
32
-
33
- const midOrder = renderPass.spriteData[mid * FLOAT32S_PER_SPRITE + 11]
34
-
35
- if(order === midOrder)
36
- return mid + 1
37
-
38
- if (order > midOrder)
39
- low = mid + 1
40
- else
41
- high = mid - 1
42
- }
43
-
44
- return low
45
- }
@@ -1,215 +0,0 @@
1
- import createSpriteQuads from './create-sprite-quads.js'
2
- import createTextureFromBuffer from '../create-texture-from-buffer.js'
3
- import createTextureFromUrl from '../create-texture-from-url.js'
4
- import readSpriteSheet from './read-spritesheet.js'
5
- import spriteWGSL from './sprite.wgsl'
6
- import round from 'round-half-up-symmetric'
7
- import { mat4, vec3 } from 'wgpu-matrix'
8
-
9
-
10
- // shared spritesheet resource, used by each sprite render node
11
-
12
-
13
- // temporary variables, allocated once to avoid garbage collection
14
- const _tmpVec3 = vec3.create(0, 0, 0)
15
-
16
-
17
- export default {
18
- type: 'cobalt:spritesheet',
19
- refs: [ ],
20
-
21
- // @params Object cobalt renderer world object
22
- // @params Object options optional data passed when initing this node
23
- onInit: async function (cobalt, options={}) {
24
- return init(cobalt, options)
25
- },
26
-
27
- onRun: function (cobalt, node, webGpuCommandEncoder) {
28
- // do whatever you need for this node. webgpu renderpasses, etc.
29
- },
30
-
31
- onDestroy: function (cobalt, node) {
32
- // any cleanup for your node should go here (releasing textures, etc.)
33
- destroy(node)
34
- },
35
-
36
- onResize: function (cobalt, node) {
37
- // do whatever you need when the dimensions of the renderer change (resize textures, etc.)
38
- _writeSpriteBuffer(cobalt, node)
39
- },
40
-
41
- onViewportPosition: function (cobalt, node) {
42
- _writeSpriteBuffer(cobalt, node)
43
- },
44
- }
45
-
46
-
47
- // configure the common settings for sprite rendering
48
- async function init (cobalt, node) {
49
- const { canvas, device } = cobalt
50
-
51
- let spritesheet, colorTexture, emissiveTexture
52
-
53
- const format = node.options.format || 'rgba8unorm'
54
-
55
- if (canvas) {
56
- // browser (canvas) path
57
- spritesheet = await fetchJson(node.options.spriteSheetJsonUrl)
58
- spritesheet = readSpriteSheet(spritesheet)
59
-
60
- colorTexture = await createTextureFromUrl(cobalt, 'sprite', node.options.colorTextureUrl, format)
61
- emissiveTexture = await createTextureFromUrl(cobalt, 'emissive sprite', node.options.emissiveTextureUrl, format)
62
-
63
- // for some reason this needs to be done _after_ creating the material, or the rendering will be blurry
64
- canvas.style.imageRendering = 'pixelated'
65
- }
66
- else {
67
- // sdl + gpu path
68
- spritesheet = readSpriteSheet(node.options.spriteSheetJson)
69
-
70
- colorTexture = await createTextureFromBuffer(cobalt, 'sprite', node.options.colorTexture, format)
71
- emissiveTexture = await createTextureFromBuffer(cobalt, 'emissive sprite', node.options.emissiveTexture, format)
72
- }
73
-
74
- const quads = createSpriteQuads(device, spritesheet)
75
-
76
- const uniformBuffer = device.createBuffer({
77
- size: 64 * 2, // 4x4 matrix with 4 bytes per float32, times 2 matrices (view, projection)
78
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
79
- })
80
-
81
- const bindGroupLayout = device.createBindGroupLayout({
82
- entries: [
83
- {
84
- binding: 0,
85
- visibility: GPUShaderStage.VERTEX,
86
- buffer: { }
87
- },
88
- {
89
- binding: 1,
90
- visibility: GPUShaderStage.FRAGMENT,
91
- texture: { }
92
- },
93
- {
94
- binding: 2,
95
- visibility: GPUShaderStage.FRAGMENT,
96
- sampler: { }
97
- },
98
- {
99
- binding: 3,
100
- visibility: GPUShaderStage.VERTEX,
101
- buffer: {
102
- type: 'read-only-storage'
103
- }
104
- },
105
- {
106
- binding: 4,
107
- visibility: GPUShaderStage.FRAGMENT,
108
- texture: { }
109
- },
110
- ],
111
- })
112
-
113
- const pipelineLayout = device.createPipelineLayout({
114
- bindGroupLayouts: [ bindGroupLayout ]
115
- })
116
-
117
- const pipeline = device.createRenderPipeline({
118
- label: 'spritesheet',
119
- vertex: {
120
- module: device.createShaderModule({
121
- code: spriteWGSL
122
- }),
123
- entryPoint: 'vs_main',
124
- buffers: [ quads.bufferLayout ]
125
- },
126
-
127
- fragment: {
128
- module: device.createShaderModule({
129
- code: spriteWGSL
130
- }),
131
- entryPoint: 'fs_main',
132
- targets: [
133
- // color
134
- {
135
- format: 'rgba16float',
136
- blend: {
137
- color: {
138
- srcFactor: 'src-alpha',
139
- dstFactor: 'one-minus-src-alpha',
140
- },
141
- alpha: {
142
- srcFactor: 'zero',
143
- dstFactor: 'one'
144
- }
145
- }
146
- },
147
-
148
- // emissive
149
- {
150
- format: 'rgba16float',
151
- }
152
- ]
153
- },
154
-
155
- primitive: {
156
- topology: 'triangle-list'
157
- },
158
-
159
- layout: pipelineLayout
160
- })
161
-
162
- return {
163
- pipeline,
164
- uniformBuffer, // perspective and view matrices for the camera
165
- quads,
166
- colorTexture,
167
- emissiveTexture,
168
- bindGroupLayout,
169
- spritesheet,
170
- }
171
- }
172
-
173
-
174
- function destroy (node) {
175
- node.data.quads.buffer.destroy()
176
- node.data.colorTexture.buffer.destroy()
177
- node.data.uniformBuffer.destroy()
178
- node.data.emissiveTexture.texture.destroy()
179
- }
180
-
181
-
182
- async function fetchJson (url) {
183
- const raw = await fetch(url)
184
- return raw.json()
185
- }
186
-
187
-
188
- function _writeSpriteBuffer (cobalt, node) {
189
-
190
- const { device, viewport } = cobalt
191
-
192
- const GAME_WIDTH = viewport.width / viewport.zoom
193
- const GAME_HEIGHT = viewport.height / viewport.zoom
194
-
195
- // left right bottom top near far
196
- const projection = mat4.ortho(0, GAME_WIDTH, GAME_HEIGHT, 0, -10.0, 10.0)
197
-
198
-
199
- // TODO: if this doesn't introduce jitter into the crossroads render, remove this disabled code entirely.
200
- //
201
- // I'm disabling the rounding because I think it fails in cases where units are not expressed in pixels
202
- // e.g., most physics engines operate on meters, not pixels, so we don't want to round to the nearest integer as that
203
- // probably isn't high enough resolution. That would mean the camera could be snapped by up to 0.5 meters
204
- // in that case. I think the better solution for expressing camera position in pixels is to round before calling
205
- // cobalt.setViewportPosition(...)
206
- //
207
- // set 3d camera position
208
- vec3.set(-round(viewport.position[0]), -round(viewport.position[1]), 0, _tmpVec3)
209
- //vec3.set(-viewport.position[0], -viewport.position[1], 0, _tmpVec3)
210
- const view = mat4.translation(_tmpVec3)
211
-
212
- device.queue.writeBuffer(node.data.uniformBuffer, 0, view.buffer)
213
- device.queue.writeBuffer(node.data.uniformBuffer, 64, projection.buffer)
214
- }
215
-
File without changes
File without changes
File without changes
File without changes