@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.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/bundle.js +284 -0
- package/cobalt2.jpeg +0 -0
- package/esbuild.js +20 -0
- package/examples/01-primitives/Game.js +8 -0
- package/examples/01-primitives/component-animation.js +8 -0
- package/examples/01-primitives/component-transform.js +13 -0
- package/examples/01-primitives/constants.js +6 -0
- package/examples/01-primitives/deps.js +2 -0
- package/examples/01-primitives/entity-sprite.js +47 -0
- package/examples/01-primitives/index.html +191 -0
- package/examples/01-primitives/system-renderer.js +37 -0
- package/examples/02-sprites/Game.js +8 -0
- package/examples/02-sprites/assets/spritesheet.json +6276 -0
- package/examples/02-sprites/assets/spritesheet.png +0 -0
- package/examples/02-sprites/assets/spritesheet_emissive.png +0 -0
- package/examples/02-sprites/component-animation.js +8 -0
- package/examples/02-sprites/component-transform.js +13 -0
- package/examples/02-sprites/constants.js +6 -0
- package/examples/02-sprites/deps.js +2 -0
- package/examples/02-sprites/entity-sprite.js +47 -0
- package/examples/02-sprites/index.html +310 -0
- package/examples/02-sprites/system-renderer.js +38 -0
- package/examples/03-tiles/Game.js +8 -0
- package/examples/03-tiles/assets/spelunky-tiles.png +0 -0
- package/examples/03-tiles/assets/spelunky0.png +0 -0
- package/examples/03-tiles/assets/spelunky1.png +0 -0
- package/examples/03-tiles/component-animation.js +8 -0
- package/examples/03-tiles/component-transform.js +13 -0
- package/examples/03-tiles/constants.js +6 -0
- package/examples/03-tiles/deps.js +2 -0
- package/examples/03-tiles/entity-sprite.js +47 -0
- package/examples/03-tiles/index.html +309 -0
- package/examples/03-tiles/system-renderer.js +38 -0
- package/examples/04-overlay/assets/spritesheet.json +22 -0
- package/examples/04-overlay/assets/spritesheet.png +0 -0
- package/examples/04-overlay/assets/spritesheet_emissive.png +0 -0
- package/examples/04-overlay/constants.js +6 -0
- package/examples/04-overlay/deps.js +1 -0
- package/examples/04-overlay/index.html +133 -0
- package/examples/05-bloom/Game.js +8 -0
- package/examples/05-bloom/assets/spritesheet.json +6276 -0
- package/examples/05-bloom/assets/spritesheet.png +0 -0
- package/examples/05-bloom/assets/spritesheet_emissive.png +0 -0
- package/examples/05-bloom/component-animation.js +8 -0
- package/examples/05-bloom/component-transform.js +13 -0
- package/examples/05-bloom/constants.js +6 -0
- package/examples/05-bloom/deps.js +2 -0
- package/examples/05-bloom/entity-sprite.js +47 -0
- package/examples/05-bloom/index.html +357 -0
- package/examples/05-bloom/system-renderer.js +38 -0
- package/examples/06-displacement/Game.js +8 -0
- package/examples/06-displacement/assets/displacement_map_repeat.jpg +0 -0
- package/examples/06-displacement/assets/spelunky-tiles.png +0 -0
- package/examples/06-displacement/assets/spelunky0.png +0 -0
- package/examples/06-displacement/assets/spelunky1.png +0 -0
- package/examples/06-displacement/component-animation.js +8 -0
- package/examples/06-displacement/component-transform.js +13 -0
- package/examples/06-displacement/constants.js +6 -0
- package/examples/06-displacement/deps.js +2 -0
- package/examples/06-displacement/entity-sprite.js +47 -0
- package/examples/06-displacement/index.html +350 -0
- package/examples/06-displacement/system-renderer.js +38 -0
- package/examples/07-sdl/assets/spritesheet.json +22 -0
- package/examples/07-sdl/assets/spritesheet.png +0 -0
- package/examples/07-sdl/assets/spritesheet_emissive.png +0 -0
- package/examples/07-sdl/main.js +109 -0
- package/examples/07-sdl/package.json +19 -0
- package/examples/08-light/Game.js +8 -0
- package/examples/08-light/assets/spelunky-tiles.png +0 -0
- package/examples/08-light/assets/spelunky0.png +0 -0
- package/examples/08-light/assets/spelunky1.png +0 -0
- package/examples/08-light/constants.js +6 -0
- package/examples/08-light/deps.js +2 -0
- package/examples/08-light/index.html +477 -0
- package/package.json +34 -0
- package/src/bloom/bloom.js +467 -0
- package/src/bloom/bloom.wgsl +176 -0
- package/src/cobalt.js +231 -0
- package/src/create-texture-from-buffer.js +39 -0
- package/src/create-texture-from-url.js +35 -0
- package/src/create-texture.js +46 -0
- package/src/deps.js +3 -0
- package/src/displacement/composition.wgsl +58 -0
- package/src/displacement/displacement-composition.ts +161 -0
- package/src/displacement/displacement-parameters-buffer.ts +44 -0
- package/src/displacement/displacement-texture.ts +221 -0
- package/src/displacement/displacement.js +160 -0
- package/src/displacement/displacement.wgsl +31 -0
- package/src/displacement/triangles-buffer.ts +95 -0
- package/src/fb-blit/fb-blit.js +161 -0
- package/src/fb-blit/fb-blit.wgsl +40 -0
- package/src/fb-texture/fb-texture.js +56 -0
- package/src/light/README.md +61 -0
- package/src/light/light.js +148 -0
- package/src/light/lights-buffer.ts +98 -0
- package/src/light/lights-renderer.ts +278 -0
- package/src/light/public-api.js +20 -0
- package/src/light/readme/01_illumination.webp +0 -0
- package/src/light/readme/02_lights_texture.webp +0 -0
- package/src/light/readme/03_lights_texture_decomposed.webp +0 -0
- package/src/light/readme/04_lights_texture_mask.webp +0 -0
- package/src/light/readme/05_lights_obstacle_decomposition.webp +0 -0
- package/src/light/readme/06_lights_hard_cast_shadows.webp +0 -0
- package/src/light/texture/lights-texture-initializer.ts +191 -0
- package/src/light/texture/lights-texture-mask.ts +286 -0
- package/src/light/texture/lights-texture.ts +121 -0
- package/src/light/types.ts +23 -0
- package/src/light/viewport.ts +63 -0
- package/src/overlay/constants.js +1 -0
- package/src/overlay/overlay.js +341 -0
- package/src/overlay/overlay.wgsl +88 -0
- package/src/primitives/constants.js +1 -0
- package/src/primitives/primitives.js +252 -0
- package/src/primitives/primitives.wgsl +54 -0
- package/src/primitives/public-api.js +325 -0
- package/src/scene-composite/scene-composite.js +168 -0
- package/src/scene-composite/scene-composite.wgsl +94 -0
- package/src/sprite/constants.js +1 -0
- package/src/sprite/create-sprite-quads.js +60 -0
- package/src/sprite/public-api.js +215 -0
- package/src/sprite/read-spritesheet.js +103 -0
- package/src/sprite/sorted-binary-insert.js +45 -0
- package/src/sprite/sprite.js +268 -0
- package/src/sprite/sprite.wgsl +103 -0
- package/src/sprite/spritesheet.js +212 -0
- package/src/tile/atlas.js +193 -0
- package/src/tile/tile.js +171 -0
- package/src/tile/tile.wgsl +105 -0
- 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
|
+
}
|