@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,221 @@
1
+ /// <reference types="@webgpu/types"/>
2
+
3
+ import displacementWGSL from './displacement.wgsl'
4
+ import * as wgpuMatrix from "wgpu-matrix";
5
+ import { TrianglesBuffer } from "./triangles-buffer";
6
+
7
+
8
+ type Viewport = {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ readonly zoom: number;
12
+ readonly position: [number, number];
13
+ };
14
+
15
+ type Parameters = {
16
+ readonly device: GPUDevice;
17
+
18
+ readonly width: number;
19
+ readonly height: number;
20
+
21
+ readonly blurFactor: number;
22
+
23
+ readonly trianglesBuffer: TrianglesBuffer;
24
+ };
25
+
26
+ type TextureWithView = {
27
+ readonly texture: GPUTexture;
28
+ readonly view: GPUTextureView;
29
+ };
30
+
31
+ class DisplacementTexture {
32
+ private readonly device: GPUDevice;
33
+ private readonly format: GPUTextureFormat = "r8unorm";
34
+ private readonly downsizeFactor: number;
35
+ private readonly multisample: number;
36
+
37
+ private textureSimple: TextureWithView;
38
+ private textureMultisampled: TextureWithView | null = null;
39
+
40
+ private readonly renderPipeline: GPURenderPipeline;
41
+ private readonly bindgroup: GPUBindGroup;
42
+ private readonly uniformsBuffer: GPUBuffer;
43
+
44
+ private readonly trianglesBuffer: TrianglesBuffer;
45
+
46
+ public constructor(params: Parameters) {
47
+ this.device = params.device;
48
+ this.downsizeFactor = params.blurFactor;
49
+ this.multisample = this.downsizeFactor > 1 ? 4 : 1;
50
+
51
+ [this.textureSimple, this.textureMultisampled] = this.createTextures(params.width, params.height);
52
+
53
+ this.trianglesBuffer = params.trianglesBuffer;
54
+
55
+ const shaderModule = this.device.createShaderModule({
56
+ label: "DisplacementTexture shader module",
57
+ code: displacementWGSL,
58
+
59
+ });
60
+
61
+ this.renderPipeline = this.device.createRenderPipeline({
62
+ label: "DisplacementTexture renderpipeline",
63
+ layout: "auto",
64
+ vertex: {
65
+ module: shaderModule,
66
+ entryPoint: "main_vertex",
67
+ buffers: [
68
+ {
69
+ attributes: [
70
+ {
71
+ shaderLocation: 0,
72
+ offset: 0,
73
+ format: "float32x2",
74
+ },
75
+ ],
76
+ arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
77
+ stepMode: "vertex",
78
+ },
79
+ ],
80
+ },
81
+ fragment: {
82
+ module: shaderModule,
83
+ entryPoint: "main_fragment",
84
+ targets: [{
85
+ format: this.format,
86
+ }],
87
+ },
88
+ primitive: {
89
+ cullMode: "none",
90
+ topology: "triangle-list",
91
+ },
92
+ multisample: {
93
+ count: this.multisample,
94
+ },
95
+ });
96
+
97
+ this.uniformsBuffer = this.device.createBuffer({
98
+ label: "DisplacementTexture uniforms buffer",
99
+ size: 64,
100
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
101
+ });
102
+
103
+ this.bindgroup = this.device.createBindGroup({
104
+ label: "DisplacementTexture bindgroup",
105
+ layout: this.renderPipeline.getBindGroupLayout(0),
106
+ entries: [
107
+ {
108
+ binding: 0,
109
+ resource: { buffer: this.uniformsBuffer },
110
+ },
111
+ ],
112
+ });
113
+ }
114
+
115
+ public update(commandEncoder: GPUCommandEncoder): void {
116
+ const targetTexture = this.textureMultisampled ?? this.textureSimple;
117
+
118
+ const textureRenderpassColorAttachment: GPURenderPassColorAttachment = {
119
+ view: targetTexture.view,
120
+ clearValue: [0, 0, 0, 1],
121
+ loadOp: "clear",
122
+ storeOp: "store",
123
+ };
124
+ if (this.textureMultisampled) {
125
+ textureRenderpassColorAttachment.resolveTarget = this.textureSimple.view;
126
+ }
127
+
128
+ const renderpassEncoder = commandEncoder.beginRenderPass({
129
+ label: "DisplacementTexture render to texture renderpass",
130
+ colorAttachments: [textureRenderpassColorAttachment],
131
+ });
132
+
133
+ const [textureWidth, textureHeight] = [targetTexture.texture.width, targetTexture.texture.height];
134
+ renderpassEncoder.setViewport(0, 0, textureWidth, textureHeight, 0, 1);
135
+ renderpassEncoder.setScissorRect(0, 0, textureWidth, textureHeight);
136
+ renderpassEncoder.setPipeline(this.renderPipeline);
137
+ renderpassEncoder.setBindGroup(0, this.bindgroup);
138
+ renderpassEncoder.setVertexBuffer(0, this.trianglesBuffer.bufferGpu);
139
+ renderpassEncoder.draw(3 * this.trianglesBuffer.spriteCount);
140
+ renderpassEncoder.end();
141
+ };
142
+
143
+ public resize(width: number, height: number): void {
144
+ this.textureSimple.texture.destroy();
145
+ this.textureMultisampled?.texture.destroy();
146
+
147
+ [this.textureSimple, this.textureMultisampled] = this.createTextures(width, height);
148
+ }
149
+
150
+ public setViewport(viewport: Viewport): void {
151
+ const scaling = [1, 1, 1];
152
+ const rotation = 0;
153
+ const translation = [1, 1, 0];
154
+
155
+ const modelMatrix = wgpuMatrix.mat4.identity();
156
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.scaling(scaling), modelMatrix, modelMatrix);
157
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.rotationZ(rotation), modelMatrix, modelMatrix);
158
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.translation(translation), modelMatrix, modelMatrix);
159
+
160
+ const viewMatrix = wgpuMatrix.mat4.translation([-viewport.position[0], -viewport.position[1], 0]);
161
+
162
+ const gameWidth = viewport.width / viewport.zoom
163
+ const gameHeight = viewport.height / viewport.zoom
164
+ // left right bottom top near far
165
+ const projectionMatrix = wgpuMatrix.mat4.ortho(0, gameWidth, gameHeight, 0, -10.0, 10.0)
166
+
167
+ const mvpMatrix = wgpuMatrix.mat4.identity();
168
+ wgpuMatrix.mat4.multiply(viewMatrix, modelMatrix, mvpMatrix);
169
+ wgpuMatrix.mat4.multiply(projectionMatrix, mvpMatrix, mvpMatrix);
170
+
171
+ this.device.queue.writeBuffer(this.uniformsBuffer, 0, mvpMatrix);
172
+ }
173
+
174
+ public getView(): GPUTextureView {
175
+ return this.textureSimple.view;
176
+ }
177
+
178
+ public destroy(): void {
179
+ this.textureSimple.texture.destroy();
180
+ this.textureMultisampled?.texture.destroy();
181
+ this.uniformsBuffer.destroy();
182
+ }
183
+
184
+ private createTextures(width: number, height: number): [TextureWithView, TextureWithView | null] {
185
+ const texture = this.device.createTexture({
186
+ label: "DisplacementTexture texture",
187
+ size: [
188
+ Math.ceil(width / this.downsizeFactor),
189
+ Math.ceil(height / this.downsizeFactor),
190
+ ],
191
+ format: this.format,
192
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
193
+ });
194
+ const textureSimple = {
195
+ texture,
196
+ view: texture.createView({ label: "DisplacementTexture texture view" }),
197
+ };
198
+
199
+ let textureMultisampled: TextureWithView | null = null;
200
+ if (this.multisample > 1) {
201
+ const textureMulti = this.device.createTexture({
202
+ label: "DisplacementTexture texture multisampled",
203
+ size: [texture.width, texture.height],
204
+ format: texture.format,
205
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
206
+ sampleCount: this.multisample,
207
+ });
208
+ textureMultisampled = {
209
+ texture: textureMulti,
210
+ view: textureMulti.createView({ label: "DisplacementTexture texture multisampled view" }),
211
+ };
212
+ }
213
+
214
+ return [textureSimple, textureMultisampled];
215
+ }
216
+ }
217
+
218
+ export {
219
+ DisplacementTexture
220
+ };
221
+
@@ -0,0 +1,160 @@
1
+ import { TrianglesBuffer } from './triangles-buffer.js'
2
+ import { DisplacementParametersBuffer } from './displacement-parameters-buffer.js'
3
+ import { DisplacementComposition } from './displacement-composition.js'
4
+ import { DisplacementTexture } from './displacement-texture.js'
5
+
6
+
7
+ // adapted to webgpu from https://github.com/pixijs/pixijs/tree/dev/packages/filter-displacement
8
+
9
+ export default {
10
+ type: 'cobalt:displacement',
11
+ refs: [
12
+
13
+ // input framebuffer texture with the scene drawn
14
+ { name: 'color', type: 'textureView', format: 'bgra8unorm', access: 'read' },
15
+
16
+ // displacement map (perlin noise texture works well here)
17
+ { name: 'map', type: 'cobaltTexture', format: 'bgra8unorm', access: 'read' },
18
+
19
+ // result we're writing to
20
+ { name: 'out', type: 'textureView', format: 'bgra8unorm', access: 'write' },
21
+ ],
22
+
23
+ // cobalt event handling functions
24
+
25
+ // @params Object cobalt renderer world object
26
+ // @params Object options optional data passed when initing this node
27
+ onInit: async function (cobalt, options={}) {
28
+ return init(cobalt, options)
29
+ },
30
+
31
+ onRun: function (cobalt, node, webGpuCommandEncoder) {
32
+ // do whatever you need for this node. webgpu renderpasses, etc.
33
+ draw(cobalt, node, webGpuCommandEncoder)
34
+ },
35
+
36
+ onDestroy: function (cobalt, node) {
37
+ // any cleanup for your node should go here (releasing textures, etc.)
38
+ destroy(node)
39
+ },
40
+
41
+ onResize: function (cobalt, node) {
42
+ // do whatever you need when the dimensions of the renderer change (resize textures, etc.)
43
+ node.data.displacementTexture.resize(cobalt.viewport.width, cobalt.viewport.height);
44
+
45
+ node.data.displacementComposition.setColorTextureView(node.refs.color.data.view);
46
+ node.data.displacementComposition.setNoiseMapTextureView(node.refs.map.view);
47
+ node.data.displacementComposition.setDisplacementTextureView(node.data.displacementTexture.getView());
48
+ },
49
+
50
+ onViewportPosition: function (cobalt, node) {
51
+ node.data.displacementTexture.setViewport(cobalt.viewport);
52
+ },
53
+
54
+ // optional
55
+ customFunctions: {
56
+
57
+ addTriangle: function (cobalt, node, triangleVertices) {
58
+ return node.data.trianglesBuffer.addTriangle(triangleVertices);
59
+ },
60
+
61
+ removeTriangle: function (cobalt, node, triangleId) {
62
+ node.data.trianglesBuffer.removeTriangle(triangleId);
63
+ },
64
+
65
+ setPosition: function (cobalt, node, triangleId, triangleVertices) {
66
+ node.data.trianglesBuffer.setTriangle(triangleId, triangleVertices);
67
+ },
68
+ },
69
+ }
70
+
71
+
72
+ // This corresponds to a WebGPU render pass. It handles 1 sprite layer.
73
+ async function init (cobalt, node) {
74
+ const { device } = cobalt
75
+
76
+ const displacementParameters = new DisplacementParametersBuffer({
77
+ device,
78
+ initialParameters: {
79
+ offsetX: node.options.offseyX ?? 0,
80
+ offsetY: node.options.offseyY ?? 0,
81
+ scale: node.options.scale ?? 20,
82
+ }
83
+ })
84
+
85
+ const MAX_SPRITE_COUNT = 256 // max number of displacement sprites in this render pass
86
+
87
+ const trianglesBuffer = new TrianglesBuffer({
88
+ device,
89
+ maxSpriteCount: MAX_SPRITE_COUNT,
90
+ });
91
+
92
+ const displacementTexture = new DisplacementTexture({
93
+ device,
94
+
95
+ width: cobalt.viewport.width,
96
+ height: cobalt.viewport.height,
97
+
98
+ blurFactor: 8,
99
+
100
+ trianglesBuffer,
101
+ });
102
+
103
+ const displacementComposition = new DisplacementComposition({
104
+ device,
105
+ targetFormat: "bgra8unorm",
106
+
107
+ colorTextureView: node.refs.color.data.view,
108
+ noiseMapTextureView: node.refs.map.view,
109
+ displacementTextureView: displacementTexture.getView(),
110
+
111
+ displacementParametersBuffer: displacementParameters,
112
+ });
113
+
114
+ return {
115
+ displacementParameters,
116
+ displacementTexture,
117
+ displacementComposition,
118
+ trianglesBuffer,
119
+ }
120
+ }
121
+
122
+
123
+ function draw(cobalt, node, commandEncoder) {
124
+ const spriteCount = node.data.trianglesBuffer.spriteCount;
125
+
126
+ if (spriteCount === 0)
127
+ return
128
+
129
+ node.data.trianglesBuffer.update();
130
+
131
+ node.data.displacementTexture.update(commandEncoder);
132
+
133
+ const renderpass = commandEncoder.beginRenderPass({
134
+ colorAttachments: [
135
+ {
136
+ view: node.refs.out,
137
+ clearValue: cobalt.clearValue,
138
+ loadOp: 'load',
139
+ storeOp: 'store'
140
+ }
141
+ ],
142
+ });
143
+ renderpass.executeBundles([node.data.displacementComposition.getRenderBundle()]);
144
+ renderpass.end()
145
+ }
146
+
147
+
148
+ function destroy (node) {
149
+ node.data.trianglesBuffer.destroy();
150
+ node.data.trianglesBuffer = null;
151
+
152
+ node.data.displacementParameters.destroy();
153
+ node.data.displacementParameters = null;
154
+
155
+ node.data.displacementTexture.destroy();
156
+ node.data.displacementTexture = null;
157
+
158
+ node.data.displacementComposition.destroy();
159
+ node.data.displacementComposition = null;
160
+ }
@@ -0,0 +1,31 @@
1
+ struct TransformData { // align(16) size(64)
2
+ mvpMatrix: mat4x4<f32>, // offset(0) align(16) size(64)
3
+ };
4
+
5
+ @group(0) @binding(0) var<uniform> transformUBO: TransformData;
6
+
7
+ struct VertexIn {
8
+ @location(0) position: vec2<f32>,
9
+ };
10
+
11
+ struct VertexOut {
12
+ @builtin(position) position: vec4<f32>,
13
+ };
14
+
15
+ @vertex
16
+ fn main_vertex (in: VertexIn) -> VertexOut {
17
+ var output: VertexOut;
18
+ output.position = transformUBO.mvpMatrix * vec4<f32>(in.position, 0.0, 1.0);
19
+ return output;
20
+ }
21
+
22
+ struct FragmentOut {
23
+ @location(0) color: vec4<f32>,
24
+ };
25
+
26
+ @fragment
27
+ fn main_fragment () -> FragmentOut {
28
+ var out: FragmentOut;
29
+ out.color = vec4<f32>(1.0, 1.0, 1.0, 1.0);
30
+ return out;
31
+ }
@@ -0,0 +1,95 @@
1
+ /// <reference types="@webgpu/types"/>
2
+
3
+ import uuid from '../uuid.js'
4
+
5
+ type Parameters = {
6
+ readonly device: GPUDevice;
7
+ readonly maxSpriteCount: number;
8
+ };
9
+
10
+ type Point = [number, number];
11
+ type TriangleVertices = [Point, Point, Point];
12
+ type TriangleData = [number, number, number, number, number, number];
13
+
14
+ class TrianglesBuffer {
15
+ private readonly device: GPUDevice;
16
+
17
+ private readonly floatsPerSprite = 6; // vec2(translate) + vec2(scale) + rotation + opacity
18
+ public readonly bufferGpu: GPUBuffer;
19
+ private bufferNeedsUpdate: boolean = false;
20
+
21
+ private readonly sprites: Map<number, TriangleData> = new Map();
22
+ public get spriteCount(): number {
23
+ return this.sprites.size;
24
+ }
25
+
26
+ public constructor(params: Parameters) {
27
+ this.device = params.device;
28
+
29
+ this.bufferGpu = this.device.createBuffer({
30
+ size: params.maxSpriteCount * this.floatsPerSprite * Float32Array.BYTES_PER_ELEMENT,
31
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
32
+ });
33
+ }
34
+
35
+ public destroy(): void {
36
+ this.bufferGpu.destroy;
37
+ }
38
+
39
+ public update(): void {
40
+ if (this.bufferNeedsUpdate) {
41
+ const bufferData: number[] = [];
42
+ for (const sprite of this.sprites.values()) {
43
+ bufferData.push(...sprite);
44
+ };
45
+ const buffer = new Float32Array(bufferData);
46
+ this.device.queue.writeBuffer(this.bufferGpu, 0, buffer);
47
+ }
48
+ }
49
+
50
+ public addTriangle(triangleVertices: TriangleVertices): number {
51
+ const triangleId = uuid();
52
+ if (this.sprites.has(triangleId)) {
53
+ throw new Error(`Duplicate triangle "${triangleId}".`);
54
+ }
55
+
56
+ const triangleData = this.buildTriangleData(triangleVertices);
57
+ this.sprites.set(triangleId, triangleData);
58
+ this.bufferNeedsUpdate = true;
59
+
60
+ return triangleId;
61
+ }
62
+
63
+ public removeTriangle(triangleId: number): void {
64
+ if (!this.sprites.has(triangleId)) {
65
+ throw new Error(`Unknown triangle "${triangleId}".`);
66
+ }
67
+ this.sprites.delete(triangleId);
68
+ this.bufferNeedsUpdate = true;
69
+ }
70
+
71
+ public setTriangle(triangleId: number, triangleVertices: TriangleVertices): void {
72
+ if (!this.sprites.has(triangleId)) {
73
+ throw new Error(`Unknown triangle "${triangleId}".`);
74
+ }
75
+ const triangleData = this.buildTriangleData(triangleVertices);
76
+ this.sprites.set(triangleId, triangleData);
77
+ this.bufferNeedsUpdate = true;
78
+ }
79
+
80
+ private buildTriangleData(triangleVertices: TriangleVertices): TriangleData {
81
+ return [
82
+ triangleVertices[0][0],
83
+ triangleVertices[0][1],
84
+ triangleVertices[1][0],
85
+ triangleVertices[1][1],
86
+ triangleVertices[2][0],
87
+ triangleVertices[2][1],
88
+ ];
89
+ }
90
+ }
91
+
92
+ export {
93
+ TrianglesBuffer
94
+ };
95
+
@@ -0,0 +1,161 @@
1
+ import blitWGSL from './fb-blit.wgsl'
2
+
3
+
4
+ // blit a source texture into a destination texture
5
+
6
+ export default {
7
+ type: 'cobalt:fbBlit',
8
+ refs: [
9
+ { name: 'in', type: 'cobaltTexture', format: 'bgra8unorm', access: 'read' },
10
+ { name: 'out', type: 'cobaltTexture', format: 'bgra8unorm', access: 'write' },
11
+ ],
12
+
13
+ // @params Object cobalt renderer world object
14
+ // @params Object options optional data passed when initing this node
15
+ onInit: async function (cobalt, options={}) {
16
+ return init(cobalt, options)
17
+ },
18
+
19
+ onRun: function (cobalt, node, webGpuCommandEncoder) {
20
+ // do whatever you need for this node. webgpu renderpasses, etc.
21
+ draw(cobalt, node, webGpuCommandEncoder)
22
+ },
23
+
24
+ onDestroy: function (cobalt, node) {
25
+ // any cleanup for your node should go here (releasing textures, etc.)
26
+ },
27
+
28
+ onResize: function (cobalt, node) {
29
+ // do whatever you need when the dimensions of the renderer change (resize textures, etc.)
30
+ resize(cobalt, node)
31
+ },
32
+
33
+ onViewportPosition: function (cobalt, node) { },
34
+ }
35
+
36
+
37
+ async function init (cobalt, node) {
38
+ const { device } = cobalt
39
+
40
+ const bindGroupLayout = device.createBindGroupLayout({
41
+ entries: [
42
+ {
43
+ binding: 0,
44
+ visibility: GPUShaderStage.FRAGMENT,
45
+ texture: { }
46
+ },
47
+ {
48
+ binding: 1,
49
+ visibility: GPUShaderStage.FRAGMENT,
50
+ sampler: { }
51
+ }
52
+ ],
53
+ })
54
+
55
+ const bindGroup = device.createBindGroup({
56
+ layout: bindGroupLayout,
57
+ entries: [
58
+ {
59
+ binding: 0,
60
+ resource: node.refs.in.data.view
61
+ },
62
+ {
63
+ binding: 1,
64
+ resource: node.refs.in.data.sampler
65
+ }
66
+ ]
67
+ })
68
+
69
+ const pipelineLayout = device.createPipelineLayout({
70
+ bindGroupLayouts: [ bindGroupLayout ]
71
+ })
72
+
73
+ const pipeline = device.createRenderPipeline({
74
+ label: 'fb-blit',
75
+ vertex: {
76
+ module: device.createShaderModule({
77
+ code: blitWGSL
78
+ }),
79
+ entryPoint: 'vs_main',
80
+ buffers: [ /*quad.bufferLayout*/ ]
81
+ },
82
+
83
+ fragment: {
84
+ module: device.createShaderModule({
85
+ code: blitWGSL
86
+ }),
87
+ entryPoint: 'fs_main',
88
+ targets: [
89
+ {
90
+ format: 'bgra8unorm',
91
+ blend: {
92
+ color: {
93
+ srcFactor: 'src-alpha',
94
+ dstFactor: 'one-minus-src-alpha',
95
+ },
96
+ alpha: {
97
+ srcFactor: 'zero',
98
+ dstFactor: 'one'
99
+ }
100
+ }
101
+ }
102
+ ]
103
+ },
104
+
105
+ primitive: {
106
+ topology: 'triangle-list'
107
+ },
108
+
109
+ layout: pipelineLayout
110
+ })
111
+
112
+ return {
113
+ bindGroupLayout,
114
+ bindGroup,
115
+ pipeline,
116
+ }
117
+ }
118
+
119
+
120
+ function draw (cobalt, node, commandEncoder) {
121
+ const { device } = cobalt
122
+
123
+ const renderpass = commandEncoder.beginRenderPass({
124
+ colorAttachments: [
125
+ {
126
+ view: node.refs.out,
127
+ clearValue: cobalt.clearValue,
128
+ loadOp: 'load',
129
+ storeOp: 'store'
130
+ }
131
+ ]
132
+ })
133
+
134
+ renderpass.setPipeline(node.data.pipeline)
135
+
136
+ renderpass.setBindGroup(0, node.data.bindGroup)
137
+
138
+ renderpass.draw(3)
139
+
140
+ renderpass.end()
141
+ }
142
+
143
+
144
+ function resize (cobalt, node) {
145
+ const { device } = cobalt
146
+
147
+ // re-build the bind group
148
+ node.data.bindGroup = device.createBindGroup({
149
+ layout: node.data.bindGroupLayout,
150
+ entries: [
151
+ {
152
+ binding: 0,
153
+ resource: node.refs.in.data.view
154
+ },
155
+ {
156
+ binding: 1,
157
+ resource: node.refs.in.data.sampler
158
+ }
159
+ ]
160
+ })
161
+ }
@@ -0,0 +1,40 @@
1
+
2
+ @binding(0) @group(0) var tileTexture: texture_2d<f32>;
3
+ @binding(1) @group(0) var tileSampler: sampler;
4
+
5
+
6
+ struct Fragment {
7
+ @builtin(position) Position : vec4<f32>,
8
+ @location(0) TexCoord : vec2<f32>
9
+ };
10
+
11
+ // fullscreen triangle position and uvs
12
+ const positions = array<vec2<f32>, 3>(
13
+ vec2<f32>(-1.0, -3.0),
14
+ vec2<f32>(3.0, 1.0),
15
+ vec2<f32>(-1.0, 1.0)
16
+ );
17
+
18
+ const uvs = array<vec2<f32>, 3>(
19
+ vec2<f32>(0.0, 2.0),
20
+ vec2<f32>(2.0, 0.0),
21
+ vec2<f32>(0.0, 0.0)
22
+ );
23
+
24
+ @vertex
25
+ fn vs_main (@builtin(vertex_index) VertexIndex : u32) -> Fragment {
26
+
27
+ var output : Fragment;
28
+
29
+ output.Position = vec4<f32>(positions[VertexIndex], 0.0, 1.0);
30
+ output.TexCoord = vec2<f32>(uvs[VertexIndex]);
31
+
32
+ return output;
33
+ }
34
+
35
+
36
+ @fragment
37
+ fn fs_main (@location(0) TexCoord: vec2<f32>) -> @location(0) vec4<f32> {
38
+ var col = textureSample(tileTexture, tileSampler, TexCoord);
39
+ return vec4<f32>(col.rgb, 1.0);
40
+ }