@footgun/cobalt 0.1.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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/bundle.js +284 -0
  4. package/cobalt2.jpeg +0 -0
  5. package/esbuild.js +20 -0
  6. package/examples/01-primitives/Game.js +8 -0
  7. package/examples/01-primitives/component-animation.js +8 -0
  8. package/examples/01-primitives/component-transform.js +13 -0
  9. package/examples/01-primitives/constants.js +6 -0
  10. package/examples/01-primitives/deps.js +2 -0
  11. package/examples/01-primitives/entity-sprite.js +47 -0
  12. package/examples/01-primitives/index.html +191 -0
  13. package/examples/01-primitives/system-renderer.js +37 -0
  14. package/examples/02-sprites/Game.js +8 -0
  15. package/examples/02-sprites/assets/spritesheet.json +6276 -0
  16. package/examples/02-sprites/assets/spritesheet.png +0 -0
  17. package/examples/02-sprites/assets/spritesheet_emissive.png +0 -0
  18. package/examples/02-sprites/component-animation.js +8 -0
  19. package/examples/02-sprites/component-transform.js +13 -0
  20. package/examples/02-sprites/constants.js +6 -0
  21. package/examples/02-sprites/deps.js +2 -0
  22. package/examples/02-sprites/entity-sprite.js +47 -0
  23. package/examples/02-sprites/index.html +310 -0
  24. package/examples/02-sprites/system-renderer.js +38 -0
  25. package/examples/03-tiles/Game.js +8 -0
  26. package/examples/03-tiles/assets/spelunky-tiles.png +0 -0
  27. package/examples/03-tiles/assets/spelunky0.png +0 -0
  28. package/examples/03-tiles/assets/spelunky1.png +0 -0
  29. package/examples/03-tiles/component-animation.js +8 -0
  30. package/examples/03-tiles/component-transform.js +13 -0
  31. package/examples/03-tiles/constants.js +6 -0
  32. package/examples/03-tiles/deps.js +2 -0
  33. package/examples/03-tiles/entity-sprite.js +47 -0
  34. package/examples/03-tiles/index.html +309 -0
  35. package/examples/03-tiles/system-renderer.js +38 -0
  36. package/examples/04-overlay/assets/spritesheet.json +22 -0
  37. package/examples/04-overlay/assets/spritesheet.png +0 -0
  38. package/examples/04-overlay/assets/spritesheet_emissive.png +0 -0
  39. package/examples/04-overlay/constants.js +6 -0
  40. package/examples/04-overlay/deps.js +1 -0
  41. package/examples/04-overlay/index.html +133 -0
  42. package/examples/05-bloom/Game.js +8 -0
  43. package/examples/05-bloom/assets/spritesheet.json +6276 -0
  44. package/examples/05-bloom/assets/spritesheet.png +0 -0
  45. package/examples/05-bloom/assets/spritesheet_emissive.png +0 -0
  46. package/examples/05-bloom/component-animation.js +8 -0
  47. package/examples/05-bloom/component-transform.js +13 -0
  48. package/examples/05-bloom/constants.js +6 -0
  49. package/examples/05-bloom/deps.js +2 -0
  50. package/examples/05-bloom/entity-sprite.js +47 -0
  51. package/examples/05-bloom/index.html +357 -0
  52. package/examples/05-bloom/system-renderer.js +38 -0
  53. package/examples/06-displacement/Game.js +8 -0
  54. package/examples/06-displacement/assets/displacement_map_repeat.jpg +0 -0
  55. package/examples/06-displacement/assets/spelunky-tiles.png +0 -0
  56. package/examples/06-displacement/assets/spelunky0.png +0 -0
  57. package/examples/06-displacement/assets/spelunky1.png +0 -0
  58. package/examples/06-displacement/component-animation.js +8 -0
  59. package/examples/06-displacement/component-transform.js +13 -0
  60. package/examples/06-displacement/constants.js +6 -0
  61. package/examples/06-displacement/deps.js +2 -0
  62. package/examples/06-displacement/entity-sprite.js +47 -0
  63. package/examples/06-displacement/index.html +350 -0
  64. package/examples/06-displacement/system-renderer.js +38 -0
  65. package/examples/07-sdl/assets/spritesheet.json +22 -0
  66. package/examples/07-sdl/assets/spritesheet.png +0 -0
  67. package/examples/07-sdl/assets/spritesheet_emissive.png +0 -0
  68. package/examples/07-sdl/main.js +109 -0
  69. package/examples/07-sdl/package.json +19 -0
  70. package/examples/08-light/Game.js +8 -0
  71. package/examples/08-light/assets/spelunky-tiles.png +0 -0
  72. package/examples/08-light/assets/spelunky0.png +0 -0
  73. package/examples/08-light/assets/spelunky1.png +0 -0
  74. package/examples/08-light/constants.js +6 -0
  75. package/examples/08-light/deps.js +2 -0
  76. package/examples/08-light/index.html +477 -0
  77. package/package.json +34 -0
  78. package/src/bloom/bloom.js +467 -0
  79. package/src/bloom/bloom.wgsl +176 -0
  80. package/src/cobalt.js +231 -0
  81. package/src/create-texture-from-buffer.js +39 -0
  82. package/src/create-texture-from-url.js +35 -0
  83. package/src/create-texture.js +46 -0
  84. package/src/deps.js +3 -0
  85. package/src/displacement/composition.wgsl +58 -0
  86. package/src/displacement/displacement-composition.ts +161 -0
  87. package/src/displacement/displacement-parameters-buffer.ts +44 -0
  88. package/src/displacement/displacement-texture.ts +221 -0
  89. package/src/displacement/displacement.js +160 -0
  90. package/src/displacement/displacement.wgsl +31 -0
  91. package/src/displacement/triangles-buffer.ts +95 -0
  92. package/src/fb-blit/fb-blit.js +161 -0
  93. package/src/fb-blit/fb-blit.wgsl +40 -0
  94. package/src/fb-texture/fb-texture.js +56 -0
  95. package/src/light/README.md +61 -0
  96. package/src/light/light.js +148 -0
  97. package/src/light/lights-buffer.ts +98 -0
  98. package/src/light/lights-renderer.ts +278 -0
  99. package/src/light/public-api.js +20 -0
  100. package/src/light/readme/01_illumination.webp +0 -0
  101. package/src/light/readme/02_lights_texture.webp +0 -0
  102. package/src/light/readme/03_lights_texture_decomposed.webp +0 -0
  103. package/src/light/readme/04_lights_texture_mask.webp +0 -0
  104. package/src/light/readme/05_lights_obstacle_decomposition.webp +0 -0
  105. package/src/light/readme/06_lights_hard_cast_shadows.webp +0 -0
  106. package/src/light/texture/lights-texture-initializer.ts +191 -0
  107. package/src/light/texture/lights-texture-mask.ts +286 -0
  108. package/src/light/texture/lights-texture.ts +121 -0
  109. package/src/light/types.ts +23 -0
  110. package/src/light/viewport.ts +63 -0
  111. package/src/overlay/constants.js +1 -0
  112. package/src/overlay/overlay.js +341 -0
  113. package/src/overlay/overlay.wgsl +88 -0
  114. package/src/primitives/constants.js +1 -0
  115. package/src/primitives/primitives.js +252 -0
  116. package/src/primitives/primitives.wgsl +54 -0
  117. package/src/primitives/public-api.js +325 -0
  118. package/src/scene-composite/scene-composite.js +168 -0
  119. package/src/scene-composite/scene-composite.wgsl +94 -0
  120. package/src/sprite/constants.js +1 -0
  121. package/src/sprite/create-sprite-quads.js +60 -0
  122. package/src/sprite/public-api.js +215 -0
  123. package/src/sprite/read-spritesheet.js +103 -0
  124. package/src/sprite/sorted-binary-insert.js +45 -0
  125. package/src/sprite/sprite.js +268 -0
  126. package/src/sprite/sprite.wgsl +103 -0
  127. package/src/sprite/spritesheet.js +212 -0
  128. package/src/tile/atlas.js +193 -0
  129. package/src/tile/tile.js +171 -0
  130. package/src/tile/tile.wgsl +105 -0
  131. package/src/uuid.js +3 -0
