@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
package/src/cobalt.js ADDED
@@ -0,0 +1,231 @@
1
+ export { default as createTexture } from './create-texture.js'
2
+ export { default as createTextureFromUrl } from './create-texture-from-url.js'
3
+ export { default as createTextureFromBuffer } from './create-texture-from-buffer.js'
4
+
5
+
6
+ // built-in run nodes
7
+ import bloomNode from './bloom/bloom.js'
8
+ import compositeNode from './scene-composite/scene-composite.js'
9
+ import spriteNode from './sprite/sprite.js'
10
+ import tileNode from './tile/tile.js'
11
+ import displacementNode from './displacement/displacement.js'
12
+ import overlayNode from './overlay/overlay.js'
13
+ import fbBlitNode from './fb-blit/fb-blit.js'
14
+ import primitivesNode from './primitives/primitives.js'
15
+ import lightNode from './light/light.js'
16
+
17
+ // built-in resource nodes
18
+ import tileAtlasNode from './tile/atlas.js'
19
+ import spritesheetNode from './sprite/spritesheet.js'
20
+ import fbTextureNode from './fb-texture/fb-texture.js'
21
+
22
+
23
+ // create and initialize a WebGPU renderer for a given canvas
24
+ // returns the data structure containing all WebGPU related stuff
25
+ export async function init (ctx, viewportWidth, viewportHeight) {
26
+
27
+ let device, gpu, context, canvas
28
+
29
+ // determine if an sdl/gpu context was passed, or if this is a browser canvas
30
+ if (ctx.sdlWindow && ctx.gpu) {
31
+ // this is an sdl/gpu context
32
+ gpu = ctx.gpu
33
+
34
+ const instance = gpu.create([ 'verbose=1' ])
35
+ const adapter = await instance.requestAdapter()
36
+ device = await adapter.requestDevice()
37
+ context = gpu.renderGPUDeviceToWindow({ device, window: ctx.sdlWindow })
38
+
39
+ // gpu module doesn't expose these globals to node namespace so manually wire them up
40
+ global.GPUBufferUsage = gpu.GPUBufferUsage
41
+ global.GPUShaderStage = gpu.GPUShaderStage
42
+ global.GPUTextureUsage = gpu.GPUTextureUsage
43
+
44
+ } else {
45
+ // ctx is a canvas element
46
+ canvas = ctx
47
+
48
+ const adapter = await navigator.gpu?.requestAdapter({ powerPreference: 'high-performance' })
49
+
50
+ device = await adapter?.requestDevice()
51
+ gpu = navigator.gpu
52
+
53
+ context = canvas.getContext('webgpu')
54
+
55
+ context.configure({
56
+ device,
57
+ format: navigator.gpu?.getPreferredCanvasFormat(), // bgra8unorm
58
+ alphaMode: 'opaque'
59
+ })
60
+ }
61
+
62
+ // all available node defintions the renderer knows about are defined here.
63
+ // the cobalt:* nodes are built in. New nodes can be defined at run time with Cobalt.defineNode(...)
64
+ const nodeDefs = {
65
+ // built in resource nodes
66
+ 'cobalt:tileAtlas': tileAtlasNode,
67
+ 'cobalt:spritesheet': spritesheetNode,
68
+ 'cobalt:fbTexture': fbTextureNode,
69
+
70
+ // builtin run nodes
71
+ 'cobalt:bloom': bloomNode,
72
+ 'cobalt:composite': compositeNode,
73
+ 'cobalt:sprite': spriteNode,
74
+ 'cobalt:tile': tileNode,
75
+ 'cobalt:displacement': displacementNode,
76
+ 'cobalt:overlay': overlayNode,
77
+ 'cobalt:fbBlit': fbBlitNode,
78
+ 'cobalt:primitives': primitivesNode,
79
+ 'cobalt:light': lightNode,
80
+ }
81
+
82
+ return {
83
+ nodeDefs,
84
+ // runnable nodes. ordering dictates render order (first to last)
85
+ nodes: [ ],
86
+
87
+ // keeps references to all node refs that need to access the per-frame default texture view
88
+ // these refs are updated on each invocation of Cobalt.draw(...)
89
+ defaultTextureViewRefs: [ ],
90
+
91
+ canvas,
92
+ device,
93
+ context,
94
+ gpu,
95
+
96
+ // used in the color attachments of renderpass
97
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
98
+
99
+ viewport: {
100
+ width: viewportWidth,
101
+ height: viewportHeight,
102
+ zoom: 1.0,
103
+ position: [ 0, 0 ], // top-left corner of the viewport
104
+ },
105
+ }
106
+ }
107
+
108
+
109
+ export function defineNode (c, nodeDefinition) {
110
+ if (!nodeDefinition?.type)
111
+ throw new Error(`Can't define a new node missing a type.`)
112
+
113
+ c.nodeDefs[nodeDefinition.type] = nodeDefinition
114
+ }
115
+
116
+
117
+ export async function initNode (c, nodeData) {
118
+ const nodeDef = c.nodeDefs[nodeData?.type]
119
+
120
+ if (!nodeDef)
121
+ throw new Error(`Can't initialize a new node missing a type.`)
122
+
123
+ const node = {
124
+ type: nodeData.type,
125
+ refs: nodeData.refs || { },
126
+ options: nodeData.options || { },
127
+ data: { },
128
+ enabled: true, // when disabled, the node won't be run
129
+ }
130
+
131
+ for (const refName in node.refs) {
132
+ if (node.refs[refName] === 'FRAME_TEXTURE_VIEW') {
133
+ c.defaultTextureViewRefs.push({ node, refName })
134
+ node.refs[refName] = getCurrentTextureView(c)
135
+ }
136
+ }
137
+
138
+ node.data = await nodeDef.onInit(c, node)
139
+
140
+ // copy in all custom functions, and ensure the first parameter is the node itself
141
+ const customFunctions = nodeDef.customFunctions || { }
142
+ for (const fnName in customFunctions) {
143
+ node[fnName] = function (...args) {
144
+ return customFunctions[fnName](c, node, ...args)
145
+ }
146
+ }
147
+
148
+ c.nodes.push(node)
149
+ return node
150
+ }
151
+
152
+
153
+ export function draw (c) {
154
+ const { device, context } = c
155
+
156
+ const commandEncoder = device.createCommandEncoder()
157
+
158
+ const v = getCurrentTextureView(c)
159
+
160
+ // some nodes may need a reference to the default texture view (the frame backing)
161
+ // this is generated each draw frame so we need to update the references
162
+ for (const r of c.defaultTextureViewRefs)
163
+ r.node.refs[r.refName] = v
164
+
165
+ // run all of the enabled nodes
166
+ for (const node of c.nodes) {
167
+ if (!node.enabled)
168
+ continue
169
+
170
+ const nodeDef = c.nodeDefs[node.type]
171
+ nodeDef.onRun(c, node, commandEncoder)
172
+ }
173
+
174
+ device.queue.submit([ commandEncoder.finish() ])
175
+
176
+ // for sdl + gpu setups, we need to do this swap() step
177
+ if (!c.canvas)
178
+ c.context.swap()
179
+ }
180
+
181
+
182
+ // clean up all the loaded data so we could re-load a level, etc.
183
+ export function reset (c) {
184
+ for (const n of c.nodes) {
185
+ const nodeDef = c.nodeDefs[n.type]
186
+ nodeDef.onDestroy(c, n)
187
+ }
188
+ c.nodes.length = 0
189
+ c.defaultTextureViewRefs.length = 0
190
+ }
191
+
192
+
193
+ export function setViewportDimensions (c, width, height) {
194
+ c.viewport.width = width
195
+ c.viewport.height = height
196
+
197
+ for (const n of c.nodes) {
198
+ const nodeDef = c.nodeDefs[n.type]
199
+ nodeDef.onResize(c, n)
200
+ }
201
+ }
202
+
203
+
204
+ // @param Array pos 2D point the viewport is centered on
205
+ export function setViewportPosition (c, pos) {
206
+ c.viewport.position[0] = pos[0] - (c.viewport.width / 2 / c.viewport.zoom)
207
+ c.viewport.position[1] = pos[1] - (c.viewport.height / 2 / c.viewport.zoom)
208
+
209
+ for (const n of c.nodes) {
210
+ const nodeDef = c.nodeDefs[n.type]
211
+ nodeDef.onViewportPosition(c, n)
212
+ }
213
+ }
214
+
215
+
216
+ export function getPreferredFormat (cobalt) {
217
+ if (cobalt.canvas)
218
+ return navigator.gpu.getPreferredCanvasFormat()
219
+ else
220
+ return cobalt.context.getPreferredFormat()
221
+ }
222
+
223
+
224
+ export function getCurrentTextureView (cobalt) {
225
+ if (cobalt.canvas)
226
+ return cobalt.context.getCurrentTexture().createView()
227
+ else {
228
+ //return cobalt.context.getCurrentTexture().createView()
229
+ return cobalt.context.getCurrentTextureView()
230
+ }
231
+ }
@@ -0,0 +1,39 @@
1
+ import createTexture from './create-texture.js'
2
+
3
+
4
+ export default function createTextureFromBuffer (c, label, image, format='rgba8unorm') {
5
+
6
+ const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
7
+ const mip_count = 1
8
+ const t = createTexture(c.device, label, image.width, image.height, mip_count, format, usage)
9
+
10
+ /*
11
+ c.device.queue.copyExternalImageToTexture(
12
+ { source: image },
13
+ { texture: t.texture },
14
+ {
15
+ width: image.width,
16
+ height: image.height
17
+ }
18
+ )*/
19
+
20
+ c.device.queue.writeTexture(
21
+ { texture: t.texture },
22
+ image.data,
23
+ { bytesPerRow: 4 * image.width },
24
+ { width: image.width, height: image.height }
25
+ )
26
+
27
+ // nearest neighbor filtering is good for da pixel art
28
+ const samplerDescriptor = {
29
+ addressModeU: 'repeat', // repeat | clamp-to-edge
30
+ addressModeV: 'repeat', // repeat | clamp-to-edge
31
+ magFilter: 'nearest',
32
+ minFilter: 'nearest',
33
+ mipmapFilter: 'nearest',
34
+ maxAnisotropy: 1
35
+ }
36
+
37
+ t.sampler = c.device.createSampler(samplerDescriptor)
38
+ return t
39
+ }
@@ -0,0 +1,35 @@
1
+ import createTexture from './create-texture.js'
2
+
3
+
4
+ export default async function createTextureFromUrl (c, label, url, format='rgba8unorm') {
5
+ const response = await fetch(url)
6
+ const blob = await response.blob()
7
+
8
+ const imageData = await createImageBitmap(blob/*, { premultiplyAlpha: 'none', resizeQuality: 'pixelated' }*/)
9
+
10
+ const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
11
+ const mip_count = 1
12
+ const t = createTexture(c.device, label, imageData.width, imageData.height, mip_count, format, usage)
13
+
14
+ c.device.queue.copyExternalImageToTexture(
15
+ { source: imageData },
16
+ { texture: t.texture },
17
+ {
18
+ width: imageData.width,
19
+ height: imageData.height
20
+ }
21
+ )
22
+
23
+ // nearest neighbor filtering is good for da pixel art
24
+ const samplerDescriptor = {
25
+ addressModeU: 'repeat', // repeat | clamp-to-edge
26
+ addressModeV: 'repeat', // repeat | clamp-to-edge
27
+ magFilter: 'nearest',
28
+ minFilter: 'nearest',
29
+ mipmapFilter: 'nearest',
30
+ maxAnisotropy: 1
31
+ }
32
+
33
+ t.sampler = c.device.createSampler(samplerDescriptor)
34
+ return t
35
+ }
@@ -0,0 +1,46 @@
1
+ export default function createTexture (device, label, width, height, mip_count, format, usage) {
2
+
3
+ const texture = device.createTexture({
4
+ label,
5
+ size: { width, height },
6
+ format,
7
+ usage,
8
+ mipLevelCount: mip_count,
9
+ sampleCount: 1,
10
+ dimension: '2d',
11
+ })
12
+
13
+ const view = texture.createView()
14
+
15
+ const mip_view = [ ]
16
+
17
+ for (let i=0; i < mip_count; i++)
18
+ mip_view.push(texture.createView({
19
+ label,
20
+ format,
21
+ dimension: '2d',
22
+ aspect: 'all',
23
+ baseMipLevel: i,
24
+ mipLevelCount: 1,
25
+ baseArrayLayer: 0,
26
+ arrayLayerCount: 1,
27
+ }))
28
+
29
+ const sampler = device.createSampler({
30
+ label: `${label} sampler`,
31
+ addressModeU: 'clamp-to-edge',
32
+ addressModeV: 'clamp-to-edge',
33
+ addressModeW: 'clamp-to-edge',
34
+ magFilter: 'linear',
35
+ minFilter: 'linear',
36
+ mipmapFilter: 'linear',
37
+ })
38
+
39
+ return {
40
+ size: { width, height },
41
+ texture,
42
+ view,
43
+ mip_view,
44
+ sampler,
45
+ }
46
+ }
package/src/deps.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as removeArrayItems } from 'https://cdn.jsdelivr.net/gh/mreinstein/remove-array-items/src/remove-array-items.js'
2
+ export { default as round } from 'https://cdn.skypack.dev/pin/round-half-up-symmetric@v2.0.0-pfMZ4UGGs9FcqO8UiEHO/mode=imports,min/optimized/round-half-up-symmetric.js'
3
+ export { mat4, vec2, vec3, vec4 } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'
@@ -0,0 +1,58 @@
1
+ struct DisplacementParameters { // align(16) size(16)
2
+ offset: vec2<f32>, // offset(0) align(8) size(8)
3
+ scale: f32, // offset(8) align(4) size(4)
4
+ };
5
+
6
+ @group(0) @binding(0) var<uniform> uniforms: DisplacementParameters;
7
+ @group(0) @binding(1) var colorTexture: texture_2d<f32>;
8
+ @group(0) @binding(2) var colorSampler: sampler;
9
+ @group(0) @binding(3) var noiseTexture: texture_2d<f32>;
10
+ @group(0) @binding(4) var noiseSampler: sampler;
11
+ @group(0) @binding(5) var displacementTexture: texture_2d<f32>;
12
+
13
+ struct VertexIn {
14
+ @builtin(vertex_index) vertexIndex: u32,
15
+ };
16
+
17
+ struct VertexOut {
18
+ @builtin(position) position: vec4<f32>,
19
+ @location(0) uv: vec2<f32>,
20
+ };
21
+
22
+ @vertex
23
+ fn main_vertex(in: VertexIn) -> VertexOut {
24
+ const corners = array<vec2<f32>, 4>(
25
+ vec2<f32>(-1, -1),
26
+ vec2<f32>(1, -1),
27
+ vec2<f32>(-1, 1),
28
+ vec2<f32>(1, 1),
29
+ );
30
+ let screenPosition = corners[in.vertexIndex];
31
+
32
+ var out: VertexOut;
33
+ out.position = vec4<f32>(screenPosition, 0, 1);
34
+ out.uv = (0.5 + 0.5 * screenPosition * vec2<f32>(1, -1));
35
+ return out;
36
+ }
37
+
38
+ struct FragmentOut {
39
+ @location(0) color: vec4<f32>,
40
+ };
41
+
42
+ @fragment
43
+ fn main_fragment(in: VertexOut) -> FragmentOut {
44
+ let noiseTextureDimensions = vec2<f32>(textureDimensions(noiseTexture, 0));
45
+ let noiseUv = in.uv + uniforms.offset / noiseTextureDimensions;
46
+ var noise = textureSample(noiseTexture, noiseSampler, noiseUv).rg;
47
+ noise -= 0.5;
48
+ noise *= uniforms.scale / noiseTextureDimensions;
49
+
50
+ let displacement = textureSample(displacementTexture, colorSampler, in.uv).r;
51
+ noise *= displacement;
52
+
53
+ let colorUv = in.uv + noise;
54
+
55
+ var out: FragmentOut;
56
+ out.color = textureSample(colorTexture, colorSampler, colorUv);
57
+ return out;
58
+ }
@@ -0,0 +1,161 @@
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
+ };
16
+
17
+ 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) {
34
+ this.device = params.device;
35
+
36
+ this.targetFormat = params.targetFormat;
37
+ this.colorTextureView = params.colorTextureView;
38
+ this.noiseMapTextureView = params.noiseMapTextureView;
39
+ this.displacementTextureView = params.displacementTextureView;
40
+
41
+ this.displacementParametersBuffer = params.displacementParametersBuffer;
42
+
43
+ const shaderModule = this.device.createShaderModule({
44
+ label: "DisplacementComposition shader module",
45
+ code: compositionWGSL,
46
+ });
47
+
48
+ this.renderPipeline = this.device.createRenderPipeline({
49
+ label: "DisplacementComposition renderpipeline",
50
+ layout: "auto",
51
+ vertex: {
52
+ module: shaderModule,
53
+ entryPoint: "main_vertex",
54
+ },
55
+ fragment: {
56
+ module: shaderModule,
57
+ entryPoint: "main_fragment",
58
+ targets: [{
59
+ format: params.targetFormat,
60
+ }],
61
+ },
62
+ primitive: {
63
+ cullMode: "none",
64
+ topology: "triangle-strip",
65
+ },
66
+ });
67
+
68
+ this.noiseSampler = this.device.createSampler({
69
+ label: "DisplacementComposition noisesampler",
70
+ addressModeU: "repeat",
71
+ addressModeV: "repeat",
72
+ addressModeW: "repeat",
73
+ magFilter: "linear",
74
+ minFilter: "linear",
75
+ mipmapFilter: "linear",
76
+ });
77
+
78
+ this.colorSampler = this.device.createSampler({
79
+ label: "DisplacementComposition colorSampler",
80
+ addressModeU: "clamp-to-edge",
81
+ addressModeV: "clamp-to-edge",
82
+ addressModeW: "clamp-to-edge",
83
+ magFilter: "linear",
84
+ minFilter: "linear",
85
+ mipmapFilter: "linear",
86
+ });
87
+ }
88
+
89
+ public getRenderBundle(): GPURenderBundle {
90
+ if (!this.renderBundle) {
91
+ this.renderBundle = this.buildRenderBundle();
92
+ }
93
+ return this.renderBundle;
94
+ }
95
+
96
+ public destroy(): void {
97
+ // nothing to do
98
+ }
99
+
100
+ public setColorTextureView(textureView: GPUTextureView): void {
101
+ this.colorTextureView = textureView;
102
+ this.renderBundle = null;
103
+ }
104
+
105
+ public setNoiseMapTextureView(textureView: GPUTextureView): void {
106
+ this.noiseMapTextureView = textureView;
107
+ this.renderBundle = null;
108
+ }
109
+
110
+ public setDisplacementTextureView(textureView: GPUTextureView): void {
111
+ this.displacementTextureView = textureView;
112
+ this.renderBundle = null;
113
+ }
114
+
115
+ private buildRenderBundle(): GPURenderBundle {
116
+ const bindgroup = this.device.createBindGroup({
117
+ label: "DisplacementComposition bindgroup 0",
118
+ layout: this.renderPipeline.getBindGroupLayout(0),
119
+ entries: [
120
+ {
121
+ binding: 0,
122
+ resource: { buffer: this.displacementParametersBuffer.bufferGpu },
123
+ },
124
+ {
125
+ binding: 1,
126
+ resource: this.colorTextureView,
127
+ },
128
+ {
129
+ binding: 2,
130
+ resource: this.colorSampler,
131
+ },
132
+ {
133
+ binding: 3,
134
+ resource: this.noiseMapTextureView,
135
+ },
136
+ {
137
+ binding: 4,
138
+ resource: this.noiseSampler,
139
+ },
140
+ {
141
+ binding: 5,
142
+ resource: this.displacementTextureView,
143
+ },
144
+ ],
145
+ });
146
+
147
+ const renderBundleEncoder = this.device.createRenderBundleEncoder({
148
+ label: "DisplacementComposition renderbundle encoder",
149
+ colorFormats: [this.targetFormat],
150
+ });
151
+ renderBundleEncoder.setPipeline(this.renderPipeline);
152
+ renderBundleEncoder.setBindGroup(0, bindgroup);
153
+ renderBundleEncoder.draw(4);
154
+ return renderBundleEncoder.finish({ label: "DisplacementComposition renderbundle" });
155
+ }
156
+ }
157
+
158
+ export {
159
+ DisplacementComposition
160
+ };
161
+
@@ -0,0 +1,44 @@
1
+ /// <reference types="@webgpu/types"/>
2
+
3
+ type DisplacementParameters = {
4
+ readonly offsetX: number;
5
+ readonly offsetY: number;
6
+ readonly scale: number;
7
+ };
8
+
9
+ type Parameters = {
10
+ readonly device: GPUDevice;
11
+ readonly initialParameters: DisplacementParameters;
12
+ };
13
+
14
+ class DisplacementParametersBuffer {
15
+ private readonly device: GPUDevice;
16
+
17
+ public readonly bufferGpu: GPUBuffer;
18
+ private needsUpdate: boolean = true;
19
+
20
+ public constructor(params: Parameters) {
21
+ this.device = params.device;
22
+
23
+ this.bufferGpu = this.device.createBuffer({
24
+ label: "DisplacementParametersBuffer buffer",
25
+ size: 16,
26
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
27
+ });
28
+
29
+ this.setParameters(params.initialParameters);
30
+ }
31
+
32
+ public setParameters(params: DisplacementParameters): void {
33
+ this.device.queue.writeBuffer(this.bufferGpu, 0, new Float32Array([params.offsetX, params.offsetY, params.scale]));
34
+ }
35
+
36
+ public destroy(): void {
37
+ this.bufferGpu.destroy();
38
+ }
39
+ }
40
+
41
+ export {
42
+ DisplacementParametersBuffer
43
+ };
44
+