@nodetool-ai/game-renderer 0.8.1 → 0.8.2

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 (273) hide show
  1. package/README.md +46 -0
  2. package/dist/.nodetool-build-stamp +1 -1
  3. package/dist/audio/mixer.d.ts +75 -0
  4. package/dist/audio/mixer.d.ts.map +1 -0
  5. package/dist/audio/mixer.js +345 -0
  6. package/dist/audio/mixer.js.map +1 -0
  7. package/dist/audio/player.d.ts +67 -0
  8. package/dist/audio/player.d.ts.map +1 -0
  9. package/dist/audio/player.js +337 -0
  10. package/dist/audio/player.js.map +1 -0
  11. package/dist/audio/spatial.d.ts +50 -0
  12. package/dist/audio/spatial.d.ts.map +1 -0
  13. package/dist/audio/spatial.js +166 -0
  14. package/dist/audio/spatial.js.map +1 -0
  15. package/dist/audio/voices.d.ts +11 -0
  16. package/dist/audio/voices.d.ts.map +1 -0
  17. package/dist/audio/voices.js +22 -0
  18. package/dist/audio/voices.js.map +1 -0
  19. package/dist/audio.d.ts +6 -38
  20. package/dist/audio.d.ts.map +1 -1
  21. package/dist/audio.js +3 -226
  22. package/dist/audio.js.map +1 -1
  23. package/dist/browser.d.ts +2 -1
  24. package/dist/browser.d.ts.map +1 -1
  25. package/dist/browser.js +5 -5
  26. package/dist/browser.js.map +1 -1
  27. package/dist/browser3d.d.ts +2 -0
  28. package/dist/browser3d.d.ts.map +1 -0
  29. package/dist/browser3d.js +2 -0
  30. package/dist/browser3d.js.map +1 -0
  31. package/dist/build.d.ts.map +1 -1
  32. package/dist/build.js +29 -19
  33. package/dist/build.js.map +1 -1
  34. package/dist/build3d.d.ts +11 -0
  35. package/dist/build3d.d.ts.map +1 -0
  36. package/dist/build3d.js +126 -0
  37. package/dist/build3d.js.map +1 -0
  38. package/dist/canvas2d.d.ts +12 -3
  39. package/dist/canvas2d.d.ts.map +1 -1
  40. package/dist/canvas2d.js +59 -12
  41. package/dist/canvas2d.js.map +1 -1
  42. package/dist/fixed-tick-host.d.ts +10 -0
  43. package/dist/fixed-tick-host.d.ts.map +1 -0
  44. package/dist/fixed-tick-host.js +29 -0
  45. package/dist/fixed-tick-host.js.map +1 -0
  46. package/dist/frame-budget.d.ts +34 -0
  47. package/dist/frame-budget.d.ts.map +1 -0
  48. package/dist/frame-budget.js +52 -0
  49. package/dist/frame-budget.js.map +1 -0
  50. package/dist/frame.d.ts +20 -1
  51. package/dist/frame.d.ts.map +1 -1
  52. package/dist/frame.js +69 -1
  53. package/dist/frame.js.map +1 -1
  54. package/dist/game3d-capture-page.js +5834 -0
  55. package/dist/game3d-player.js +6001 -0
  56. package/dist/imageDiff.d.ts +9 -0
  57. package/dist/imageDiff.d.ts.map +1 -0
  58. package/dist/imageDiff.js +30 -0
  59. package/dist/imageDiff.js.map +1 -0
  60. package/dist/index.d.ts +12 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +9 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/input-bindings.d.ts +76 -0
  65. package/dist/input-bindings.d.ts.map +1 -0
  66. package/dist/input-bindings.js +377 -0
  67. package/dist/input-bindings.js.map +1 -0
  68. package/dist/input.d.ts +1 -1
  69. package/dist/input.d.ts.map +1 -1
  70. package/dist/input.js +1 -14
  71. package/dist/input.js.map +1 -1
  72. package/dist/input3d.d.ts +3 -0
  73. package/dist/input3d.d.ts.map +1 -0
  74. package/dist/input3d.js +3 -0
  75. package/dist/input3d.js.map +1 -0
  76. package/dist/node.d.ts +5 -0
  77. package/dist/node.d.ts.map +1 -1
  78. package/dist/node.js +28 -11
  79. package/dist/node.js.map +1 -1
  80. package/dist/node3d.d.ts +29 -0
  81. package/dist/node3d.d.ts.map +1 -0
  82. package/dist/node3d.js +160 -0
  83. package/dist/node3d.js.map +1 -0
  84. package/dist/particles/curves.d.ts +16 -0
  85. package/dist/particles/curves.d.ts.map +1 -0
  86. package/dist/particles/curves.js +49 -0
  87. package/dist/particles/curves.js.map +1 -0
  88. package/dist/particles/index.d.ts +6 -0
  89. package/dist/particles/index.d.ts.map +1 -0
  90. package/dist/particles/index.js +6 -0
  91. package/dist/particles/index.js.map +1 -0
  92. package/dist/particles/random.d.ts +10 -0
  93. package/dist/particles/random.d.ts.map +1 -0
  94. package/dist/particles/random.js +26 -0
  95. package/dist/particles/random.js.map +1 -0
  96. package/dist/particles/render2d.d.ts +40 -0
  97. package/dist/particles/render2d.d.ts.map +1 -0
  98. package/dist/particles/render2d.js +70 -0
  99. package/dist/particles/render2d.js.map +1 -0
  100. package/dist/particles/shapes.d.ts +18 -0
  101. package/dist/particles/shapes.d.ts.map +1 -0
  102. package/dist/particles/shapes.js +92 -0
  103. package/dist/particles/shapes.js.map +1 -0
  104. package/dist/particles/simulator.d.ts +88 -0
  105. package/dist/particles/simulator.d.ts.map +1 -0
  106. package/dist/particles/simulator.js +371 -0
  107. package/dist/particles/simulator.js.map +1 -0
  108. package/dist/player-v1.js +2502 -0
  109. package/dist/preparation3d.d.ts +3 -0
  110. package/dist/preparation3d.d.ts.map +1 -0
  111. package/dist/preparation3d.js +3 -0
  112. package/dist/preparation3d.js.map +1 -0
  113. package/dist/renderPipeline.d.ts +12 -0
  114. package/dist/renderPipeline.d.ts.map +1 -0
  115. package/dist/renderPipeline.js +17 -0
  116. package/dist/renderPipeline.js.map +1 -0
  117. package/dist/renderer3d/animation/index.d.ts +11 -0
  118. package/dist/renderer3d/animation/index.d.ts.map +1 -0
  119. package/dist/renderer3d/animation/index.js +212 -0
  120. package/dist/renderer3d/animation/index.js.map +1 -0
  121. package/dist/renderer3d/assets/cache.d.ts +14 -0
  122. package/dist/renderer3d/assets/cache.d.ts.map +1 -0
  123. package/dist/renderer3d/assets/cache.js +74 -0
  124. package/dist/renderer3d/assets/cache.js.map +1 -0
  125. package/dist/renderer3d/assets/dispose.d.ts +4 -0
  126. package/dist/renderer3d/assets/dispose.d.ts.map +1 -0
  127. package/dist/renderer3d/assets/dispose.js +41 -0
  128. package/dist/renderer3d/assets/dispose.js.map +1 -0
  129. package/dist/renderer3d/assets/hdri-contract.d.ts +23 -0
  130. package/dist/renderer3d/assets/hdri-contract.d.ts.map +1 -0
  131. package/dist/renderer3d/assets/hdri-contract.js +2 -0
  132. package/dist/renderer3d/assets/hdri-contract.js.map +1 -0
  133. package/dist/renderer3d/assets/instances.d.ts +5 -0
  134. package/dist/renderer3d/assets/instances.d.ts.map +1 -0
  135. package/dist/renderer3d/assets/instances.js +86 -0
  136. package/dist/renderer3d/assets/instances.js.map +1 -0
  137. package/dist/renderer3d/capture-driver-types.d.ts +42 -0
  138. package/dist/renderer3d/capture-driver-types.d.ts.map +1 -0
  139. package/dist/renderer3d/capture-driver-types.js +2 -0
  140. package/dist/renderer3d/capture-driver-types.js.map +1 -0
  141. package/dist/renderer3d/capture-page.d.ts +36 -0
  142. package/dist/renderer3d/capture-page.d.ts.map +1 -0
  143. package/dist/renderer3d/capture-page.js +68 -0
  144. package/dist/renderer3d/capture-page.js.map +1 -0
  145. package/dist/renderer3d/culling.d.ts +10 -0
  146. package/dist/renderer3d/culling.d.ts.map +1 -0
  147. package/dist/renderer3d/culling.js +27 -0
  148. package/dist/renderer3d/culling.js.map +1 -0
  149. package/dist/renderer3d/environment/index.d.ts +4 -0
  150. package/dist/renderer3d/environment/index.d.ts.map +1 -0
  151. package/dist/renderer3d/environment/index.js +9 -0
  152. package/dist/renderer3d/environment/index.js.map +1 -0
  153. package/dist/renderer3d/environment/sky.d.ts +45 -0
  154. package/dist/renderer3d/environment/sky.d.ts.map +1 -0
  155. package/dist/renderer3d/environment/sky.js +221 -0
  156. package/dist/renderer3d/environment/sky.js.map +1 -0
  157. package/dist/renderer3d/hud/fonts.d.ts +13 -0
  158. package/dist/renderer3d/hud/fonts.d.ts.map +1 -0
  159. package/dist/renderer3d/hud/fonts.js +69 -0
  160. package/dist/renderer3d/hud/fonts.js.map +1 -0
  161. package/dist/renderer3d/hud/paint.d.ts +4 -0
  162. package/dist/renderer3d/hud/paint.d.ts.map +1 -0
  163. package/dist/renderer3d/hud/paint.js +22 -0
  164. package/dist/renderer3d/hud/paint.js.map +1 -0
  165. package/dist/renderer3d/import.d.ts +34 -0
  166. package/dist/renderer3d/import.d.ts.map +1 -0
  167. package/dist/renderer3d/import.js +408 -0
  168. package/dist/renderer3d/import.js.map +1 -0
  169. package/dist/renderer3d/index.d.ts +74 -0
  170. package/dist/renderer3d/index.d.ts.map +1 -0
  171. package/dist/renderer3d/index.js +338 -0
  172. package/dist/renderer3d/index.js.map +1 -0
  173. package/dist/renderer3d/lights/index.d.ts +7 -0
  174. package/dist/renderer3d/lights/index.d.ts.map +1 -0
  175. package/dist/renderer3d/lights/index.js +67 -0
  176. package/dist/renderer3d/lights/index.js.map +1 -0
  177. package/dist/renderer3d/materials/index.d.ts +4 -0
  178. package/dist/renderer3d/materials/index.d.ts.map +1 -0
  179. package/dist/renderer3d/materials/index.js +8 -0
  180. package/dist/renderer3d/materials/index.js.map +1 -0
  181. package/dist/renderer3d/passes/post-processing.d.ts +44 -0
  182. package/dist/renderer3d/passes/post-processing.d.ts.map +1 -0
  183. package/dist/renderer3d/passes/post-processing.js +213 -0
  184. package/dist/renderer3d/passes/post-processing.js.map +1 -0
  185. package/dist/renderer3d/pipeline.d.ts +15 -0
  186. package/dist/renderer3d/pipeline.d.ts.map +1 -0
  187. package/dist/renderer3d/pipeline.js +10 -0
  188. package/dist/renderer3d/pipeline.js.map +1 -0
  189. package/dist/renderer3d/playwright-contract.d.ts +3 -0
  190. package/dist/renderer3d/playwright-contract.d.ts.map +1 -0
  191. package/dist/renderer3d/playwright-contract.js +4 -0
  192. package/dist/renderer3d/playwright-contract.js.map +1 -0
  193. package/dist/renderer3d/preparation.d.ts +79 -0
  194. package/dist/renderer3d/preparation.d.ts.map +1 -0
  195. package/dist/renderer3d/preparation.js +458 -0
  196. package/dist/renderer3d/preparation.js.map +1 -0
  197. package/dist/renderer3d/scene-sync.d.ts +8 -0
  198. package/dist/renderer3d/scene-sync.d.ts.map +1 -0
  199. package/dist/renderer3d/scene-sync.js +96 -0
  200. package/dist/renderer3d/scene-sync.js.map +1 -0
  201. package/dist/renderer3d/shadows/cascades.d.ts +40 -0
  202. package/dist/renderer3d/shadows/cascades.d.ts.map +1 -0
  203. package/dist/renderer3d/shadows/cascades.js +244 -0
  204. package/dist/renderer3d/shadows/cascades.js.map +1 -0
  205. package/dist/renderer3d/shadows/index.d.ts +44 -0
  206. package/dist/renderer3d/shadows/index.d.ts.map +1 -0
  207. package/dist/renderer3d/shadows/index.js +129 -0
  208. package/dist/renderer3d/shadows/index.js.map +1 -0
  209. package/dist/renderer3d/types.d.ts +18 -0
  210. package/dist/renderer3d/types.d.ts.map +1 -0
  211. package/dist/renderer3d/types.js +2 -0
  212. package/dist/renderer3d/types.js.map +1 -0
  213. package/dist/smoke.d.ts +15 -0
  214. package/dist/smoke.d.ts.map +1 -0
  215. package/dist/smoke.js +138 -0
  216. package/dist/smoke.js.map +1 -0
  217. package/dist/standalone-player.d.ts +11 -1
  218. package/dist/standalone-player.d.ts.map +1 -1
  219. package/dist/standalone-player.js +67 -41
  220. package/dist/standalone-player.js.map +1 -1
  221. package/dist/standalone-player3d.d.ts +14 -0
  222. package/dist/standalone-player3d.d.ts.map +1 -0
  223. package/dist/standalone-player3d.js +255 -0
  224. package/dist/standalone-player3d.js.map +1 -0
  225. package/dist/touch-controls.d.ts +22 -8
  226. package/dist/touch-controls.d.ts.map +1 -1
  227. package/dist/touch-controls.js +88 -21
  228. package/dist/touch-controls.js.map +1 -1
  229. package/dist/ui/controller.d.ts +59 -0
  230. package/dist/ui/controller.d.ts.map +1 -0
  231. package/dist/ui/controller.js +201 -0
  232. package/dist/ui/controller.js.map +1 -0
  233. package/dist/ui/hud2d.d.ts +11 -0
  234. package/dist/ui/hud2d.d.ts.map +1 -0
  235. package/dist/ui/hud2d.js +25 -0
  236. package/dist/ui/hud2d.js.map +1 -0
  237. package/dist/ui/layout.d.ts +42 -0
  238. package/dist/ui/layout.d.ts.map +1 -0
  239. package/dist/ui/layout.js +182 -0
  240. package/dist/ui/layout.js.map +1 -0
  241. package/dist/ui/paint.d.ts +45 -0
  242. package/dist/ui/paint.d.ts.map +1 -0
  243. package/dist/ui/paint.js +121 -0
  244. package/dist/ui/paint.js.map +1 -0
  245. package/dist/webgpu/effects.d.ts +22 -0
  246. package/dist/webgpu/effects.d.ts.map +1 -0
  247. package/dist/webgpu/effects.js +69 -0
  248. package/dist/webgpu/effects.js.map +1 -0
  249. package/dist/webgpu/hud.d.ts +4 -0
  250. package/dist/webgpu/hud.d.ts.map +1 -0
  251. package/dist/webgpu/hud.js +27 -0
  252. package/dist/webgpu/hud.js.map +1 -0
  253. package/dist/webgpu/lighting.d.ts +4 -0
  254. package/dist/webgpu/lighting.d.ts.map +1 -0
  255. package/dist/webgpu/lighting.js +47 -0
  256. package/dist/webgpu/lighting.js.map +1 -0
  257. package/dist/webgpu/pipeline.d.ts +11 -0
  258. package/dist/webgpu/pipeline.d.ts.map +1 -0
  259. package/dist/webgpu/pipeline.js +11 -0
  260. package/dist/webgpu/pipeline.js.map +1 -0
  261. package/dist/webgpu/sprites.d.ts +6 -0
  262. package/dist/webgpu/sprites.d.ts.map +1 -0
  263. package/dist/webgpu/sprites.js +74 -0
  264. package/dist/webgpu/sprites.js.map +1 -0
  265. package/dist/webgpu/types.d.ts +14 -0
  266. package/dist/webgpu/types.d.ts.map +1 -0
  267. package/dist/webgpu/types.js +2 -0
  268. package/dist/webgpu/types.js.map +1 -0
  269. package/dist/webgpu.d.ts +4 -3
  270. package/dist/webgpu.d.ts.map +1 -1
  271. package/dist/webgpu.js +119 -279
  272. package/dist/webgpu.js.map +1 -1
  273. package/package.json +47 -10