@@ -0,0 +1,467 @@
1
+ import bloomWGSL from './bloom.wgsl'
2
+ import createTexture from '../create-texture.js'
3
+
4
+
5
+ // ported from https://github.com/rledrin/WebGPU-Bloom
6
+
7
+ // TODO: investigate dynamic ubo offsets again. I feel like that could be more efficient
8
+ // good example of this: https://github.com/austinEng/webgpu-samples/blob/main/src/sample/animometer/main.ts
9
+
10
+
11
+ const BLOOM_MIP_COUNT = 7
12
+
13
+ const MODE_PREFILTER = 0
14
+ const MODE_DOWNSAMPLE = 1
15
+ const MODE_UPSAMPLE_FIRST = 2
16
+ const MODE_UPSAMPLE = 3
17
+
18
+
19
+ export default {
20
+ type: 'cobalt:bloom',
21
+ refs: [
22
+ { name: 'emissive', type: 'textureView', format: 'rgba16', access: 'read' },
23
+ { name: 'hdr', type: 'textureView', format: 'rgba16', access: 'read' },
24
+ { name: 'bloom', type: 'textureView', format: 'rgba16', access: 'readwrite' },
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.data, 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
+ // do whatever you need when the dimensions of the renderer change (resize textures, etc.)
44
+ resize(cobalt, node)
45
+ },
46
+
47
+ onViewportPosition: function (cobalt, node) { },
48
+ }
49
+
50
+
51
+ function init (cobalt, nodeData) {
52
+ const { device } = cobalt
53
+ const viewportWidth = cobalt.viewport.width
54
+ const viewportHeight = cobalt.viewport.height
55
+
56
+ const bloom_mat = {
57
+ compute_pipeline: null,
58
+ bind_group: [ ],
59
+ bind_group_layout: [ ],
60
+ bind_groups_textures: [ ],
61
+ }
62
+
63
+ const layout = device.createBindGroupLayout({
64
+ entries: [
65
+ {
66
+ binding: 0,
67
+ visibility: GPUShaderStage.COMPUTE,
68
+ storageTexture: {
69
+ access: 'write-only',
70
+ format: 'rgba16float',
71
+ viewDimension: '2d'
72
+ }
73
+ },
74
+ {
75
+ binding: 1,
76
+ visibility: GPUShaderStage.COMPUTE,
77
+ texture: {
78
+ sampleType: 'float',
79
+ viewDimension: '2d',
80
+ multisampled: false
81
+ }
82
+ },
83
+ {
84
+ binding: 2,
85
+ visibility: GPUShaderStage.COMPUTE,
86
+ texture: {
87
+ sampleType: 'float',
88
+ viewDimension: '2d',
89
+ multisampled: false
90
+ }
91
+ },
92
+ {
93
+ binding: 3,
94
+ visibility: GPUShaderStage.COMPUTE,
95
+ sampler: { }
96
+ },
97
+ {
98
+ binding: 4,
99
+ visibility: GPUShaderStage.COMPUTE,
100
+ buffer: {
101
+ type: 'uniform',
102
+ //minBindingSize: 24 // sizeOf(BloomParam)
103
+ }
104
+ },
105
+ {
106
+ binding: 5,
107
+ visibility: GPUShaderStage.COMPUTE,
108
+ buffer: {
109
+ type: 'uniform',
110
+ //minBindingSize: 4 // sizeOf(lode_mode Param)
111
+ }
112
+ },
113
+ ]
114
+ })
115
+
116
+ bloom_mat.bind_group_layout.push(layout)
117
+
118
+ bloom_mat.bind_groups_textures.push(createTexture(
119
+ device,
120
+ 'bloom downsampler image 0',
121
+ viewportWidth / 2,
122
+ viewportHeight / 2,
123
+ BLOOM_MIP_COUNT,
124
+ 'rgba16float',
125
+ GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
126
+ ))
127
+
128
+ bloom_mat.bind_groups_textures.push(createTexture(
129
+ device,
130
+ 'bloom downsampler image 1',
131
+ viewportWidth / 2,
132
+ viewportHeight / 2,
133
+ BLOOM_MIP_COUNT,
134
+ 'rgba16float',
135
+ GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
136
+ ))
137
+
138
+ // link bloom_mat.bind_groups_textures[2]) to bloom_mat.bloomTexture
139
+ //bloom_mat.bind_groups_textures.push(bloom_mat.bloomTexture)
140
+ bloom_mat.bind_groups_textures.push(nodeData.refs.bloom.data)
141
+ /*
142
+ bloom_mat.bind_groups_textures.push(createTexture(
143
+ device,
144
+ 'bloom upsampler image',
145
+ viewportWidth / 2,
146
+ viewportHeight / 2,
147
+ BLOOM_MIP_COUNT,
148
+ 'rgba16float',
149
+ GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
150
+ ))
151
+ */
152
+
153
+ const compute_pipeline_layout = device.createPipelineLayout({
154
+ bindGroupLayouts: bloom_mat.bind_group_layout
155
+ })
156
+
157
+ const compute_pipeline = device.createComputePipeline({
158
+ layout: compute_pipeline_layout,
159
+ compute: {
160
+ module: device.createShaderModule({
161
+ code: bloomWGSL,
162
+ }),
163
+ entryPoint: 'cs_main',
164
+ },
165
+ })
166
+
167
+ set_all_bind_group(cobalt, bloom_mat, nodeData)
168
+
169
+ bloom_mat.compute_pipeline = compute_pipeline
170
+
171
+ // return whatever data you want to store for this node
172
+ return bloom_mat
173
+ }
174
+
175
+
176
+ function set_all_bind_group (cobalt, bloom_mat, node) {
177
+
178
+ const { refs } = node
179
+
180
+ const { device } = cobalt
181
+
182
+ // create a buffer that holds static parameters, shared across all bloom bind groups
183
+ const bloom_threshold = node.options.bloom_threshold ?? 0.1 //1.0
184
+ const bloom_knee = node.options.bloom_knee ?? 0.2
185
+ const combine_constant = node.options.bloom_combine_constant ?? 0.68
186
+
187
+ const dat = new Float32Array([ bloom_threshold,
188
+ bloom_threshold - bloom_knee,
189
+ bloom_knee * 2.0,
190
+ 0.25 / bloom_knee,
191
+ combine_constant,
192
+ // required byte alignment bs
193
+ 0, 0, 0
194
+ ])
195
+
196
+ const params_buf = device.createBuffer({
197
+ label: 'bloom static parameters buffer',
198
+ size: dat.byteLength, // vec4<f32> and f32 and u32 with 4 bytes per float32 and 4 bytes per u32
199
+ mappedAtCreation: true,
200
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
201
+ })
202
+
203
+ new Float32Array(params_buf.getMappedRange()).set(dat)
204
+
205
+ params_buf.unmap()
206
+
207
+ bloom_mat.bind_group.length = 0
208
+ bloom_mat.params_buf = params_buf
209
+
210
+ // Prefilter bind group
211
+ bloom_mat.bind_group.push(create_bloom_bind_group(
212
+ device,
213
+ bloom_mat,
214
+ bloom_mat.bind_groups_textures[0].mip_view[0],
215
+ refs.emissive.data.view,
216
+ refs.hdr.data.view, // unused here, only for upsample passes
217
+ refs.hdr.data.sampler,
218
+ params_buf,
219
+ MODE_PREFILTER << 16 | 0, // mode_lod value
220
+ ));
221
+
222
+ // Downsample bind groups
223
+ for ( let i=1; i < BLOOM_MIP_COUNT; i++) {
224
+ // Ping
225
+ bloom_mat.bind_group.push(create_bloom_bind_group(
226
+ device,
227
+ bloom_mat,
228
+ bloom_mat.bind_groups_textures[1].mip_view[i],
229
+ bloom_mat.bind_groups_textures[0].view,
230
+ refs.hdr.data.view, // unused here, only for upsample passes
231
+ refs.hdr.data.sampler,
232
+ params_buf,
233
+ MODE_DOWNSAMPLE << 16 | (i - 1), // mode_lod value
234
+ ))
235
+
236
+ // Pong
237
+ bloom_mat.bind_group.push(create_bloom_bind_group(
238
+ device,
239
+ bloom_mat,
240
+ bloom_mat.bind_groups_textures[0].mip_view[i],
241
+ bloom_mat.bind_groups_textures[1].view,
242
+ refs.hdr.data.view, // unused here, only for upsample passes
243
+ refs.hdr.data.sampler,
244
+ params_buf,
245
+ MODE_DOWNSAMPLE << 16 | i, // mode_lod value
246
+ ))
247
+ }
248
+
249
+ // First Upsample
250
+ bloom_mat.bind_group.push(create_bloom_bind_group(
251
+ device,
252
+ bloom_mat,
253
+ bloom_mat.bind_groups_textures[2].mip_view[BLOOM_MIP_COUNT - 1],
254
+ bloom_mat.bind_groups_textures[0].view,
255
+ refs.hdr.data.view, // unused here, only for upsample passes
256
+ refs.hdr.data.sampler,
257
+ params_buf,
258
+ MODE_UPSAMPLE_FIRST << 16 | (BLOOM_MIP_COUNT - 2), // mode_lod value
259
+ ))
260
+
261
+ let o = true
262
+
263
+ // Upsample
264
+ for (let i = BLOOM_MIP_COUNT-2; i >= 0; i--) {
265
+ if (o) {
266
+ bloom_mat.bind_group.push(create_bloom_bind_group(
267
+ device,
268
+ bloom_mat,
269
+ bloom_mat.bind_groups_textures[1].mip_view[i],
270
+ bloom_mat.bind_groups_textures[0].view,
271
+ bloom_mat.bind_groups_textures[2].view,
272
+ refs.hdr.data.sampler,
273
+ params_buf,
274
+ MODE_UPSAMPLE << 16 | i, // mode_lod value
275
+ ))
276
+ o = false
277
+ } else {
278
+ bloom_mat.bind_group.push(create_bloom_bind_group(
279
+ device,
280
+ bloom_mat,
281
+ bloom_mat.bind_groups_textures[2].mip_view[i],
282
+ bloom_mat.bind_groups_textures[0].view,
283
+ bloom_mat.bind_groups_textures[1].view,
284
+ refs.hdr.data.sampler,
285
+ params_buf,
286
+ MODE_UPSAMPLE << 16 | i, // mode_lod value
287
+ ))
288
+ o = true
289
+ }
290
+ }
291
+ }
292
+
293
+
294
+ function create_bloom_bind_group (device, bloom_mat, output_image, input_image, bloom_image, sampler, params_buf, mode_lod) {
295
+
296
+ const dat2 = new Uint32Array([ mode_lod ])
297
+
298
+ const lod_buf = device.createBuffer({
299
+ label: 'bloom static mode_lod buffer',
300
+ size: dat2.byteLength,
301
+ mappedAtCreation: true,
302
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
303
+ })
304
+
305
+ new Uint32Array(lod_buf.getMappedRange()).set(dat2)
306
+
307
+ lod_buf.unmap()
308
+
309
+ return device.createBindGroup({
310
+ label: 'bloom bind group layout',
311
+ layout: bloom_mat.bind_group_layout[0],
312
+ entries: [
313
+ {
314
+ binding: 0,
315
+ resource: output_image
316
+ },
317
+ {
318
+ binding: 1,
319
+ resource: input_image
320
+ },
321
+ {
322
+ binding: 2,
323
+ resource: bloom_image
324
+ },
325
+ {
326
+ binding: 3,
327
+ resource: sampler
328
+ },
329
+ {
330
+ binding: 4,
331
+ resource: {
332
+ buffer: params_buf
333
+ }
334
+ },
335
+ {
336
+ binding: 5,
337
+ resource: {
338
+ buffer: lod_buf
339
+ }
340
+ }
341
+ ]
342
+ })
343
+ }
344
+
345
+
346
+ function draw (cobalt, bloom_mat, commandEncoder) {
347
+ const MODE_PREFILTER = 0
348
+ const MODE_DOWNSAMPLE = 1
349
+ const MODE_UPSAMPLE_FIRST = 2
350
+ const MODE_UPSAMPLE = 3
351
+
352
+ let bind_group_index = 0
353
+
354
+ const compute_pass = commandEncoder.beginComputePass({
355
+ label: 'bloom Compute Pass',
356
+ })
357
+
358
+ compute_pass.setPipeline(bloom_mat.compute_pipeline)
359
+
360
+
361
+ // PreFilter
362
+ compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
363
+ bind_group_index += 1
364
+
365
+ let mip_size = get_mip_size(0, bloom_mat.bind_groups_textures[0])
366
+
367
+ compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
368
+
369
+ // Downsample
370
+ for (let i=1; i < BLOOM_MIP_COUNT; i++) {
371
+ mip_size = get_mip_size(i, bloom_mat.bind_groups_textures[0])
372
+
373
+ // Ping
374
+ compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
375
+ bind_group_index += 1
376
+ compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
377
+
378
+ // Pong
379
+ compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
380
+ bind_group_index += 1
381
+ compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
382
+ }
383
+
384
+
385
+ // First Upsample
386
+ compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
387
+ bind_group_index += 1
388
+ mip_size = get_mip_size(BLOOM_MIP_COUNT - 1, bloom_mat.bind_groups_textures[2])
389
+ compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
390
+
391
+
392
+ // Upsample
393
+ for (let i=BLOOM_MIP_COUNT - 2; i >= 0; i--) {
394
+ mip_size = get_mip_size(i, bloom_mat.bind_groups_textures[2])
395
+
396
+ compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
397
+ bind_group_index += 1
398
+ compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
399
+ }
400
+
401
+
402
+ compute_pass.end()
403
+ }
404
+
405
+
406
+ function get_mip_size (current_mip, texture) {
407
+ let width = texture.size.width
408
+ let height = texture.size.height
409
+
410
+ for (let i =0; i < current_mip; i++) {
411
+ width /= 2
412
+ height /= 2
413
+ }
414
+
415
+ return { width, height, depthOrArrayLayers: 1 }
416
+ }
417
+
418
+
419
+ function resize (cobalt, nodeData) {
420
+ const { device } = cobalt
421
+ const bloom_mat = nodeData.data
422
+ destroy(bloom_mat)
423
+
424
+ bloom_mat.bind_groups_textures.push(createTexture(
425
+ device,
426
+ 'bloom downsampler image 0',
427
+ cobalt.viewport.width / 2,
428
+ cobalt.viewport.height / 2,
429
+ BLOOM_MIP_COUNT,
430
+ 'rgba16float',
431
+ GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
432
+ ))
433
+
434
+ bloom_mat.bind_groups_textures.push(createTexture(
435
+ device,
436
+ 'bloom downsampler image 1',
437
+ cobalt.viewport.width / 2,
438
+ cobalt.viewport.height / 2,
439
+ BLOOM_MIP_COUNT,
440
+ 'rgba16float',
441
+ GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
442
+ ))
443
+
444
+ // link bloom_mat.bind_groups_textures[2]) to bloom_mat.bloomTexture
445
+ bloom_mat.bind_groups_textures.push(nodeData.refs.bloom.data)
446
+ /*
447
+ bloom_mat.bind_groups_textures.push(createTexture(
448
+ device,
449
+ 'bloom upsampler image',
450
+ cobalt.viewport.width / 2,
451
+ cobalt.viewport.height / 2,
452
+ BLOOM_MIP_COUNT,
453
+ 'rgba16float',
454
+ GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
455
+ ))
456
+ */
457
+
458
+ set_all_bind_group(cobalt, bloom_mat, nodeData)
459
+ }
460
+
461
+
462
+ function destroy (bloom_mat) {
463
+ for (const t of bloom_mat.bind_groups_textures)
464
+ t.texture.destroy()
465
+
466
+ bloom_mat.bind_groups_textures.length = 0
467
+ }
@@ -0,0 +1,176 @@
1
+ // Compute Shader
2
+
3
+ const BLOOM_MIP_COUNT: i32 = 7;
4
+
5
+ const MODE_PREFILTER: u32 = 0u;
6
+ const MODE_DOWNSAMPLE: u32 = 1u;
7
+ const MODE_UPSAMPLE_FIRST: u32 = 2u;
8
+ const MODE_UPSAMPLE: u32 = 3u;
9
+
10
+ const EPSILON: f32 = 1.0e-4;
11
+
12
+ struct bloom_param {
13
+ parameters: vec4<f32>, // (x) threshold, (y) threshold - knee, (z) knee * 2, (w) 0.25 / knee
14
+ combine_constant: f32,
15
+ doop: u32,
16
+ ferp: u32,
17
+ }
18
+
19
+ struct mode_lod_param {
20
+ mode_lod: u32,
21
+ }
22
+
23
+
24
+ @group(0) @binding(0) var output_texture: texture_storage_2d<rgba16float, write>;
25
+ @group(0) @binding(1) var input_texture: texture_2d<f32>;
26
+ @group(0) @binding(2) var bloom_texture: texture_2d<f32>;
27
+ @group(0) @binding(3) var samp: sampler;
28
+ @group(0) @binding(4) var<uniform> param: bloom_param;
29
+ @group(0) @binding(5) var<uniform> pc: mode_lod_param;
30
+
31
+
32
+ // PushConstants don't work in webgpu in chrome because they've been removed from the spec for v1 :(
33
+ // might be added after v1 though https://github.com/gpuweb/gpuweb/issues/75
34
+ //var<push_constant> pc: PushConstants;
35
+
36
+
37
+ // Quadratic color thresholding
38
+ // curve = (threshold - knee, knee * 2, 0.25 / knee)
39
+ fn QuadraticThreshold(color: vec4<f32>, threshold: f32, curve: vec3<f32>) -> vec4<f32>
40
+ {
41
+ // Maximum pixel brightness
42
+ let brightness = max(max(color.r, color.g), color.b);
43
+ // Quadratic curve
44
+ var rq: f32 = clamp(brightness - curve.x, 0.0, curve.y);
45
+ rq = curve.z * (rq * rq);
46
+ let ret_color = color * max(rq, brightness - threshold) / max(brightness, EPSILON);
47
+ return ret_color;
48
+ }
49
+
50
+ fn Prefilter(color: vec4<f32>, uv: vec2<f32>) -> vec4<f32>
51
+ {
52
+ let clamp_value = 20.0;
53
+ var c = min(vec4<f32>(clamp_value), color);
54
+ c = QuadraticThreshold(color, param.parameters.x, param.parameters.yzw);
55
+ return c;
56
+ }
57
+
58
+ fn DownsampleBox13(tex: texture_2d<f32>, lod: f32, uv: vec2<f32>, tex_size: vec2<f32>) -> vec3<f32>
59
+ {
60
+ // Center
61
+ let A = textureSampleLevel(tex, samp, uv, lod).rgb;
62
+
63
+ let texel_size = tex_size * 0.5; // Sample from center of texels
64
+
65
+ // Inner box
66
+ let B = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-1.0, -1.0), lod).rgb;
67
+ let C = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-1.0, 1.0), lod).rgb;
68
+ let D = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(1.0, 1.0), lod).rgb;
69
+ let E = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(1.0, -1.0), lod).rgb;
70
+
71
+ // Outer box
72
+ let F = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, -2.0), lod).rgb;
73
+ let G = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, 0.0), lod).rgb;
74
+ let H = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(0.0, 2.0), lod).rgb;
75
+ let I = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 2.0), lod).rgb;
76
+ let J = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 2.0), lod).rgb;
77
+ let K = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 0.0), lod).rgb;
78
+ let L = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, -2.0), lod).rgb;
79
+ let M = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(0.0, -2.0), lod).rgb;
80
+
81
+ // Weights
82
+ var result: vec3<f32> = vec3<f32>(0.0);
83
+ // Inner box
84
+ result = result + (B + C + D + E) * 0.5;
85
+ // Bottom-left box
86
+ result = result + (F + G + A + M) * 0.125;
87
+ // Top-left box
88
+ result = result + (G + H + I + A) * 0.125;
89
+ // Top-right box
90
+ result = result + (A + I + J + K) * 0.125;
91
+ // Bottom-right box
92
+ result = result + (M + A + K + L) * 0.125;
93
+
94
+ // 4 samples each
95
+ result = result * 0.25;
96
+
97
+ return result;
98
+ }
99
+
100
+ fn UpsampleTent9(tex: texture_2d<f32>, lod: f32, uv: vec2<f32>, texel_size: vec2<f32>, radius: f32) -> vec3<f32>
101
+ {
102
+ let offset = texel_size.xyxy * vec4<f32>(1.0, 1.0, -1.0, 0.0) * radius;
103
+
104
+ // Center
105
+ var result: vec3<f32> = textureSampleLevel(tex, samp, uv, lod).rgb * 4.0;
106
+
107
+ result = result + textureSampleLevel(tex, samp, uv - offset.xy, lod).rgb;
108
+ result = result + textureSampleLevel(tex, samp, uv - offset.wy, lod).rgb * 2.0;
109
+ result = result + textureSampleLevel(tex, samp, uv - offset.zy, lod).rgb;
110
+
111
+ result = result + textureSampleLevel(tex, samp, uv + offset.zw, lod).rgb * 2.0;
112
+ result = result + textureSampleLevel(tex, samp, uv + offset.xw, lod).rgb * 2.0;
113
+
114
+ result = result + textureSampleLevel(tex, samp, uv + offset.zy, lod).rgb;
115
+ result = result + textureSampleLevel(tex, samp, uv + offset.wy, lod).rgb * 2.0;
116
+ result = result + textureSampleLevel(tex, samp, uv + offset.xy, lod).rgb;
117
+
118
+ return result * (1.0 / 16.0);
119
+ }
120
+
121
+ fn combine(ex_color: vec3<f32>, color_to_add: vec3<f32>, combine_constant: f32) -> vec3<f32>
122
+ {
123
+ let existing_color = ex_color + (-color_to_add);
124
+ let blended_color = (combine_constant * existing_color) + color_to_add;
125
+ return blended_color;
126
+ }
127
+
128
+
129
+ @compute @workgroup_size(8, 4, 1)
130
+ fn cs_main(@builtin(global_invocation_id) global_invocation_id: vec3<u32>)
131
+ {
132
+ let mode = pc.mode_lod >> 16u;
133
+ let lod = pc.mode_lod & 65535u;
134
+
135
+ let imgSize = textureDimensions(output_texture);
136
+
137
+ if (global_invocation_id.x < u32(imgSize.x) && global_invocation_id.y < u32(imgSize.y)) {
138
+
139
+ var texCoords: vec2<f32> = vec2<f32>(f32(global_invocation_id.x) / f32(imgSize.x), f32(global_invocation_id.y) / f32(imgSize.y));
140
+ texCoords = texCoords + (1.0 / vec2<f32>(imgSize)) * 0.5;
141
+
142
+ let texSize = vec2<f32>(textureDimensions(input_texture, i32(lod)));
143
+ var color: vec4<f32> = vec4<f32>(1.0);
144
+
145
+ if (mode == MODE_PREFILTER)
146
+ {
147
+ color = vec4<f32>(DownsampleBox13(input_texture, f32(lod), texCoords, 1.0 / texSize), 1.0);
148
+ color = Prefilter(color, texCoords);
149
+ }
150
+ else if (mode == MODE_DOWNSAMPLE)
151
+ {
152
+ color = vec4<f32>(DownsampleBox13(input_texture, f32(lod), texCoords, 1.0 / texSize), 1.0);
153
+ }
154
+ else if (mode == MODE_UPSAMPLE_FIRST)
155
+ {
156
+ let bloomTexSize = textureDimensions(input_texture, i32(lod) + 1);
157
+ let sampleScale = 1.0;
158
+ let upsampledTexture = UpsampleTent9(input_texture, f32(lod) + 1.0, texCoords, 1.0 / vec2<f32>(bloomTexSize), sampleScale);
159
+
160
+ let existing = textureSampleLevel(input_texture, samp, texCoords, f32(lod)).rgb;
161
+ color = vec4<f32>(combine(existing, upsampledTexture, param.combine_constant), 1.0);
162
+ }
163
+ else if (mode == MODE_UPSAMPLE)
164
+ {
165
+ let bloomTexSize = textureDimensions(bloom_texture, i32(lod) + 1);
166
+ let sampleScale = 1.0;
167
+ let upsampledTexture = UpsampleTent9(bloom_texture, f32(lod) + 1.0, texCoords, 1.0 / vec2<f32>(bloomTexSize), sampleScale);
168
+
169
+ let existing = textureSampleLevel(input_texture, samp, texCoords, f32(lod)).rgb;
170
+ color = vec4<f32>(combine(existing, upsampledTexture, param.combine_constant), 1.0);
171
+ }
172
+
173
+ textureStore(output_texture, vec2<i32>(global_invocation_id.xy), color);
174
+ }
175
+ }
176
+