@footgun/cobalt 0.8.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@footgun/cobalt",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "bundle.js",
6
6
  "description": "A 2D WebGpu renderer",
package/src/cobalt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { default as createTexture } from './create-texture.js'
2
2
  export { default as createTextureFromBuffer } from './create-texture-from-buffer.js'
3
3
  export { default as createTextureFromUrl } from './create-texture-from-url.js'
4
+ export { default as getPreferredFormat } from './get-preferred-format.js'
4
5
 
5
6
  // built-in run nodes
6
7
  import bloomNode from './bloom/bloom.js'
@@ -1,50 +1,27 @@
1
- /// <reference types="@webgpu/types"/>
2
-
3
- import { DisplacementParametersBuffer } from "./displacement-parameters-buffer";
4
- import compositionWGSL from "./composition.wgsl"
5
-
6
- type Parameters = {
7
- readonly device: GPUDevice;
8
- readonly targetFormat: GPUTextureFormat;
9
-
10
- readonly colorTextureView: GPUTextureView;
11
- readonly noiseMapTextureView: GPUTextureView;
12
- readonly displacementTextureView: GPUTextureView;
13
-
14
- readonly displacementParametersBuffer: DisplacementParametersBuffer;
15
- };
1
+ import compositionWGSL from "./composition.wgsl";
16
2
 