package/dist/webgpu.js CHANGED
@@ -1,142 +1,13 @@
1
- import { colorBrightnessContrastV1, colorCubeLutV1, createDefaultRegistry, createExecutor, createGPUContextFromDevice, createLabeledTexture, createRecipeRunner, filtersGlowV1, LabeledTexture } from "@nodetool-ai/gpu/pool";
2
- import * as d from "typegpu/data";
1
+ import { INSTANCE_FLOATS, SHADER, appendBatch, writeInstance } from "./webgpu/sprites.js";
2
+ import { LIGHT_SHADER, gameLightingUniforms } from "./webgpu/lighting.js";
3
+ import { PRESENT_SHADER, encodeGameEffects } from "./webgpu/effects.js";
4
+ import { HUD_SHADER, paintWebGPUHud } from "./webgpu/hud.js";
5
+ import { loadGameUiImages } from "./ui/hud2d.js";
6
+ import { WebGPURenderPipeline } from "./webgpu/pipeline.js";
7
+ import { createDefaultRegistry, createExecutor, createGPUContextFromDevice, createLabeledTexture, createRecipeRunner, LabeledTexture } from "@nodetool-ai/gpu/pool";
3
8
  import { imageHeight, imageWidth } from "./canvas2d.js";
4
- import { paintHud, parseTint, projectedCamera, visibleItems } from "./frame.js";
5
- const INSTANCE_FLOATS = 14;
6
- const SHADER = `
7
- struct Camera { center: vec2f, viewport: vec2f, zoom: f32, padding0: f32, padding1: f32, padding2: f32 };
8
- override linearizeInput: bool = false;
9
- fn srgbToLinear(rgb: vec3f) -> vec3f {
10
- return select(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3f(2.4)), rgb > vec3f(0.04045));
11
- }
12
- fn linearToSrgb(rgb: vec3f) -> vec3f {
13
- return select(rgb * 12.92, 1.055 * pow(rgb, vec3f(1.0 / 2.4)) - 0.055, rgb > vec3f(0.0031308));
14
- }
15
- @group(0) @binding(0) var<uniform> camera: Camera;
16
- @group(0) @binding(1) var atlasSampler: sampler;
17
- @group(0) @binding(2) var atlas: texture_2d<f32>;
18
- @group(1) @binding(0) var lightSampler: sampler;
19
- @group(1) @binding(1) var lightTexture: texture_2d<f32>;
20
-
21
- struct VertexInput {
22
- @builtin(vertex_index) corner: u32,
23
- @location(0) center: vec2f,
24
- @location(1) size: vec2f,
25
- @location(2) uv: vec4f,
26
- @location(3) color: vec4f,
27
- @location(4) rotation: f32,
28
- @location(5) unlit: f32,
29
- };
30
- struct VertexOutput {
31
- @builtin(position) position: vec4f,
32
- @location(0) uv: vec2f,
33
- @location(1) color: vec4f,
34
- @location(3) @interpolate(flat) unlit: f32,
35
- @location(4) lightUv: vec2f,
36
- };
37
- @vertex fn vertexMain(input: VertexInput) -> VertexOutput {
38
- let corners = array<vec2f, 6>(
39
- vec2f(-0.5, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, 0.5),
40
- vec2f(0.5, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
41
- let local = corners[input.corner] * input.size;
42
- let sine = sin(input.rotation);
43
- let cosine = cos(input.rotation);
44
- let rotated = vec2f(local.x * cosine - local.y * sine, local.x * sine + local.y * cosine);
45
- let world = input.center + rotated;
46
- let clip = (world - camera.center) * camera.zoom * 2.0 / camera.viewport;
47
- var output: VertexOutput;
48
- output.position = vec4f(clip, 0.0, 1.0);
49
- let uvCorner = corners[input.corner] + vec2f(0.5, 0.5);
50
- output.uv = input.uv.xy + vec2f(uvCorner.x, 1.0 - uvCorner.y) * input.uv.zw;
51
- output.color = input.color;
52
- output.unlit = input.unlit;
53
- output.lightUv = clip * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);
54
- return output;
55
- }
56
- @fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
57
- let sampled = textureSample(atlas, atlasSampler, input.uv) * input.color;
58
- var straight = select(sampled.rgb, srgbToLinear(sampled.rgb), linearizeInput);
59
- if (input.unlit < 0.5) {
60
- let irradiance = textureSampleLevel(lightTexture, lightSampler, input.lightUv, 0.0).rgb;
61
- let lit = clamp(srgbToLinear(sampled.rgb) * irradiance, vec3f(0.0), vec3f(1.0));
62
- straight = select(linearToSrgb(lit), lit, linearizeInput);
63
- }
64
- return vec4f(straight * sampled.a, sampled.a);
65
- }`;
66
- const LIGHT_SHADER = `
67
- struct Camera { center: vec2f, viewport: vec2f, zoom: f32, padding0: f32, padding1: f32, padding2: f32 };
68
- struct PointLight { position: vec2f, radius: f32, intensity: f32, color: vec4f, falloff: f32, pad0: f32, pad1: f32, pad2: f32 };
69
- struct Lighting { ambient: vec4f, info: vec4f, points: array<PointLight, 32> };
70
- fn srgbToLinear(rgb: vec3f) -> vec3f {
71
- return select(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3f(2.4)), rgb > vec3f(0.04045));
72
- }
73
- @group(0) @binding(0) var<uniform> camera: Camera;
74
- @group(0) @binding(1) var<uniform> lighting: Lighting;
75
- struct VertexOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f };
76
- @vertex fn vertexMain(@builtin(vertex_index) index: u32) -> VertexOutput {
77
- let positions = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
78
- var output: VertexOutput;
79
- output.position = vec4f(positions[index], 0.0, 1.0);
80
- output.uv = positions[index] * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);
81
- return output;
82
- }
83
- @fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
84
- let world = camera.center + (input.uv - vec2f(0.5, 0.5)) * camera.viewport / camera.zoom * vec2f(1.0, -1.0);
85
- var irradiance = srgbToLinear(lighting.ambient.rgb) * lighting.ambient.a;
86
- for (var i = 0u; i < 32u; i += 1u) {
87
- if (i >= u32(lighting.info.x)) { break; }
88
- let point = lighting.points[i];
89
- let distance = length(world - point.position);
90
- if (distance < point.radius) {
91
- irradiance += srgbToLinear(point.color.rgb) * point.intensity * pow(1.0 - distance / point.radius, point.falloff);
92
- }
93
- }
94
- return vec4f(irradiance, 1.0);
95
- }`;
96
- const PRESENT_SHADER = `
97
- override decodeInput: bool = false;
98
- override encodeOutput: bool = true;
99
- override cropScaleX: f32 = 1.0;
100
- override cropScaleY: f32 = 1.0;
101
- override cropOffsetX: f32 = 0.0;
102
- override cropOffsetY: f32 = 0.0;
103
- @group(0) @binding(0) var sourceSampler: sampler;
104
- @group(0) @binding(1) var sourceTexture: texture_2d<f32>;
105
- struct VertexOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f };
106
- @vertex fn vertexMain(@builtin(vertex_index) index: u32) -> VertexOutput {
107
- let positions = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
108
- var output: VertexOutput;
109
- output.position = vec4f(positions[index], 0.0, 1.0);
110
- output.uv = positions[index] * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);
111
- return output;
112
- }
113
- @fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
114
- let uv = input.uv * vec2f(cropScaleX, cropScaleY) + vec2f(cropOffsetX, cropOffsetY);
115
- let premul = textureSample(sourceTexture, sourceSampler, uv);
116
- let straight = premul.rgb / max(premul.a, 1.0 / 255.0);
117
- let linear = select(straight / 12.92, pow((straight + 0.055) / 1.055, vec3f(2.4)),
118
- straight > vec3f(0.04045));
119
- let encoded = select(straight * 12.92, 1.055 * pow(straight, vec3f(1.0 / 2.4)) - 0.055,
120
- straight > vec3f(0.0031308));
121
- var converted = select(straight, linear, decodeInput);
122
- converted = select(converted, encoded, encodeOutput);
123
- return vec4f(clamp(converted, vec3f(0.0), vec3f(1.0)) * premul.a, premul.a);
124
- }`;
125
- const HUD_SHADER = `
126
- @group(0) @binding(0) var sourceSampler: sampler;
127
- @group(0) @binding(1) var sourceTexture: texture_2d<f32>;
128
- struct VertexOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f };
129
- @vertex fn vertexMain(@builtin(vertex_index) index: u32) -> VertexOutput {
130
- let positions = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
131
- var output: VertexOutput;
132
- output.position = vec4f(positions[index], 0.0, 1.0);
133
- output.uv = positions[index] * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);
134
- return output;
135
- }
136
- @fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
137
- let color = textureSample(sourceTexture, sourceSampler, input.uv);
138
- return vec4f(color.rgb * color.a, color.a);
139
- }`;
9
+ import { parseTint, projectedCamera, sourceRect, visibleItems } from "./frame.js";
10
+ import { PARTICLE_DOT_ASSET, PARTICLE_DOT_SIZE, particleDotPixels } from "./particles/render2d.js";
140
11
  /** Draws ordered sprites as consecutive texture batches on a private WebGPU device. */
