@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.
- package/CHANGELOG.md +16 -0
- package/bundle.js +1313 -1301
- package/examples/01-primitives/index.html +1 -1
- package/examples/02-sprites/entity-sprite.js +10 -15
- package/examples/02-sprites/hdr.html +321 -0
- package/examples/02-sprites/index.html +21 -120
- package/examples/02-sprites/system-renderer.js +2 -2
- package/examples/03-tiles/index.html +33 -35
- package/examples/03-tiles/system-renderer.js +2 -2
- package/examples/04-overlay/index.html +178 -21
- package/examples/05-bloom/index.html +23 -23
- package/examples/05-bloom/system-renderer.js +2 -2
- package/examples/06-displacement/index.html +20 -112
- package/examples/06-displacement/system-renderer.js +2 -2
- package/examples/08-light/index.html +51 -123
- package/package.json +1 -1
- package/src/cobalt.js +8 -8
- package/src/sprite/public-api.js +57 -177
- package/src/sprite/sprite.js +301 -177
- package/src/sprite/sprite.wgsl +68 -87
- package/src/sprite-hdr/public-api.js +95 -0
- package/src/sprite-hdr/sprite.js +414 -0
- package/src/sprite-hdr/sprite.wgsl +101 -0
- package/src/{sprite → spritesheet}/create-sprite-quads.js +11 -11
- package/src/{sprite → spritesheet}/read-spritesheet.js +62 -28
- package/src/spritesheet/spritesheet.js +75 -0
- package/src/{tile → tile-hdr}/atlas.js +5 -3
- package/src/{tile → tile-hdr}/tile.js +9 -6
- package/examples/04-overlay/deps.js +0 -1
- package/src/overlay/constants.js +0 -1
- package/src/overlay/overlay.js +0 -343
- package/src/overlay/overlay.wgsl +0 -88
- package/src/sprite/constants.js +0 -1
- package/src/sprite/sorted-binary-insert.js +0 -45
- package/src/sprite/spritesheet.js +0 -215
- /package/examples/02-sprites/{Game.js → Global.js} +0 -0
- /package/examples/03-tiles/{Game.js → Global.js} +0 -0
- /package/examples/05-bloom/{Game.js → Global.js} +0 -0
- /package/examples/06-displacement/{Game.js → Global.js} +0 -0
- /package/examples/08-light/{Game.js → Global.js} +0 -0
- /package/src/{tile → tile-hdr}/tile.wgsl +0 -0
package/src/overlay/overlay.js
DELETED
|
@@ -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
|
-
|
package/src/overlay/overlay.wgsl
DELETED
|
@@ -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
|
-
}
|
package/src/sprite/constants.js
DELETED
|
@@ -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
|