@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/CHANGELOG.md +4 -0
- package/biome.json +23 -0
- package/bundle.js +16 -14546
- package/esbuild.js +1 -1
- package/examples/01-primitives/main.js +22 -0
- package/package.json +1 -1
- package/src/bloom/bloom.js +188 -174
- package/src/cobalt.js +48 -66
- package/src/create-texture-from-buffer.js +9 -8
- package/src/create-texture-from-url.js +22 -10
- package/src/create-texture.js +15 -14
- package/src/displacement/displacement.js +42 -47
- package/src/fb-blit/fb-blit.js +34 -36
- package/src/fb-texture/fb-texture.js +30 -15
- package/src/get-preferred-format.js +3 -5
- package/src/light/light.js +30 -34
- package/src/light/public-api.js +6 -8
- package/src/primitives/constants.js +1 -1
- package/src/primitives/primitives.js +61 -64
- package/src/primitives/public-api.js +76 -73
- package/src/scene-composite/scene-composite.js +77 -78
- package/src/sprite/public-api.js +31 -46
- package/src/sprite/sprite.js +156 -154
- package/src/sprite-hdr/public-api.js +23 -38
- package/src/sprite-hdr/sprite.js +166 -165
- package/src/spritesheet/read-spritesheet.js +27 -118
- package/src/spritesheet/spritesheet.js +37 -21
- package/src/tile-hdr/atlas.js +54 -50
- package/src/tile-hdr/tile.js +40 -42
- package/src/uuid.js +2 -2
- package/src/spritesheet/create-sprite-quads.js +0 -60
package/src/sprite/public-api.js
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
import uuid from '../uuid.js'
|
|
2
1
|
import { vec2, vec4 } from 'wgpu-matrix'
|
|
3
|
-
|
|
2
|
+
import uuid from '../uuid.js'
|
|
4
3
|
|
|
5
4
|
// returns a unique identifier for the created sprite
|
|
6
|
-
export function addSprite
|
|
7
|
-
|
|
5
|
+
export function addSprite(cobalt, renderPass, name, position, scale, tint, opacity, rotation) {
|
|
8
6
|
const { idByName } = renderPass.refs.spritesheet.data
|
|
9
7
|
|
|
10
8
|
renderPass.data.sprites.push({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
position: vec2.clone(position),
|
|
10
|
+
sizeX: 1,
|
|
11
|
+
sizeY: 1,
|
|
12
|
+
scale: vec2.clone(scale),
|
|
13
|
+
rotation,
|
|
14
|
+
opacity,
|
|
15
|
+
tint: vec4.clone(tint),
|
|
16
|
+
spriteID: idByName.get(name),
|
|
17
|
+
id: uuid(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return renderPass.data.sprites.at(-1).id
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (let i=0; i < renderPass.data.sprites.length; i++) {
|
|
23
|
+
export function removeSprite(cobalt, renderPass, id) {
|
|
24
|
+
for (let i = 0; i < renderPass.data.sprites.length; i++) {
|
|
27
25
|
if (renderPass.data.sprites[i].id === id) {
|
|
28
26
|
renderPass.data.sprites.splice(i, 1)
|
|
29
27
|
return
|
|
@@ -31,65 +29,52 @@ export function removeSprite (cobalt, renderPass, id) {
|
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
35
32
|
// remove all sprites
|
|
36
|
-
export function clear
|
|
37
|
-
|
|
33
|
+
export function clear(cobalt, renderPass) {
|
|
34
|
+
renderPass.data.sprites.length = 0
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
export function setSpriteName (cobalt, renderPass, id, name) {
|
|
37
|
+
export function setSpriteName(cobalt, renderPass, id, name) {
|
|
42
38
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
43
39
|
|
|
44
|
-
if (!sprite)
|
|
45
|
-
return
|
|
40
|
+
if (!sprite) return
|
|
46
41
|
|
|
47
42
|
const { idByName } = renderPass.refs.spritesheet.data
|
|
48
43
|
|
|
49
44
|
sprite.spriteID = idByName.get(name)
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
|
|
53
|
-
export function setSpritePosition (cobalt, renderPass, id, position) {
|
|
47
|
+
export function setSpritePosition(cobalt, renderPass, id, position) {
|
|
54
48
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
55
|
-
if (!sprite)
|
|
56
|
-
return
|
|
49
|
+
if (!sprite) return
|
|
57
50
|
|
|
58
51
|
vec2.copy(position, sprite.position)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
export function setSpriteTint (cobalt, renderPass, id, tint) {
|
|
54
|
+
export function setSpriteTint(cobalt, renderPass, id, tint) {
|
|
63
55
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
64
|
-
if (!sprite)
|
|
65
|
-
return
|
|
56
|
+
if (!sprite) return
|
|
66
57
|
|
|
67
58
|
vec4.copy(tint, sprite.tint)
|
|
68
59
|
}
|
|
69
60
|
|
|
70
|
-
|
|
71
|
-
export function setSpriteOpacity (cobalt, renderPass, id, opacity) {
|
|
61
|
+
export function setSpriteOpacity(cobalt, renderPass, id, opacity) {
|
|
72
62
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
73
|
-
if (!sprite)
|
|
74
|
-
return
|
|
63
|
+
if (!sprite) return
|
|
75
64
|
|
|
76
65
|
sprite.opacity = opacity
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
export function setSpriteRotation (cobalt, renderPass, id, rotation) {
|
|
68
|
+
export function setSpriteRotation(cobalt, renderPass, id, rotation) {
|
|
81
69
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
82
|
-
if (!sprite)
|
|
83
|
-
return
|
|
70
|
+
if (!sprite) return
|
|
84
71
|
|
|
85
72
|
sprite.rotation = rotation
|
|
86
73
|
}
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
export function setSpriteScale (cobalt, renderPass, id, scale) {
|
|
75
|
+
export function setSpriteScale(cobalt, renderPass, id, scale) {
|
|
90
76
|
const sprite = renderPass.data.sprites.find((s) => s.id === id)
|
|
91
|
-
if (!sprite)
|
|
92
|
-
return
|
|
77
|
+
if (!sprite) return
|
|
93
78
|
|
|
94
|
-
vec2.copy(scale, sprite.scale)
|
|
79
|
+
vec2.copy(scale, sprite.scale)
|
|
95
80
|
}
|
package/src/sprite/sprite.js
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
|
+
import round from 'round-half-up-symmetric'
|
|
2
|
+
import { mat4, vec3 } from 'wgpu-matrix'
|
|
1
3
|
import getPreferredFormat from '../get-preferred-format.js'
|
|
2
4
|
import * as publicAPI from './public-api.js'
|
|
3
|
-
import spriteWGSL
|
|
4
|
-
import round from 'round-half-up-symmetric'
|
|
5
|
-
import { mat4, vec3 } from 'wgpu-matrix'
|
|
6
|
-
|
|
5
|
+
import spriteWGSL from './sprite.wgsl'
|
|
7
6
|
|
|
8
7
|
// temporary variables, allocated once to avoid garbage collection
|
|
9
8
|
const _tmpVec3 = vec3.create(0, 0, 0)
|
|
10
9
|
|
|
11
10
|
// Packed instance layout: 48 bytes (aligned for vec4 fetch)
|
|
12
|
-
const INSTANCE_STRIDE = 64
|
|
11
|
+
const INSTANCE_STRIDE = 64
|
|
13
12
|
|
|
14
13
|
// Offsets inside one instance (bytes)
|
|
15
|
-
const OFF_POS = 0
|
|
16
|
-
const OFF_SIZE = 8
|
|
17
|
-
const OFF_SCALE = 16
|
|
18
|
-
const OFF_TINT = 24
|
|
19
|
-
const OFF_SPRITEID = 40
|
|
20
|
-
const OFF_OPACITY = 44
|
|
21
|
-
const OFF_ROT = 48
|
|
22
|
-
|
|
14
|
+
const OFF_POS = 0 // float32x2 (8B)
|
|
15
|
+
const OFF_SIZE = 8 // float32x2 (8B)
|
|
16
|
+
const OFF_SCALE = 16 // float32x2 (8B)
|
|
17
|
+
const OFF_TINT = 24 // float32x4 (16B)
|
|
18
|
+
const OFF_SPRITEID = 40 // uint32 (4B)
|
|
19
|
+
const OFF_OPACITY = 44 // float32 (4B)
|
|
20
|
+
const OFF_ROT = 48 // float32 (4B)
|
|
23
21
|
|
|
24
22
|
export default {
|
|
25
|
-
type:
|
|
23
|
+
type: 'cobalt:sprite',
|
|
26
24
|
refs: [
|
|
27
|
-
{ name:
|
|
25
|
+
{ name: 'spritesheet', type: 'customResource', access: 'read' },
|
|
28
26
|
{
|
|
29
|
-
name:
|
|
30
|
-
type:
|
|
31
|
-
format:
|
|
32
|
-
access:
|
|
27
|
+
name: 'color',
|
|
28
|
+
type: 'textureView',
|
|
29
|
+
format: 'rgba8unorm',
|
|
30
|
+
access: 'write',
|
|
33
31
|
},
|
|
34
32
|
],
|
|
35
33
|
|
|
@@ -38,32 +36,38 @@ export default {
|
|
|
38
36
|
// @params Object cobalt renderer world object
|
|
39
37
|
// @params Object options optional data passed when initing this node
|
|
40
38
|
onInit: async function (cobalt, options = {}) {
|
|
41
|
-
return init(cobalt, options)
|
|
39
|
+
return init(cobalt, options)
|
|
42
40
|
},
|
|
43
41
|
|
|
44
42
|
onRun: function (cobalt, node, webGpuCommandEncoder) {
|
|
45
43
|
// do whatever you need for this node. webgpu renderpasses, etc.
|
|
46
|
-
draw(cobalt, node, webGpuCommandEncoder)
|
|
44
|
+
draw(cobalt, node, webGpuCommandEncoder)
|
|
47
45
|
},
|
|
48
46
|
|
|
49
47
|
// Clean up GPU resources. Most WebGPU objects are GC-managed and don't
|
|
50
48
|
// expose destroy(); buffers/textures/query-sets do.
|
|
51
49
|
onDestroy: function (cobalt, node) {
|
|
52
50
|
// Explicitly destroy GPU resources that have a destroy() method
|
|
53
|
-
try {
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
try {
|
|
52
|
+
node.data.instanceBuf?.destroy()
|
|
53
|
+
} catch {}
|
|
54
|
+
try {
|
|
55
|
+
node.data.spriteBuf?.destroy()
|
|
56
|
+
} catch {}
|
|
57
|
+
try {
|
|
58
|
+
node.data.uniformBuffer?.destroy()
|
|
59
|
+
} catch {}
|
|
56
60
|
|
|
57
61
|
// These do not have destroy(); drop references to let GC reclaim
|
|
58
|
-
node.data.pipeline = null
|
|
59
|
-
node.data.bindGroup = null
|
|
60
|
-
node.data.bindGroupLayout = null
|
|
62
|
+
node.data.pipeline = null // GPURenderPipeline
|
|
63
|
+
node.data.bindGroup = null // GPUBindGroup
|
|
64
|
+
node.data.bindGroupLayout = null // GPUBindGroupLayout
|
|
61
65
|
|
|
62
66
|
// CPU-side allocations
|
|
63
|
-
node.data.instanceStaging = null
|
|
64
|
-
node.data.instanceView = null
|
|
65
|
-
node.data.sprites.length = 0
|
|
66
|
-
node.data.visible.length = 0
|
|
67
|
+
node.data.instanceStaging = null
|
|
68
|
+
node.data.instanceView = null
|
|
69
|
+
node.data.sprites.length = 0
|
|
70
|
+
node.data.visible.length = 0
|
|
67
71
|
},
|
|
68
72
|
|
|
69
73
|
onResize: function (cobalt, node) {
|
|
@@ -78,108 +82,108 @@ export default {
|
|
|
78
82
|
customFunctions: {
|
|
79
83
|
...publicAPI,
|
|
80
84
|
},
|
|
81
|
-
}
|
|
85
|
+
}
|
|
82
86
|
|
|
83
87
|
async function init(cobalt, nodeData) {
|
|
84
|
-
const { device } = cobalt
|
|
88
|
+
const { device } = cobalt
|
|
85
89
|
|
|
86
90
|
const { descs, names } = nodeData.refs.spritesheet.data.spritesheet
|
|
87
91
|
|
|
88
92
|
const uniformBuffer = device.createBuffer({
|
|
89
93
|
size: 64 * 2, // 4x4 matrix with 4 bytes per float32, times 2 matrices (view, projection)
|
|
90
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
94
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
91
95
|
})
|
|
92
96
|
|
|
93
97
|
// Pack into std430-like struct (4*float*? + vec2 + vec2 → 32 bytes). We'll just write tightly as 8 floats.
|
|
94
|
-
const BYTES_PER_DESC = 8 * 4
|
|
95
|
-
const buf = new ArrayBuffer(BYTES_PER_DESC * descs.length)
|
|
96
|
-
const f32 = new Float32Array(buf)
|
|
97
|
-
for (let i=0;i<descs.length;i++){
|
|
98
|
-
const d = descs[i]
|
|
99
|
-
const base = i * 8
|
|
100
|
-
f32[base+0] = d.UvOrigin[0]
|
|
101
|
-
f32[base+1] = d.UvOrigin[1]
|
|
102
|
-
f32[base+2] = d.UvSpan[0]
|
|
103
|
-
f32[base+3] = d.UvSpan[1]
|
|
104
|
-
f32[base+4] = d.FrameSize[0]
|
|
105
|
-
f32[base+5] = d.FrameSize[1]
|
|
106
|
-
f32[base+6] = d.CenterOffset[0]
|
|
107
|
-
f32[base+7] = d.CenterOffset[1]
|
|
98
|
+
const BYTES_PER_DESC = 8 * 4 // 8 float32s
|
|
99
|
+
const buf = new ArrayBuffer(BYTES_PER_DESC * descs.length)
|
|
100
|
+
const f32 = new Float32Array(buf)
|
|
101
|
+
for (let i = 0; i < descs.length; i++) {
|
|
102
|
+
const d = descs[i]
|
|
103
|
+
const base = i * 8
|
|
104
|
+
f32[base + 0] = d.UvOrigin[0]
|
|
105
|
+
f32[base + 1] = d.UvOrigin[1]
|
|
106
|
+
f32[base + 2] = d.UvSpan[0]
|
|
107
|
+
f32[base + 3] = d.UvSpan[1]
|
|
108
|
+
f32[base + 4] = d.FrameSize[0]
|
|
109
|
+
f32[base + 5] = d.FrameSize[1]
|
|
110
|
+
f32[base + 6] = d.CenterOffset[0]
|
|
111
|
+
f32[base + 7] = d.CenterOffset[1]
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// create buffer for sprite uv lookup
|
|
111
115
|
const spriteBuf = device.createBuffer({
|
|
112
|
-
label:
|
|
116
|
+
label: 'sprite desc table',
|
|
113
117
|
size: Math.max(16, buf.byteLength),
|
|
114
118
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
115
|
-
})
|
|
119
|
+
})
|
|
116
120
|
|
|
117
|
-
device.queue.writeBuffer(spriteBuf, 0, buf)
|
|
121
|
+
device.queue.writeBuffer(spriteBuf, 0, buf)
|
|
118
122
|
|
|
119
123
|
// --- Instance buffer (growable) ---
|
|
120
|
-
const instanceCap = 1024
|
|
124
|
+
const instanceCap = 1024
|
|
121
125
|
const instanceBuf = device.createBuffer({
|
|
122
|
-
label:
|
|
126
|
+
label: 'sprite instances',
|
|
123
127
|
size: INSTANCE_STRIDE * instanceCap,
|
|
124
128
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
125
|
-
})
|
|
126
|
-
const instanceStaging = new ArrayBuffer(INSTANCE_STRIDE * instanceCap)
|
|
127
|
-
const instanceView = new DataView(instanceStaging)
|
|
129
|
+
})
|
|
130
|
+
const instanceStaging = new ArrayBuffer(INSTANCE_STRIDE * instanceCap)
|
|
131
|
+
const instanceView = new DataView(instanceStaging)
|
|
128
132
|
|
|
129
133
|
// --- Pipeline ---
|
|
130
|
-
const shader = device.createShaderModule({ code: spriteWGSL })
|
|
134
|
+
const shader = device.createShaderModule({ code: spriteWGSL })
|
|
131
135
|
const bgl = device.createBindGroupLayout({
|
|
132
136
|
entries: [
|
|
133
137
|
{
|
|
134
138
|
binding: 0,
|
|
135
139
|
visibility: GPUShaderStage.VERTEX,
|
|
136
|
-
buffer: { type:
|
|
140
|
+
buffer: { type: 'uniform' },
|
|
137
141
|
},
|
|
138
142
|
{
|
|
139
143
|
binding: 1,
|
|
140
144
|
visibility: GPUShaderStage.FRAGMENT,
|
|
141
|
-
sampler: { type:
|
|
145
|
+
sampler: { type: 'filtering' },
|
|
142
146
|
},
|
|
143
147
|
{
|
|
144
148
|
binding: 2,
|
|
145
149
|
visibility: GPUShaderStage.FRAGMENT,
|
|
146
|
-
texture: { sampleType:
|
|
150
|
+
texture: { sampleType: 'float' },
|
|
147
151
|
},
|
|
148
152
|
{
|
|
149
153
|
binding: 3,
|
|
150
154
|
visibility: GPUShaderStage.VERTEX,
|
|
151
|
-
buffer: { type:
|
|
155
|
+
buffer: { type: 'read-only-storage' },
|
|
152
156
|
},
|
|
153
157
|
],
|
|
154
|
-
})
|
|
158
|
+
})
|
|
155
159
|
const pipelineLayout = device.createPipelineLayout({
|
|
156
160
|
bindGroupLayouts: [bgl],
|
|
157
|
-
})
|
|
161
|
+
})
|
|
158
162
|
|
|
159
163
|
const instLayout = {
|
|
160
164
|
arrayStride: INSTANCE_STRIDE,
|
|
161
|
-
stepMode:
|
|
165
|
+
stepMode: 'instance',
|
|
162
166
|
attributes: [
|
|
163
|
-
{ shaderLocation: 0, offset: OFF_POS, format:
|
|
164
|
-
{ shaderLocation: 1, offset: OFF_SIZE, format:
|
|
165
|
-
{ shaderLocation: 2, offset: OFF_SCALE, format:
|
|
166
|
-
{ shaderLocation: 3, offset: OFF_TINT, format:
|
|
167
|
-
{ shaderLocation: 4, offset: OFF_SPRITEID, format:
|
|
168
|
-
{ shaderLocation: 5, offset: OFF_OPACITY, format:
|
|
169
|
-
{ shaderLocation: 6, offset: OFF_ROT, format:
|
|
167
|
+
{ shaderLocation: 0, offset: OFF_POS, format: 'float32x2' },
|
|
168
|
+
{ shaderLocation: 1, offset: OFF_SIZE, format: 'float32x2' },
|
|
169
|
+
{ shaderLocation: 2, offset: OFF_SCALE, format: 'float32x2' },
|
|
170
|
+
{ shaderLocation: 3, offset: OFF_TINT, format: 'float32x4' },
|
|
171
|
+
{ shaderLocation: 4, offset: OFF_SPRITEID, format: 'uint32' },
|
|
172
|
+
{ shaderLocation: 5, offset: OFF_OPACITY, format: 'float32' },
|
|
173
|
+
{ shaderLocation: 6, offset: OFF_ROT, format: 'float32' },
|
|
170
174
|
],
|
|
171
|
-
}
|
|
175
|
+
}
|
|
172
176
|
|
|
173
177
|
const pipeline = device.createRenderPipeline({
|
|
174
178
|
layout: pipelineLayout,
|
|
175
179
|
vertex: {
|
|
176
180
|
module: shader,
|
|
177
|
-
entryPoint:
|
|
181
|
+
entryPoint: 'vs_main',
|
|
178
182
|
buffers: [instLayout],
|
|
179
183
|
},
|
|
180
184
|
fragment: {
|
|
181
185
|
module: shader,
|
|
182
|
-
entryPoint:
|
|
186
|
+
entryPoint: 'fs_main',
|
|
183
187
|
targets: [
|
|
184
188
|
// color
|
|
185
189
|
{
|
|
@@ -191,17 +195,15 @@ async function init(cobalt, nodeData) {
|
|
|
191
195
|
},
|
|
192
196
|
alpha: {
|
|
193
197
|
srcFactor: 'zero',
|
|
194
|
-
dstFactor: 'one'
|
|
195
|
-
}
|
|
196
|
-
}
|
|
198
|
+
dstFactor: 'one',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
197
201
|
},
|
|
198
202
|
],
|
|
199
203
|
},
|
|
200
|
-
primitive: { topology:
|
|
204
|
+
primitive: { topology: 'triangle-strip', cullMode: 'none' },
|
|
201
205
|
multisample: { count: 1 },
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
const bindGroupLayout = bgl;
|
|
206
|
+
})
|
|
205
207
|
|
|
206
208
|
const bindGroup = device.createBindGroup({
|
|
207
209
|
layout: bgl,
|
|
@@ -213,11 +215,11 @@ async function init(cobalt, nodeData) {
|
|
|
213
215
|
{ binding: 2, resource: nodeData.refs.spritesheet.data.colorTexture.view },
|
|
214
216
|
{ binding: 3, resource: { buffer: spriteBuf } },
|
|
215
217
|
],
|
|
216
|
-
})
|
|
218
|
+
})
|
|
217
219
|
|
|
218
220
|
return {
|
|
219
|
-
sprites: [
|
|
220
|
-
visible: [
|
|
221
|
+
sprites: [],
|
|
222
|
+
visible: [],
|
|
221
223
|
visibleCount: 0,
|
|
222
224
|
viewRect: { x: 0, y: 0, w: 0, h: 0 },
|
|
223
225
|
|
|
@@ -234,36 +236,29 @@ async function init(cobalt, nodeData) {
|
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
|
|
237
|
-
|
|
238
|
-
function ensureCapacity (cobalt, node, nInstances) {
|
|
239
|
-
|
|
239
|
+
function ensureCapacity(cobalt, node, nInstances) {
|
|
240
240
|
const { instanceCap } = node.data
|
|
241
241
|
|
|
242
|
-
if (nInstances <= instanceCap)
|
|
243
|
-
return;
|
|
242
|
+
if (nInstances <= instanceCap) return
|
|
244
243
|
|
|
245
244
|
let newCap = instanceCap
|
|
246
|
-
if (newCap === 0)
|
|
247
|
-
newCap = 1024;
|
|
245
|
+
if (newCap === 0) newCap = 1024
|
|
248
246
|
|
|
249
|
-
while (newCap < nInstances)
|
|
250
|
-
newCap *= 2;
|
|
247
|
+
while (newCap < nInstances) newCap *= 2
|
|
251
248
|
|
|
252
|
-
node.data.instanceBuf.destroy()
|
|
249
|
+
node.data.instanceBuf.destroy()
|
|
253
250
|
node.data.instanceBuf = cobalt.device.createBuffer({
|
|
254
251
|
size: INSTANCE_STRIDE * newCap,
|
|
255
252
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
256
|
-
})
|
|
253
|
+
})
|
|
257
254
|
|
|
258
|
-
node.data.instanceStaging = new ArrayBuffer(INSTANCE_STRIDE * newCap)
|
|
259
|
-
node.data.instanceView = new DataView(node.data.instanceStaging)
|
|
260
|
-
node.data.instanceCap = newCap
|
|
255
|
+
node.data.instanceStaging = new ArrayBuffer(INSTANCE_STRIDE * newCap)
|
|
256
|
+
node.data.instanceView = new DataView(node.data.instanceStaging)
|
|
257
|
+
node.data.instanceCap = newCap
|
|
261
258
|
}
|
|
262
259
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const { device, context } = cobalt;
|
|
260
|
+
function draw(cobalt, node, commandEncoder) {
|
|
261
|
+
const { device, context } = cobalt
|
|
267
262
|
|
|
268
263
|
const { instanceView, instanceBuf, instanceStaging, pipeline, bindGroup } = node.data
|
|
269
264
|
|
|
@@ -275,21 +270,26 @@ function draw (cobalt, node, commandEncoder) {
|
|
|
275
270
|
viewRect.y = cobalt.viewport.position[1]
|
|
276
271
|
viewRect.w = cobalt.viewport.width
|
|
277
272
|
viewRect.h = cobalt.viewport.height
|
|
278
|
-
|
|
273
|
+
|
|
279
274
|
node.data.visibleCount = 0
|
|
280
275
|
|
|
281
276
|
for (const s of node.data.sprites) {
|
|
282
|
-
const d = descs[s.spriteID]
|
|
283
|
-
if (!d)
|
|
284
|
-
continue;
|
|
277
|
+
const d = descs[s.spriteID]
|
|
278
|
+
if (!d) continue
|
|
285
279
|
|
|
286
280
|
// avoid sprite viewport culling when drawing in screenspace mode (typically ui/hud layers)
|
|
287
281
|
if (!node.options.isScreenSpace) {
|
|
288
|
-
const sx =
|
|
289
|
-
const sy =
|
|
290
|
-
const rad = Math.hypot(sx, sy)
|
|
291
|
-
const x = s.position[0],
|
|
292
|
-
|
|
282
|
+
const sx = d.FrameSize[0] * s.sizeX * s.scale[0] * 0.5
|
|
283
|
+
const sy = d.FrameSize[1] * s.sizeY * s.scale[1] * 0.5
|
|
284
|
+
const rad = Math.hypot(sx, sy)
|
|
285
|
+
const x = s.position[0],
|
|
286
|
+
y = s.position[1]
|
|
287
|
+
if (
|
|
288
|
+
x + rad < viewRect.x ||
|
|
289
|
+
x - rad > viewRect.x + viewRect.w ||
|
|
290
|
+
y + rad < viewRect.y ||
|
|
291
|
+
y - rad > viewRect.y + viewRect.h
|
|
292
|
+
)
|
|
293
293
|
continue
|
|
294
294
|
}
|
|
295
295
|
|
|
@@ -300,38 +300,44 @@ function draw (cobalt, node, commandEncoder) {
|
|
|
300
300
|
ensureCapacity(cobalt, node, node.data.visibleCount)
|
|
301
301
|
|
|
302
302
|
// Pack instances into staging buffer
|
|
303
|
-
for (let i=0; i < node.data.visibleCount; i++){
|
|
304
|
-
const base = i * INSTANCE_STRIDE
|
|
305
|
-
const s = node.data.visible[i]
|
|
306
|
-
const tint = s.tint
|
|
307
|
-
|
|
308
|
-
instanceView.setFloat32(base + OFF_POS + 0, s.position[0], true)
|
|
309
|
-
instanceView.setFloat32(base + OFF_POS + 4, s.position[1], true)
|
|
310
|
-
|
|
311
|
-
instanceView.setFloat32(base + OFF_SIZE + 0, s.sizeX, true)
|
|
312
|
-
instanceView.setFloat32(base + OFF_SIZE + 4, s.sizeY, true)
|
|
313
|
-
|
|
314
|
-
instanceView.setFloat32(base + OFF_SCALE + 0, s.scale[0], true)
|
|
315
|
-
instanceView.setFloat32(base + OFF_SCALE + 4, s.scale[1], true)
|
|
316
|
-
|
|
317
|
-
instanceView.setFloat32(base + OFF_TINT + 0, tint[0], true)
|
|
318
|
-
instanceView.setFloat32(base + OFF_TINT + 4, tint[1], true)
|
|
319
|
-
instanceView.setFloat32(base + OFF_TINT + 8, tint[2], true)
|
|
320
|
-
instanceView.setFloat32(base + OFF_TINT + 12, tint[3], true)
|
|
321
|
-
|
|
322
|
-
instanceView.setUint32(base + OFF_SPRITEID, s.spriteID >>> 0, true)
|
|
323
|
-
|
|
324
|
-
instanceView.setFloat32(base + OFF_OPACITY, s.opacity, true)
|
|
325
|
-
|
|
326
|
-
instanceView.setFloat32(base + OFF_ROT, s.rotation, true)
|
|
303
|
+
for (let i = 0; i < node.data.visibleCount; i++) {
|
|
304
|
+
const base = i * INSTANCE_STRIDE
|
|
305
|
+
const s = node.data.visible[i]
|
|
306
|
+
const tint = s.tint
|
|
307
|
+
|
|
308
|
+
instanceView.setFloat32(base + OFF_POS + 0, s.position[0], true)
|
|
309
|
+
instanceView.setFloat32(base + OFF_POS + 4, s.position[1], true)
|
|
310
|
+
|
|
311
|
+
instanceView.setFloat32(base + OFF_SIZE + 0, s.sizeX, true)
|
|
312
|
+
instanceView.setFloat32(base + OFF_SIZE + 4, s.sizeY, true)
|
|
313
|
+
|
|
314
|
+
instanceView.setFloat32(base + OFF_SCALE + 0, s.scale[0], true)
|
|
315
|
+
instanceView.setFloat32(base + OFF_SCALE + 4, s.scale[1], true)
|
|
316
|
+
|
|
317
|
+
instanceView.setFloat32(base + OFF_TINT + 0, tint[0], true)
|
|
318
|
+
instanceView.setFloat32(base + OFF_TINT + 4, tint[1], true)
|
|
319
|
+
instanceView.setFloat32(base + OFF_TINT + 8, tint[2], true)
|
|
320
|
+
instanceView.setFloat32(base + OFF_TINT + 12, tint[3], true)
|
|
321
|
+
|
|
322
|
+
instanceView.setUint32(base + OFF_SPRITEID, s.spriteID >>> 0, true)
|
|
323
|
+
|
|
324
|
+
instanceView.setFloat32(base + OFF_OPACITY, s.opacity, true)
|
|
325
|
+
|
|
326
|
+
instanceView.setFloat32(base + OFF_ROT, s.rotation, true)
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
device.queue.writeBuffer(
|
|
329
|
+
device.queue.writeBuffer(
|
|
330
|
+
instanceBuf,
|
|
331
|
+
0,
|
|
332
|
+
instanceStaging,
|
|
333
|
+
0,
|
|
334
|
+
node.data.visibleCount * INSTANCE_STRIDE,
|
|
335
|
+
)
|
|
330
336
|
|
|
331
337
|
const loadOp = node.options.loadOp || 'load'
|
|
332
338
|
|
|
333
339
|
const pass = commandEncoder.beginRenderPass({
|
|
334
|
-
label:
|
|
340
|
+
label: 'sprite renderpass',
|
|
335
341
|
colorAttachments: [
|
|
336
342
|
// color
|
|
337
343
|
{
|
|
@@ -341,43 +347,39 @@ function draw (cobalt, node, commandEncoder) {
|
|
|
341
347
|
storeOp: 'store',
|
|
342
348
|
},
|
|
343
349
|
],
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
pass.setPipeline(pipeline);
|
|
347
|
-
pass.setBindGroup(0, bindGroup);
|
|
348
|
-
pass.setVertexBuffer(0, instanceBuf);
|
|
349
|
-
if (node.data.visibleCount)
|
|
350
|
-
pass.draw(4, node.data.visibleCount, 0, 0); // triangle strip, 4 verts per instance
|
|
351
|
-
pass.end();
|
|
352
|
-
}
|
|
353
|
-
|
|
350
|
+
})
|
|
354
351
|
|
|
355
|
-
|
|
352
|
+
pass.setPipeline(pipeline)
|
|
353
|
+
pass.setBindGroup(0, bindGroup)
|
|
354
|
+
pass.setVertexBuffer(0, instanceBuf)
|
|
355
|
+
if (node.data.visibleCount) pass.draw(4, node.data.visibleCount, 0, 0) // triangle strip, 4 verts per instance
|
|
356
|
+
pass.end()
|
|
357
|
+
}
|
|
356
358
|
|
|
359
|
+
function _writeSpriteBuffer(cobalt, node) {
|
|
357
360
|
const { device, viewport } = cobalt
|
|
358
361
|
|
|
359
362
|
const GAME_WIDTH = viewport.width / viewport.zoom
|
|
360
363
|
const GAME_HEIGHT = viewport.height / viewport.zoom
|
|
361
364
|
|
|
362
365
|
// left right bottom top near far
|
|
363
|
-
const projection = mat4.ortho(0,
|
|
366
|
+
const projection = mat4.ortho(0, GAME_WIDTH, GAME_HEIGHT, 0, -10.0, 10.0)
|
|
364
367
|
|
|
365
368
|
// set 3d camera position
|
|
366
|
-
if (
|
|
369
|
+
if (node.options.isScreenSpace) {
|
|
367
370
|
vec3.set(0, 0, 0, _tmpVec3)
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
371
|
+
} else {
|
|
370
372
|
// TODO: if this doesn't introduce jitter into the crossroads render, remove this disabled code entirely.
|
|
371
373
|
//
|
|
372
374
|
// I'm disabling the rounding because I think it fails in cases where units are not expressed in pixels
|
|
373
|
-
// e.g., most physics engines operate on meters, not pixels, so we don't want to round to the nearest integer as that
|
|
375
|
+
// e.g., most physics engines operate on meters, not pixels, so we don't want to round to the nearest integer as that
|
|
374
376
|
// probably isn't high enough resolution. That would mean the camera could be snapped by up to 0.5 meters
|
|
375
377
|
// in that case. I think the better solution for expressing camera position in pixels is to round before calling
|
|
376
378
|
// cobalt.setViewportPosition(...)
|
|
377
379
|
//
|
|
378
380
|
vec3.set(-round(viewport.position[0]), -round(viewport.position[1]), 0, _tmpVec3)
|
|
379
381
|
//vec3.set(-viewport.position[0], -viewport.position[1], 0, _tmpVec3)
|
|
380
|
-
}
|
|
382
|
+
}
|
|
381
383
|
|
|
382
384
|
const view = mat4.translation(_tmpVec3)
|
|
383
385
|
|