141
12
  export class WebGPUGameRenderer {
142
13
  canvas;
@@ -145,6 +16,7 @@ export class WebGPUGameRenderer {
145
16
  format;
146
17
  assets;
147
18
  adapterType;
19
+ renderPipeline = new WebGPURenderPipeline();
148
20
  backend = "webgpu";
149
21
  pipeline;
150
22
  effectSpritePipeline;
@@ -179,6 +51,7 @@ export class WebGPUGameRenderer {
179
51
  textures = new Map();
180
52
  validatedLuts = new Set();
181
53
  fallback;
54
+ particleDot;
182
55
  placeholders = new Map();
183
56
  instanceBuffer;
184
57
  instanceCapacity = 0;
@@ -297,6 +170,10 @@ export class WebGPUGameRenderer {
297
170
  const fallbackTexture = device.createTexture({ size: [1, 1], format: "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST });
298
171
  device.queue.writeTexture({ texture: fallbackTexture }, new Uint8Array([255, 0, 255, 255]), { bytesPerRow: 4 }, [1, 1]);
299
172
  this.fallback = this.textureEntry(fallbackTexture, 1, 1);
173
+ const dotTexture = device.createTexture({ size: [PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE], format: "rgba8unorm",
174
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST });
175
+ device.queue.writeTexture({ texture: dotTexture }, particleDotPixels(), { bytesPerRow: PARTICLE_DOT_SIZE * 4 }, [PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE]);
176
+ this.particleDot = this.textureEntry(dotTexture, PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE, "linear");
300
177
  void device.lost.then(() => { this.lost = true; });
301
178
  }
302
179
  get capabilities() {
@@ -441,6 +318,9 @@ export class WebGPUGameRenderer {
441
318
  return this.textureEntry(texture, width, height, sampling);
442
319
  }
443
320
  async getTexture(assetId, sampling) {
321
+ if (assetId === PARTICLE_DOT_ASSET) {
322
+ return { texture: this.particleDot, uploadedBytes: 0 };
323
+ }
444
324
  const cached = this.textures.get(assetId);
445
325
  if (cached) {
446
326
  return { texture: cached, uploadedBytes: 0 };
@@ -498,14 +378,14 @@ export class WebGPUGameRenderer {
498
378
  this.placeholders.set(assetId, entry);
499
379
  return entry;
500
380
  }
501
- async render(frame, interpolation) {
381
+ async render(frame, interpolation, particles) {
502
382
  if (this.disposed || this.lost) {
503
383
  throw new Error(this.lost ? "WebGPU device was lost" : "Game renderer is disposed");
504
384
  }
505
385
  this.device.pushErrorScope("validation");
506
386
  let popped = false;
507
387
  try {
508
- const stats = await this.renderFrame(frame, interpolation);
388
+ const stats = await this.renderFrame(frame, interpolation, particles);
509
389
  const validationError = await this.device.popErrorScope();
510
390
  popped = true;
511
391
  if (validationError) {
@@ -521,16 +401,16 @@ export class WebGPUGameRenderer {
521
401
  throw error;
522
402
  }
523
403
  }
524
- async renderFrame(frame, interpolation) {
404
+ async renderFrame(frame, interpolation, particles) {
525
405
  const bloomRadius = this.effects.reduce((maximum, effect) => effect.kind === "bloom" ? Math.max(maximum, effect.radius) : maximum, 0);
526
406
  const overscanPixels = Math.ceil(bloomRadius * 2);
527
407
  const overscanWorld = overscanPixels * frame.width / (Math.max(1, this.canvas.width) * projectedCamera(frame, interpolation).zoom);
528
- const items = visibleItems(frame, interpolation, overscanWorld);
408
+ const items = visibleItems(frame, interpolation, overscanWorld, particles && { field: particles });
529
409
  const sampling = new Map(items.map((item) => [item.assetId, item.sampling]));
530
410
  const assetIds = [...sampling.keys()];
531
411
  const textureResults = await Promise.all(assetIds.map((assetId) => this.getTexture(assetId, sampling.get(assetId) ?? "nearest")));
532
412
  const textureById = new Map(assetIds.map((assetId, index) => [assetId, textureResults[index]]));
533
- const hudTexture = this.updateHud(frame);
413
+ const hudTexture = this.updateHud(frame, await loadGameUiImages(frame, (assetId) => this.assets.get(assetId)));
534
414
  const count = items.length + (hudTexture ? 1 : 0);
535
415
  this.ensureInstanceCapacity(count);
536
416
  const instances = new Float32Array(count * INSTANCE_FLOATS);
@@ -542,22 +422,23 @@ export class WebGPUGameRenderer {
542
422
  continue;
543
423
  }
544
424
  const texture = result.texture;
545
- const rect = texture.width === 1 && texture.height === 1 ? { x: 0, y: 0, width: 1, height: 1 } : item.frame ?? { x: 0, y: 0, width: texture.width, height: texture.height };
425
+ const rect = texture.width === 1 && texture.height === 1 ? { x: 0, y: 0, width: 1, height: 1 } : sourceRect(item, texture.width, texture.height);
546
426
  const tint = parseTint(item.tint);
547
- const insetX = item.sampling === "linear" && item.frame && rect.width > 1 ? 0.5 : 0;
548
- const insetY = item.sampling === "linear" && item.frame && rect.height > 1 ? 0.5 : 0;
427
+ const subRect = item.frame !== undefined || item.cell !== undefined;
428
+ const insetX = item.sampling === "linear" && subRect && rect.width > 1 ? 0.5 : 0;
429
+ const insetY = item.sampling === "linear" && subRect && rect.height > 1 ? 0.5 : 0;
549
430
  const u = (rect.x + insetX) / texture.width;
550
431
  const v = (rect.y + insetY) / texture.height;
551
432
  const du = (rect.width - 2 * insetX) / texture.width;
552
433
  const dv = (rect.height - 2 * insetY) / texture.height;
553
- this.writeInstance(instances, index, item.x, item.y, item.width, item.height, item.flipX ? u + du : u, item.flipY ? v + dv : v, item.flipX ? -du : du, item.flipY ? -dv : dv, tint[0], tint[1], tint[2], item.opacity, item.rotation, item.unlit ? 1 : 0);
554
- this.appendBatch(batches, texture, item.blend, index);
434
+ writeInstance(instances, index, item.x, item.y, item.width, item.height, item.flipX ? u + du : u, item.flipY ? v + dv : v, item.flipX ? -du : du, item.flipY ? -dv : dv, tint[0], tint[1], tint[2], item.opacity, item.rotation, item.unlit ? 1 : 0);
435
+ appendBatch(batches, texture, item.blend, index);
555
436
  }
556
437
  if (hudTexture) {
557
438
  const scale = frame.camera.zoom;
558
439
  const projected = projectedCamera(frame, interpolation);
559
- this.writeInstance(instances, items.length, projected.x, projected.y, frame.width / scale, frame.height / scale, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1);
560
- this.appendBatch(batches, hudTexture, "normal", items.length);
440
+ writeInstance(instances, items.length, projected.x, projected.y, frame.width / scale, frame.height / scale, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1);
441
+ appendBatch(batches, hudTexture, "normal", items.length);
561
442
  }
562
443
  const projected = projectedCamera(frame, interpolation);
563
444
  const camera = new Float32Array([projected.x, projected.y,
@@ -565,18 +446,8 @@ export class WebGPUGameRenderer {
565
446
  frame.height * (this.canvas.height + overscanPixels * 2) / Math.max(1, this.canvas.height),
566
447
  frame.camera.zoom, 0, 0, 0]);
567
448
  this.device.queue.writeBuffer(this.cameraBuffer, 0, camera);
568
- const lighting = new Float32Array(392);
569
449
  const sceneLighting = frame.lighting;
570
- if (sceneLighting) {
571
- const ambient = parseTint(sceneLighting.ambient.color);
572
- lighting.set([ambient[0], ambient[1], ambient[2], sceneLighting.ambient.intensity,
573
- sceneLighting.points.length, 1], 0);
574
- for (const [index, point] of sceneLighting.points.entries()) {
575
- const color = parseTint(point.color);
576
- lighting.set([point.x, point.y, point.radius, point.intensity,
577
- color[0], color[1], color[2], 0, point.falloff], 8 + index * 12);
578
- }
579
- }
450
+ const lighting = gameLightingUniforms(frame);
580
451
  this.device.queue.writeBuffer(this.lightingBuffer, 0, lighting);
581
452
  if (count > 0 && this.instanceBuffer) {
582
453
  this.device.queue.writeBuffer(this.instanceBuffer, 0, instances);
@@ -591,107 +462,94 @@ export class WebGPUGameRenderer {
591
462
  this.lightTarget.destroy();
592
463
  this.lightTarget = undefined;
593
464
  }
594
- if (sceneLighting) {
595
- const target = this.ensureLightTarget(this.canvas.width + (hasEffects ? overscanPixels * 2 : 0), this.canvas.height + (hasEffects ? overscanPixels * 2 : 0));
596
- const lightGroup = this.device.createBindGroup({ layout: this.lightLayout, entries: [
597
- { binding: 0, resource: { buffer: this.cameraBuffer } },
598
- { binding: 1, resource: { buffer: this.lightingBuffer } },
599
- ] });
600
- const lightPass = encoder.beginRenderPass({ colorAttachments: [{ view: target.createView(),
601
- loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 1 } }] });
602
- lightPass.setPipeline(this.lightPipeline);
603
- lightPass.setBindGroup(0, lightGroup);
604
- lightPass.draw(3);
605
- lightPass.end();
606
- target.markWritten();
607
- }
608
- const lightSampleGroup = this.device.createBindGroup({ layout: this.lightSampleLayout, entries: [
609
- { binding: 0, resource: this.sampler },
610
- { binding: 1, resource: (this.lightTarget?.texture ?? this.lightFallback).createView() },
611
- ] });
612
- const pass = encoder.beginRenderPass({ colorAttachments: [{
613
- view: hasEffects ? this.sourceTarget.createView() : this.context.getCurrentTexture().createView(),
614
- loadOp: "clear",
615
- storeOp: "store",
616
- clearValue: { r: 0, g: 0, b: 0, a: 0 },
617
- }] });
618
- if (this.instanceBuffer) {
619
- pass.setVertexBuffer(0, this.instanceBuffer);
620
- pass.setBindGroup(1, lightSampleGroup);
621
- let blend;
622
- for (const batch of batches) {
623
- if (hudAfterEffects && batch.first >= items.length) {
624
- continue;
625
- }
626
- if (batch.blend !== blend) {
627
- blend = batch.blend;
628
- pass.setPipeline(blend === "additive"
629
- ? hasEffects ? this.effectAdditivePipeline : this.additivePipeline
630
- : hasEffects ? this.effectSpritePipeline : this.pipeline);
465
+ await this.renderPipeline.render({
466
+ lighting: () => {
467
+ if (sceneLighting) {
468
+ const target = this.ensureLightTarget(this.canvas.width + (hasEffects ? overscanPixels * 2 : 0), this.canvas.height + (hasEffects ? overscanPixels * 2 : 0));
469
+ const lightGroup = this.device.createBindGroup({ layout: this.lightLayout, entries: [
470
+ { binding: 0, resource: { buffer: this.cameraBuffer } },
471
+ { binding: 1, resource: { buffer: this.lightingBuffer } },
472
+ ] });
473
+ const lightPass = encoder.beginRenderPass({ colorAttachments: [{ view: target.createView(),
474
+ loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 1 } }] });
475
+ lightPass.setPipeline(this.lightPipeline);
476
+ lightPass.setBindGroup(0, lightGroup);
477
+ lightPass.draw(3);
478
+ lightPass.end();
479
+ target.markWritten();
631
480
  }
632
- pass.setBindGroup(0, batch.texture.bindGroup);
633
- pass.draw(6, batch.count, 0, batch.first);
634
- }
635
- }
636
- pass.end();
637
- if (hasEffects) {
638
- let source = this.sourceTarget;
639
- let output = this.effectTarget;
640
- source.markWritten();
641
- this.effectContext.uniformRing.beginSubmission();
642
- for (const effect of this.effects) {
643
- if (effect.kind === "brightnessContrast") {
644
- this.effectExecutor.encode({ ctx: this.effectContext, module: colorBrightnessContrastV1,
645
- encoder, inputs: { source }, output,
646
- params: { brightness: effect.brightness, contrast: effect.contrast },
647
- dispatch: { kind: "fragment" } });
481
+ },
482
+ sprites: () => {
483
+ const lightSampleGroup = this.device.createBindGroup({ layout: this.lightSampleLayout, entries: [
484
+ { binding: 0, resource: this.sampler },
485
+ { binding: 1, resource: (this.lightTarget?.texture ?? this.lightFallback).createView() },
486
+ ] });
487
+ const pass = encoder.beginRenderPass({ colorAttachments: [{
488
+ view: hasEffects ? this.sourceTarget.createView() : this.context.getCurrentTexture().createView(),
489
+ loadOp: "clear",
490
+ storeOp: "store",
491
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
492
+ }] });
493
+ if (this.instanceBuffer) {
494
+ pass.setVertexBuffer(0, this.instanceBuffer);
495
+ pass.setBindGroup(1, lightSampleGroup);
496
+ let blend;
497
+ for (const batch of batches) {
498
+ if (hudAfterEffects && batch.first >= items.length) {
499
+ continue;
500
+ }
501
+ if (batch.blend !== blend) {
502
+ blend = batch.blend;
503
+ pass.setPipeline(blend === "additive"
504
+ ? hasEffects ? this.effectAdditivePipeline : this.additivePipeline
505
+ : hasEffects ? this.effectSpritePipeline : this.pipeline);
506
+ }
507
+ pass.setBindGroup(0, batch.texture.bindGroup);
508
+ pass.draw(6, batch.count, 0, batch.first);
509
+ }
648
510
  }
649
- else if (effect.kind === "bloom") {
650
- this.effectRecipeRunner.encode({ ctx: this.effectContext, module: filtersGlowV1,
651
- registry: this.effectRegistry, executor: this.effectExecutor, encoder, inputs: { source }, output,
652
- params: { threshold: effect.threshold, softness: effect.softness,
653
- radius: effect.radius, intensity: effect.intensity } });
511
+ pass.end();
512
+ },
513
+ effects: async () => {
514
+ if (hasEffects) {
515
+ let source = this.sourceTarget;
516
+ const output = this.effectTarget;
517
+ source = await encodeGameEffects({ effects: this.effects, source, output, encoder,
518
+ effectContext: this.effectContext, effectExecutor: this.effectExecutor,
519
+ effectRecipeRunner: this.effectRecipeRunner, effectRegistry: this.effectRegistry,
520
+ srgbTarget: this.srgbTarget, lutTarget: this.lutTarget, toSrgbPipeline: this.toSrgbPipeline,
521
+ toLinearPipeline: this.toLinearPipeline, getLutTexture: (id, size) => this.getLutTexture(id, size),
522
+ convertColor: (command, input, target, pipeline) => this.convertColor(command, input, target, pipeline) });
523
+ const presentGroup = this.device.createBindGroup({ layout: this.presentLayout, entries: [
524
+ { binding: 0, resource: this.sampler }, { binding: 1, resource: source.createView() },
525
+ ] });
526
+ const presentPass = encoder.beginRenderPass({ colorAttachments: [{
527
+ view: this.context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store",
528
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
529
+ }] });
530
+ presentPass.setPipeline(this.presentPipeline);
531
+ presentPass.setBindGroup(0, presentGroup);
532
+ presentPass.draw(3);
533
+ presentPass.end();
654
534
  }
655
- else {
656
- const srgbSource = this.srgbTarget;
657
- const srgbOutput = this.lutTarget;
658
- const lut = await this.getLutTexture(effect.assetId, effect.size);
659
- this.convertColor(encoder, source, srgbSource, this.toSrgbPipeline);
660
- this.effectExecutor.encode({ ctx: this.effectContext, module: colorCubeLutV1,
661
- encoder, inputs: { source: srgbSource, lut }, output: srgbOutput,
662
- params: { size: effect.size, intensity: effect.intensity,
663
- domainMin: d.vec4f(...effect.domainMin, 0), domainMax: d.vec4f(...effect.domainMax, 0) },
664
- dispatch: { kind: "fragment" } });
665
- this.convertColor(encoder, srgbOutput, output, this.toLinearPipeline);
535
+ },
536
+ hud: () => {
537
+ if (hudAfterEffects && hudTexture) {
538
+ const hudGroup = this.device.createBindGroup({ layout: this.presentLayout, entries: [
539
+ { binding: 0, resource: this.sampler }, { binding: 1, resource: hudTexture.texture.createView() },
540
+ ] });
541
+ const hudPass = encoder.beginRenderPass({ colorAttachments: [{
542
+ view: this.context.getCurrentTexture().createView(), loadOp: "load", storeOp: "store",
543
+ }] });
544
+ hudPass.setPipeline(this.hudPipeline);
545
+ hudPass.setBindGroup(0, hudGroup);
546
+ hudPass.draw(3);
547
+ hudPass.end();
666
548
  }
667
- [source, output] = [output, source];
668
- }
669
- const presentGroup = this.device.createBindGroup({ layout: this.presentLayout, entries: [
670
- { binding: 0, resource: this.sampler }, { binding: 1, resource: source.createView() },
671
- ] });
672
- const presentPass = encoder.beginRenderPass({ colorAttachments: [{
673
- view: this.context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store",
674
- clearValue: { r: 0, g: 0, b: 0, a: 0 },
675
- }] });
676
- presentPass.setPipeline(this.presentPipeline);
677
- presentPass.setBindGroup(0, presentGroup);
678
- presentPass.draw(3);
679
- presentPass.end();
680
- if (hudAfterEffects && hudTexture) {
681
- const hudGroup = this.device.createBindGroup({ layout: this.presentLayout, entries: [
682
- { binding: 0, resource: this.sampler }, { binding: 1, resource: hudTexture.texture.createView() },
683
- ] });
684
- const hudPass = encoder.beginRenderPass({ colorAttachments: [{
685
- view: this.context.getCurrentTexture().createView(), loadOp: "load", storeOp: "store",
686
- }] });
687
- hudPass.setPipeline(this.hudPipeline);
688
- hudPass.setBindGroup(0, hudGroup);
689
- hudPass.draw(3);
690
- hudPass.end();
691
549
  }
692
- }
550
+ });
693
551
  this.device.queue.submit([encoder.finish()]);
694
- const textureBytes = [...this.textures.values(), ...this.placeholders.values(), this.fallback,
552
+ const textureBytes = [...this.textures.values(), ...this.placeholders.values(), this.fallback, this.particleDot,
695
553
  ...(this.hudTexture ? [this.hudTexture] : [])].reduce((sum, entry) => sum + entry.width * entry.height * 4, 0);
696
554
  return {
697
555
  backend: this.backend,
@@ -707,21 +565,14 @@ export class WebGPUGameRenderer {
707
565
  instanceBufferBytes: this.instanceCapacity * INSTANCE_FLOATS * 4,
708
566
  };
709
567
  }
710
- updateHud(frame) {
711
- if (frame.hud.length === 0) {
568
+ updateHud(frame, images) {
569
+ if (frame.hud.length === 0 && !frame.ui) {
712
570
  return undefined;
713
571
  }
714
- const key = JSON.stringify([this.canvas.width, this.canvas.height, frame.hud]);
572
+ const key = JSON.stringify([this.canvas.width, this.canvas.height, frame.hud, frame.ui, [...images.keys()]]);
715
573
  if (key !== this.hudKey) {
716
574
  const canvas = this.hudCanvas ?? document.createElement("canvas");
717
- canvas.width = this.canvas.width;
718
- canvas.height = this.canvas.height;
719
- const context = canvas.getContext("2d");
720
- if (!context) {
721
- throw new Error("HUD canvas is unavailable");
722
- }
723
- context.clearRect(0, 0, canvas.width, canvas.height);
724
- paintHud(context, frame.hud, 1, frame.gameId);
575
+ paintWebGPUHud(canvas, this.canvas.width, this.canvas.height, frame, images);
725
576
  this.hudTexture?.texture.destroy();
726
577
  this.hudTexture = this.upload(canvas);
727
578
  this.hudCanvas = canvas;
@@ -729,18 +580,6 @@ export class WebGPUGameRenderer {
729
580
  }
730
581
  return this.hudTexture;
731
582
  }
732
- appendBatch(batches, texture, blend, index) {
733
- const last = batches[batches.length - 1];
734
- if (last?.texture === texture && last.blend === blend) {
735
- last.count++;
736
- }
737
- else {
738
- batches.push({ texture, blend, first: index, count: 1 });
739
- }
740
- }
741
- writeInstance(target, index, ...values) {
742
- target.set(values, index * INSTANCE_FLOATS);
743
- }
744
583
  ensureInstanceCapacity(count) {
745
584
  if (count <= this.instanceCapacity) {
746
585
  return;
@@ -790,6 +629,7 @@ export class WebGPUGameRenderer {
790
629
  this.lightTarget?.destroy();
791
630
  this.lightFallback.destroy();
792
631
  this.fallback.texture.destroy();
632
+ this.particleDot.texture.destroy();
793
633
  this.hudTexture?.texture.destroy();
794
634
  for (const entry of this.placeholders.values()) {
795
635
  entry.texture.destroy();