17
3
  class DisplacementComposition {
18
- private readonly device: GPUDevice;
19
- private readonly targetFormat: GPUTextureFormat;
20
- private readonly renderPipeline: GPURenderPipeline;
21
-
22
- private readonly colorSampler: GPUSampler;
23
- private readonly noiseSampler: GPUSampler;
24
-
25
- private readonly displacementParametersBuffer: DisplacementParametersBuffer;
26
-
27
- private renderBundle: GPURenderBundle | null = null;
28
-
29
- private colorTextureView: GPUTextureView;
30
- private noiseMapTextureView: GPUTextureView;
31
- private displacementTextureView: GPUTextureView;
32
-
33
- public constructor(params: Parameters) {
4
+ device;
5
+ targetFormat;
6
+ renderPipeline;
7
+ colorSampler;
8
+ noiseSampler;
9
+ displacementParametersBuffer;
10
+ renderBundle = null;
11
+ colorTextureView;
12
+ noiseMapTextureView;
13
+ displacementTextureView;
14
+ constructor(params) {
34
15
  this.device = params.device;
35
-
36
16
  this.targetFormat = params.targetFormat;
37
17
  this.colorTextureView = params.colorTextureView;
38
18
  this.noiseMapTextureView = params.noiseMapTextureView;
39
19
  this.displacementTextureView = params.displacementTextureView;
40
-
41
20
  this.displacementParametersBuffer = params.displacementParametersBuffer;
42
-
43
21
  const shaderModule = this.device.createShaderModule({
44
22
  label: "DisplacementComposition shader module",
45
23
  code: compositionWGSL,
46
24
  });
47
-
48
25
  this.renderPipeline = this.device.createRenderPipeline({
49
26
  label: "DisplacementComposition renderpipeline",
50
27
  layout: "auto",
@@ -56,15 +33,14 @@ class DisplacementComposition {
56
33
  module: shaderModule,
57
34
  entryPoint: "main_fragment",
58
35
  targets: [{
59
- format: params.targetFormat,
60
- }],
36
+ format: params.targetFormat,
37
+ }],
61
38
  },
62
39
  primitive: {
63
40
  cullMode: "none",
64
41
  topology: "triangle-strip",
65
42
  },
66
43
  });
67
-
68
44
  this.noiseSampler = this.device.createSampler({
69
45
  label: "DisplacementComposition noisesampler",
70
46
  addressModeU: "repeat",
@@ -74,7 +50,6 @@ class DisplacementComposition {
74
50
  minFilter: "linear",
75
51
  mipmapFilter: "linear",
76
52
  });
77
-
78
53
  this.colorSampler = this.device.createSampler({
79
54
  label: "DisplacementComposition colorSampler",
80
55
  addressModeU: "clamp-to-edge",
@@ -85,34 +60,28 @@ class DisplacementComposition {
85
60
  mipmapFilter: "linear",
86
61
  });
87
62
  }
88
-
89
- public getRenderBundle(): GPURenderBundle {
63
+ getRenderBundle() {
90
64
  if (!this.renderBundle) {
91
65
  this.renderBundle = this.buildRenderBundle();
92
66
  }
93
67
  return this.renderBundle;
94
68
  }
95
-
96
- public destroy(): void {
69
+ destroy() {
97
70
  // nothing to do
98
71
  }
99
-
100
- public setColorTextureView(textureView: GPUTextureView): void {
72
+ setColorTextureView(textureView) {
101
73
  this.colorTextureView = textureView;
102
74
  this.renderBundle = null;
103
75
  }
104
-
105
- public setNoiseMapTextureView(textureView: GPUTextureView): void {
76
+ setNoiseMapTextureView(textureView) {
106
77
  this.noiseMapTextureView = textureView;
107
78
  this.renderBundle = null;
108
79
  }
109
-
110
- public setDisplacementTextureView(textureView: GPUTextureView): void {
80
+ setDisplacementTextureView(textureView) {
111
81
  this.displacementTextureView = textureView;
112
82
  this.renderBundle = null;
113
83
  }
114
-
115
- private buildRenderBundle(): GPURenderBundle {
84
+ buildRenderBundle() {
116
85
  const bindgroup = this.device.createBindGroup({
117
86
  label: "DisplacementComposition bindgroup 0",
118
87
  layout: this.renderPipeline.getBindGroupLayout(0),
@@ -143,7 +112,6 @@ class DisplacementComposition {
143
112
  },
144
113
  ],
145
114
  });
146
-
147
115
  const renderBundleEncoder = this.device.createRenderBundleEncoder({
148
116
  label: "DisplacementComposition renderbundle encoder",
149
117
  colorFormats: [this.targetFormat],
@@ -154,8 +122,4 @@ class DisplacementComposition {
154
122
  return renderBundleEncoder.finish({ label: "DisplacementComposition renderbundle" });
155
123
  }
156
124
  }
157
-
158
- export {
159
- DisplacementComposition
160
- };
161
-
125
+ export { DisplacementComposition };
@@ -0,0 +1,21 @@
1
+ class DisplacementParametersBuffer {
2
+ device;
3
+ bufferGpu;
4
+ needsUpdate = true;
5
+ constructor(params) {
6
+ this.device = params.device;
7
+ this.bufferGpu = this.device.createBuffer({
8
+ label: "DisplacementParametersBuffer buffer",
9
+ size: 16,
10
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
11
+ });
12
+ this.setParameters(params.initialParameters);
13
+ }
14
+ setParameters(params) {
15
+ this.device.queue.writeBuffer(this.bufferGpu, 0, new Float32Array([params.offsetX, params.offsetY, params.scale]));
16
+ }
17
+ destroy() {
18
+ this.bufferGpu.destroy();
19
+ }
20
+ }
21
+ export { DisplacementParametersBuffer };
@@ -0,0 +1,174 @@
1
+ import displacementWGSL from './displacement.wgsl'
2
+ import * as wgpuMatrix from 'wgpu-matrix'
3
+
4
+ class DisplacementTexture {
5
+ device
6
+ format = 'r8unorm'
7
+ downsizeFactor
8
+ multisample
9
+ textureSimple
10
+ textureMultisampled = null
11
+ renderPipeline
12
+ bindgroup
13
+ uniformsBuffer
14
+ trianglesBuffer
15
+ constructor(params) {
16
+ this.device = params.device
17
+ this.downsizeFactor = params.blurFactor
18
+ this.multisample = this.downsizeFactor > 1 ? 4 : 1
19
+ ;[this.textureSimple, this.textureMultisampled] = this.createTextures(
20
+ params.width,
21
+ params.height,
22
+ )
23
+ this.trianglesBuffer = params.trianglesBuffer
24
+ const shaderModule = this.device.createShaderModule({
25
+ label: 'DisplacementTexture shader module',
26
+ code: displacementWGSL,
27
+ })
28
+ this.renderPipeline = this.device.createRenderPipeline({
29
+ label: 'DisplacementTexture renderpipeline',
30
+ layout: 'auto',
31
+ vertex: {
32
+ module: shaderModule,
33
+ entryPoint: 'main_vertex',
34
+ buffers: [
35
+ {
36
+ attributes: [
37
+ {
38
+ shaderLocation: 0,
39
+ offset: 0,
40
+ format: 'float32x2',
41
+ },
42
+ ],
43
+ arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT,
44
+ stepMode: 'vertex',
45
+ },
46
+ ],
47
+ },
48
+ fragment: {
49
+ module: shaderModule,
50
+ entryPoint: 'main_fragment',
51
+ targets: [
52
+ {
53
+ format: this.format,
54
+ },
55
+ ],
56
+ },
57
+ primitive: {
58
+ cullMode: 'none',
59
+ topology: 'triangle-list',
60
+ },
61
+ multisample: {
62
+ count: this.multisample,
63
+ },
64
+ })
65
+ this.uniformsBuffer = this.device.createBuffer({
66
+ label: 'DisplacementTexture uniforms buffer',
67
+ size: 64,
68
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
69
+ })
70
+ this.bindgroup = this.device.createBindGroup({
71
+ label: 'DisplacementTexture bindgroup',
72
+ layout: this.renderPipeline.getBindGroupLayout(0),
73
+ entries: [
74
+ {
75
+ binding: 0,
76
+ resource: { buffer: this.uniformsBuffer },
77
+ },
78
+ ],
79
+ })
80
+ }
81
+ update(commandEncoder) {
82
+ const targetTexture = this.textureMultisampled ?? this.textureSimple
83
+ const textureRenderpassColorAttachment = {
84
+ view: targetTexture.view,
85
+ clearValue: [0, 0, 0, 1],
86
+ loadOp: 'clear',
87
+ storeOp: 'store',
88
+ }
89
+ if (this.textureMultisampled) {
90
+ textureRenderpassColorAttachment.resolveTarget = this.textureSimple.view
91
+ }
92
+ const renderpassEncoder = commandEncoder.beginRenderPass({
93
+ label: 'DisplacementTexture render to texture renderpass',
94
+ colorAttachments: [textureRenderpassColorAttachment],
95
+ })
96
+ const [textureWidth, textureHeight] = [
97
+ targetTexture.texture.width,
98
+ targetTexture.texture.height,
99
+ ]
100
+ renderpassEncoder.setViewport(0, 0, textureWidth, textureHeight, 0, 1)
101
+ renderpassEncoder.setScissorRect(0, 0, textureWidth, textureHeight)
102
+ renderpassEncoder.setPipeline(this.renderPipeline)
103
+ renderpassEncoder.setBindGroup(0, this.bindgroup)
104
+ renderpassEncoder.setVertexBuffer(0, this.trianglesBuffer.bufferGpu)
105
+ renderpassEncoder.draw(3 * this.trianglesBuffer.spriteCount)
106
+ renderpassEncoder.end()
107
+ }
108
+ resize(width, height) {
109
+ this.textureSimple.texture.destroy()
110
+ this.textureMultisampled?.texture.destroy()
111
+ ;[this.textureSimple, this.textureMultisampled] = this.createTextures(width, height)
112
+ }
113
+ setViewport(viewport) {
114
+ // TODO: don't re-create these arrays and matrices every tick
115
+ const scaling = [1, 1, 1]
116
+ const rotation = 0
117
+ const translation = [1, 1, 0]
118
+ const modelMatrix = wgpuMatrix.mat4.identity()
119
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.scaling(scaling), modelMatrix, modelMatrix)
120
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.rotationZ(rotation), modelMatrix, modelMatrix)
121
+ wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.translation(translation), modelMatrix, modelMatrix)
122
+ const viewMatrix = wgpuMatrix.mat4.translation([
123
+ -viewport.position[0],
124
+ -viewport.position[1],
125
+ 0,
126
+ ])
127
+ const gameWidth = viewport.width / viewport.zoom
128
+ const gameHeight = viewport.height / viewport.zoom
129
+ // left right bottom top near far
130
+ const projectionMatrix = wgpuMatrix.mat4.ortho(0, gameWidth, gameHeight, 0, -10.0, 10.0)
131
+ const mvpMatrix = wgpuMatrix.mat4.identity()
132
+ wgpuMatrix.mat4.multiply(viewMatrix, modelMatrix, mvpMatrix)
133
+ wgpuMatrix.mat4.multiply(projectionMatrix, mvpMatrix, mvpMatrix)
134
+ this.device.queue.writeBuffer(this.uniformsBuffer, 0, mvpMatrix)
135
+ }
136
+ getView() {
137
+ return this.textureSimple.view
138
+ }
139
+ destroy() {
140
+ this.textureSimple.texture.destroy()
141
+ this.textureMultisampled?.texture.destroy()
142
+ this.uniformsBuffer.destroy()
143
+ }
144
+ createTextures(width, height) {
145
+ const texture = this.device.createTexture({
146
+ label: 'DisplacementTexture texture',
147
+ size: [Math.ceil(width / this.downsizeFactor), Math.ceil(height / this.downsizeFactor)],
148
+ format: this.format,
149
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
150
+ })
151
+ const textureSimple = {
152
+ texture,
153
+ view: texture.createView({ label: 'DisplacementTexture texture view' }),
154
+ }
155
+ let textureMultisampled = null
156
+ if (this.multisample > 1) {
157
+ const textureMulti = this.device.createTexture({
158
+ label: 'DisplacementTexture texture multisampled',
159
+ size: [texture.width, texture.height],
160
+ format: texture.format,
161
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
162
+ sampleCount: this.multisample,
163
+ })
164
+ textureMultisampled = {
165
+ texture: textureMulti,
166
+ view: textureMulti.createView({
167
+ label: 'DisplacementTexture texture multisampled view',
168
+ }),
169
+ }
170
+ }
171
+ return [textureSimple, textureMultisampled]
172
+ }
173
+ }
174
+ export { DisplacementTexture }
@@ -1,74 +1,53 @@
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];
1
+ import uuid from '../uuid.js';
13
2
 
14
3
  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 {
4
+ device;
5
+ floatsPerSprite = 6; // vec2(translate) + vec2(scale) + rotation + opacity
6
+ bufferGpu;
7
+ bufferNeedsUpdate = false;
8
+ sprites = new Map();
9
+ get spriteCount() {
23
10
  return this.sprites.size;
24
11
  }
25
-
26
- public constructor(params: Parameters) {
12
+ constructor(params) {
27
13
  this.device = params.device;
28
-
29
14
  this.bufferGpu = this.device.createBuffer({
30
15
  size: params.maxSpriteCount * this.floatsPerSprite * Float32Array.BYTES_PER_ELEMENT,
31
16
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
32
17
  });
33
18
  }
34
-
35
- public destroy(): void {
19
+ destroy() {
36
20
  this.bufferGpu.destroy;
37
21
  }
38
-
39
- public update(): void {
22
+ update() {
40
23
  if (this.bufferNeedsUpdate) {
41
- const bufferData: number[] = [];
24
+ const bufferData = [];
42
25
  for (const sprite of this.sprites.values()) {
43
26
  bufferData.push(...sprite);
44
- };
27
+ }
28
+ ;
45
29
  const buffer = new Float32Array(bufferData);
46
30
  this.device.queue.writeBuffer(this.bufferGpu, 0, buffer);
47
31
  }
48
32
  }
49
-
50
- public addTriangle(triangleVertices: TriangleVertices): number {
33
+ addTriangle(triangleVertices) {
51
34
  const triangleId = uuid();
52
35
  if (this.sprites.has(triangleId)) {
53
36
  throw new Error(`Duplicate triangle "${triangleId}".`);
54
37
  }
55
-
56
38
  const triangleData = this.buildTriangleData(triangleVertices);
57
39
  this.sprites.set(triangleId, triangleData);
58
40
  this.bufferNeedsUpdate = true;
59
-
60
41
  return triangleId;
61
42
  }
62
-
63
- public removeTriangle(triangleId: number): void {
43
+ removeTriangle(triangleId) {
64
44
  if (!this.sprites.has(triangleId)) {
65
45
  throw new Error(`Unknown triangle "${triangleId}".`);
66
46
  }
67
47
  this.sprites.delete(triangleId);
68
48
  this.bufferNeedsUpdate = true;
69
49
  }
70
-
71
- public setTriangle(triangleId: number, triangleVertices: TriangleVertices): void {
50
+ setTriangle(triangleId, triangleVertices) {
72
51
  if (!this.sprites.has(triangleId)) {
73
52
  throw new Error(`Unknown triangle "${triangleId}".`);
74
53
  }
@@ -76,8 +55,7 @@ class TrianglesBuffer {
76
55
  this.sprites.set(triangleId, triangleData);
77
56
  this.bufferNeedsUpdate = true;
78
57
  }
79
-
80
- private buildTriangleData(triangleVertices: TriangleVertices): TriangleData {
58
+ buildTriangleData(triangleVertices) {
81
59
  return [
82
60
  triangleVertices[0][0],
83
61
  triangleVertices[0][1],
@@ -88,8 +66,4 @@ class TrianglesBuffer {
88
66
  ];
89
67
  }
90
68
  }
91
-
92
- export {
93
- TrianglesBuffer
94
- };
95
-
69
+ export { TrianglesBuffer };
@@ -1,9 +1,5 @@
1
- /// <reference types="@webgpu/types"/>
2
-
3
- import { type Light } from "./types";
4
-
5
1
  class LightsBuffer {
6
- public static readonly structs = {
2
+ static structs = {
7
3
  definition: `
8
4
  struct Light { // align(16) size(48)
9
5
  color: vec3<f32>, // offset(0) align(16) size(12)
@@ -28,24 +24,16 @@ struct LightsBuffer { // align(16)
28
24
  lights: { offset: 16, stride: 48 },
29
25
  },
30
26
  };
31
-
32
- private readonly device: GPUDevice;
33
-
34
- public readonly maxLightsCount: number;
35
- private currentLightsCount: number = 0;
36
-
37
- private readonly buffer: {
38
- readonly bufferCpu: ArrayBuffer;
39
- readonly bufferGpu: GPUBuffer;
40
- };
41
- public get gpuBuffer(): GPUBuffer {
27
+ device;
28
+ maxLightsCount;
29
+ currentLightsCount = 0;
30
+ buffer;
31
+ get gpuBuffer() {
42
32
  return this.buffer.bufferGpu;
43
33
  }
44
-
45
- public constructor(device: GPUDevice, maxLightsCount: number) {
34
+ constructor(device, maxLightsCount) {
46
35
  this.device = device;
47
36
  this.maxLightsCount = maxLightsCount;
48
-
49
37
  const bufferCpu = new ArrayBuffer(LightsBuffer.computeBufferBytesLength(maxLightsCount));
50
38
  const bufferGpu = device.createBuffer({
51
39
  label: "LightsBuffer buffer",
@@ -53,19 +41,15 @@ struct LightsBuffer { // align(16)
53
41
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
54
42
  });
55
43
  this.buffer = { bufferCpu, bufferGpu };
56
-
57
44
  this.setLights([]);
58
45
  }
59
-
60
- public setLights(lights: ReadonlyArray<Light>): void {
46
+ setLights(lights) {
61
47
  if (lights.length > this.maxLightsCount) {
62
48
  throw new Error(`Too many lights "${lights.length}", max is "${this.maxLightsCount}".`);
63
49
  }
64
-
65
50
  const newBufferLength = LightsBuffer.computeBufferBytesLength(lights.length);
66
51
  new Uint32Array(this.buffer.bufferCpu, 0, 1).set([lights.length]);
67
-
68
- lights.forEach((light: Light, index: number) => {
52
+ lights.forEach((light, index) => {
69
53
  new Float32Array(this.buffer.bufferCpu, LightsBuffer.structs.lightsBuffer.lights.offset + LightsBuffer.structs.lightsBuffer.lights.stride * index, 9).set([
70
54
  ...light.color,
71
55
  light.radius,
@@ -75,24 +59,17 @@ struct LightsBuffer { // align(16)
75
59
  light.attenuationExp
76
60
  ]);
77
61
  });
78
-
79
62
  this.device.queue.writeBuffer(this.buffer.bufferGpu, 0, this.buffer.bufferCpu, 0, newBufferLength);
80
63
  this.currentLightsCount = lights.length;
81
64
  }
82
-
83
- public get lightsCount(): number {
65
+ get lightsCount() {
84
66
  return this.currentLightsCount;
85
67
  }
86
-
87
- public destroy(): void {
68
+ destroy() {
88
69
  this.buffer.bufferGpu.destroy();
89
70
  }
90
-
91
- private static computeBufferBytesLength(lightsCount: number): number {
71
+ static computeBufferBytesLength(lightsCount) {
92
72
  return LightsBuffer.structs.lightsBuffer.lights.offset + LightsBuffer.structs.lightsBuffer.lights.stride * lightsCount;
93
73
  }
94
74
  }
95
-
96
- export {
97
- LightsBuffer
98
- };
75
+ export { LightsBuffer };