@driftengine/core 4.11.1 → 4.13.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 (231) hide show
  1. package/CHANGELOG.json +102 -0
  2. package/README.md +18 -9
  3. package/dist/geometry/meshBuilder.d.ts +10 -0
  4. package/dist/geometry/meshBuilder.js +11 -2
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.js +3 -2
  7. package/dist/math/color.d.ts +25 -0
  8. package/dist/math/color.js +37 -0
  9. package/dist/render/backend/webgl2/renderer.d.ts +33 -5
  10. package/dist/render/backend/webgl2/renderer.js +123 -49
  11. package/dist/render/backend/webgl2/skinScatter.d.ts +3 -1
  12. package/dist/render/backend/webgl2/skinScatter.js +5 -1
  13. package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
  14. package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
  15. package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
  16. package/dist/render/backend/webgpu/flockPass.d.ts +1 -1
  17. package/dist/render/backend/webgpu/giFieldPass.d.ts +3 -1
  18. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +10 -1
  19. package/dist/render/backend/webgpu/gpuDrivenPass.js +28 -11
  20. package/dist/render/backend/webgpu/panelPass.d.ts +1 -1
  21. package/dist/render/backend/webgpu/postPass.d.ts +2 -2
  22. package/dist/render/backend/webgpu/renderer.d.ts +21 -8
  23. package/dist/render/backend/webgpu/renderer.js +103 -38
  24. package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
  25. package/dist/render/backend/webgpu/skinScatterPass.d.ts +5 -1
  26. package/dist/render/backend/webgpu/skinScatterPass.js +9 -3
  27. package/dist/render/backend/webgpu/skyPass.d.ts +11 -1
  28. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  29. package/dist/render/backend/webgpu/windStreakPass.d.ts +1 -1
  30. package/dist/render/causticsRenderer.d.ts +3 -1
  31. package/dist/render/causticsRenderer.js +5 -1
  32. package/dist/render/decalPass.d.ts +3 -1
  33. package/dist/render/decalPass.js +4 -1
  34. package/dist/render/flockRenderer.d.ts +1 -1
  35. package/dist/render/flockRenderer.js +4 -1
  36. package/dist/render/globalMediumPass.d.ts +3 -1
  37. package/dist/render/globalMediumPass.js +5 -1
  38. package/dist/render/gradeColor.d.ts +21 -0
  39. package/dist/render/gradeColor.js +64 -0
  40. package/dist/render/lightMatrix.d.ts +13 -0
  41. package/dist/render/lightMatrix.js +17 -0
  42. package/dist/render/passGrade.d.ts +13 -0
  43. package/dist/render/passGrade.js +47 -0
  44. package/dist/render/plumeRenderer.d.ts +1 -1
  45. package/dist/render/plumeRenderer.js +4 -1
  46. package/dist/render/renderQuality.d.ts +57 -18
  47. package/dist/render/renderQuality.js +3 -3
  48. package/dist/render/sceneTarget.d.ts +2 -0
  49. package/dist/render/sceneTarget.js +1 -0
  50. package/dist/render/sdfTextRenderer.d.ts +1 -1
  51. package/dist/render/sdfTextRenderer.js +1 -1
  52. package/dist/render/shaders/arcane.d.ts +1 -1
  53. package/dist/render/shaders/arcane.js +5 -1
  54. package/dist/render/shaders/bolt.d.ts +1 -1
  55. package/dist/render/shaders/bolt.js +4 -1
  56. package/dist/render/shaders/caustics.d.ts +1 -1
  57. package/dist/render/shaders/caustics.js +4 -1
  58. package/dist/render/shaders/decalProject.js +12 -2
  59. package/dist/render/shaders/film.d.ts +1 -1
  60. package/dist/render/shaders/film.js +22 -9
  61. package/dist/render/shaders/fire.d.ts +1 -1
  62. package/dist/render/shaders/fire.js +4 -1
  63. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  64. package/dist/render/shaders/flat/areaLight.js +1 -1
  65. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  66. package/dist/render/shaders/flat/glassTint.js +1 -0
  67. package/dist/render/shaders/flat/index.d.ts +5 -0
  68. package/dist/render/shaders/flat/index.js +1 -1
  69. package/dist/render/shaders/flat/layerLooks.d.ts +16 -0
  70. package/dist/render/shaders/flat/layerLooks.js +108 -0
  71. package/dist/render/shaders/flat/layered.d.ts +6 -4
  72. package/dist/render/shaders/flat/layered.js +77 -13
  73. package/dist/render/shaders/flat/main.d.ts +1 -1
  74. package/dist/render/shaders/flat/main.js +7 -2
  75. package/dist/render/shaders/flat/models.js +1 -1
  76. package/dist/render/shaders/flat/preamble.js +6 -0
  77. package/dist/render/shaders/flock.d.ts +1 -1
  78. package/dist/render/shaders/flock.js +4 -1
  79. package/dist/render/shaders/generated/arcane.wgsl.d.ts +11 -1
  80. package/dist/render/shaders/generated/arcane.wgsl.js +11 -1
  81. package/dist/render/shaders/generated/bolt.wgsl.d.ts +12 -2
  82. package/dist/render/shaders/generated/bolt.wgsl.js +12 -2
  83. package/dist/render/shaders/generated/caustics.wgsl.d.ts +12 -2
  84. package/dist/render/shaders/generated/caustics.wgsl.js +12 -2
  85. package/dist/render/shaders/generated/decalProject.wgsl.d.ts +6 -1
  86. package/dist/render/shaders/generated/decalProject.wgsl.js +6 -1
  87. package/dist/render/shaders/generated/film.wgsl.d.ts +12 -2
  88. package/dist/render/shaders/generated/film.wgsl.js +12 -2
  89. package/dist/render/shaders/generated/fire.wgsl.d.ts +11 -1
  90. package/dist/render/shaders/generated/fire.wgsl.js +11 -1
  91. package/dist/render/shaders/generated/flat.wgsl.d.ts +144 -16
  92. package/dist/render/shaders/generated/flat.wgsl.js +183 -55
  93. package/dist/render/shaders/generated/flock.wgsl.d.ts +12 -2
  94. package/dist/render/shaders/generated/flock.wgsl.js +12 -2
  95. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +12 -2
  96. package/dist/render/shaders/generated/globalMedium.wgsl.js +12 -2
  97. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +36 -16
  98. package/dist/render/shaders/generated/lightVolume.wgsl.js +39 -19
  99. package/dist/render/shaders/generated/panel.wgsl.d.ts +12 -2
  100. package/dist/render/shaders/generated/panel.wgsl.js +12 -2
  101. package/dist/render/shaders/generated/rush.wgsl.d.ts +31 -26
  102. package/dist/render/shaders/generated/rush.wgsl.js +31 -26
  103. package/dist/render/shaders/generated/scatter.wgsl.d.ts +12 -2
  104. package/dist/render/shaders/generated/scatter.wgsl.js +12 -2
  105. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +11 -1
  106. package/dist/render/shaders/generated/skinBlur.wgsl.js +11 -1
  107. package/dist/render/shaders/generated/sky.wgsl.d.ts +12 -2
  108. package/dist/render/shaders/generated/sky.wgsl.js +12 -2
  109. package/dist/render/shaders/generated/smoke.wgsl.d.ts +11 -1
  110. package/dist/render/shaders/generated/smoke.wgsl.js +11 -1
  111. package/dist/render/shaders/generated/text.wgsl.d.ts +11 -1
  112. package/dist/render/shaders/generated/text.wgsl.js +11 -1
  113. package/dist/render/shaders/generated/water.wgsl.d.ts +12 -2
  114. package/dist/render/shaders/generated/water.wgsl.js +12 -2
  115. package/dist/render/shaders/generated/windStreaks.wgsl.d.ts +12 -2
  116. package/dist/render/shaders/generated/windStreaks.wgsl.js +12 -2
  117. package/dist/render/shaders/globalMedium.js +14 -1
  118. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +15 -6
  119. package/dist/render/shaders/gpudriven/present.wgsl.js +71 -8
  120. package/dist/render/shaders/lightVolume.js +4 -1
  121. package/dist/render/shaders/line.d.ts +1 -1
  122. package/dist/render/shaders/outputTransform.d.ts +8 -2
  123. package/dist/render/shaders/outputTransform.js +18 -11
  124. package/dist/render/shaders/panel.d.ts +1 -1
  125. package/dist/render/shaders/panel.js +5 -1
  126. package/dist/render/shaders/particle.d.ts +3 -3
  127. package/dist/render/shaders/rush.js +8 -0
  128. package/dist/render/shaders/scatter.d.ts +1 -1
  129. package/dist/render/shaders/scatter.js +4 -1
  130. package/dist/render/shaders/sdfText.d.ts +1 -1
  131. package/dist/render/shaders/skinBlur.js +5 -1
  132. package/dist/render/shaders/sky.d.ts +1 -1
  133. package/dist/render/shaders/sky.js +5 -1
  134. package/dist/render/shaders/smoke.d.ts +1 -1
  135. package/dist/render/shaders/smoke.js +4 -1
  136. package/dist/render/shaders/text.d.ts +1 -1
  137. package/dist/render/shaders/text.js +4 -1
  138. package/dist/render/shaders/water.d.ts +1 -1
  139. package/dist/render/shaders/water.js +12 -2
  140. package/dist/render/shaders/windStreaks.d.ts +1 -1
  141. package/dist/render/shaders/windStreaks.js +4 -1
  142. package/dist/render/surfaceLayerLooks.d.ts +92 -0
  143. package/dist/render/surfaceLayerLooks.js +102 -0
  144. package/dist/render/surfaceLayers.d.ts +47 -6
  145. package/dist/render/surfaceLayers.js +5 -1
  146. package/dist/render/textRenderer.d.ts +3 -1
  147. package/dist/render/textRenderer.js +7 -1
  148. package/dist/render/vertexDefaults.d.ts +1 -1
  149. package/dist/render/vertexDefaults.js +1 -1
  150. package/dist/render/waterRenderer.d.ts +1 -1
  151. package/dist/render/waterRenderer.js +4 -1
  152. package/dist/render/windStreakRenderer.d.ts +3 -1
  153. package/dist/render/windStreakRenderer.js +5 -1
  154. package/package.json +6 -6
  155. package/scripts/browser.d.mts +5 -0
  156. package/scripts/browser.mjs +68 -14
  157. package/scripts/look.d.mts +81 -0
  158. package/scripts/look.mjs +380 -0
  159. package/src/geometry/meshBuilder.ts +21 -2
  160. package/src/index.ts +13 -2
  161. package/src/math/color.ts +39 -0
  162. package/src/render/backend/webgl2/renderer.ts +174 -46
  163. package/src/render/backend/webgl2/skinScatter.ts +5 -0
  164. package/src/render/backend/webgpu/giFieldPass.ts +3 -1
  165. package/src/render/backend/webgpu/gpuDrivenPass.ts +29 -13
  166. package/src/render/backend/webgpu/renderer.ts +112 -37
  167. package/src/render/backend/webgpu/skinScatterPass.ts +17 -1
  168. package/src/render/causticsRenderer.ts +5 -0
  169. package/src/render/decalPass.ts +3 -0
  170. package/src/render/flockRenderer.ts +5 -0
  171. package/src/render/globalMediumPass.ts +10 -1
  172. package/src/render/gradeColor.ts +90 -0
  173. package/src/render/lightMatrix.ts +32 -0
  174. package/src/render/passGrade.ts +58 -0
  175. package/src/render/plumeRenderer.ts +5 -0
  176. package/src/render/renderQuality.ts +60 -21
  177. package/src/render/sceneTarget.ts +3 -0
  178. package/src/render/sdfTextRenderer.ts +1 -1
  179. package/src/render/shaders/arcane.ts +5 -1
  180. package/src/render/shaders/bolt.ts +4 -1
  181. package/src/render/shaders/caustics.ts +4 -1
  182. package/src/render/shaders/decalProject.ts +12 -2
  183. package/src/render/shaders/film.ts +22 -9
  184. package/src/render/shaders/fire.ts +4 -1
  185. package/src/render/shaders/flat/areaLight.ts +1 -1
  186. package/src/render/shaders/flat/glassTint.ts +3 -1
  187. package/src/render/shaders/flat/index.ts +6 -1
  188. package/src/render/shaders/flat/layerLooks.ts +108 -0
  189. package/src/render/shaders/flat/layered.ts +78 -13
  190. package/src/render/shaders/flat/main.ts +7 -2
  191. package/src/render/shaders/flat/models.ts +1 -1
  192. package/src/render/shaders/flat/preamble.ts +6 -0
  193. package/src/render/shaders/flock.ts +4 -1
  194. package/src/render/shaders/generated/arcane.wgsl.ts +11 -1
  195. package/src/render/shaders/generated/bolt.wgsl.ts +12 -2
  196. package/src/render/shaders/generated/caustics.wgsl.ts +12 -2
  197. package/src/render/shaders/generated/decalProject.wgsl.ts +6 -1
  198. package/src/render/shaders/generated/film.wgsl.ts +12 -2
  199. package/src/render/shaders/generated/fire.wgsl.ts +11 -1
  200. package/src/render/shaders/generated/flat.wgsl.ts +183 -55
  201. package/src/render/shaders/generated/flock.wgsl.ts +12 -2
  202. package/src/render/shaders/generated/globalMedium.wgsl.ts +12 -2
  203. package/src/render/shaders/generated/lightVolume.wgsl.ts +39 -19
  204. package/src/render/shaders/generated/panel.wgsl.ts +12 -2
  205. package/src/render/shaders/generated/rush.wgsl.ts +31 -26
  206. package/src/render/shaders/generated/scatter.wgsl.ts +12 -2
  207. package/src/render/shaders/generated/skinBlur.wgsl.ts +11 -1
  208. package/src/render/shaders/generated/sky.wgsl.ts +12 -2
  209. package/src/render/shaders/generated/smoke.wgsl.ts +11 -1
  210. package/src/render/shaders/generated/text.wgsl.ts +11 -1
  211. package/src/render/shaders/generated/water.wgsl.ts +12 -2
  212. package/src/render/shaders/generated/windStreaks.wgsl.ts +12 -2
  213. package/src/render/shaders/globalMedium.ts +14 -1
  214. package/src/render/shaders/gpudriven/present.wgsl.ts +72 -8
  215. package/src/render/shaders/lightVolume.ts +4 -1
  216. package/src/render/shaders/outputTransform.ts +19 -11
  217. package/src/render/shaders/panel.ts +5 -1
  218. package/src/render/shaders/rush.ts +8 -0
  219. package/src/render/shaders/scatter.ts +4 -1
  220. package/src/render/shaders/skinBlur.ts +5 -1
  221. package/src/render/shaders/sky.ts +5 -1
  222. package/src/render/shaders/smoke.ts +4 -1
  223. package/src/render/shaders/text.ts +4 -1
  224. package/src/render/shaders/water.ts +12 -2
  225. package/src/render/shaders/windStreaks.ts +4 -1
  226. package/src/render/surfaceLayerLooks.ts +186 -0
  227. package/src/render/surfaceLayers.ts +52 -7
  228. package/src/render/textRenderer.ts +6 -0
  229. package/src/render/vertexDefaults.ts +1 -1
  230. package/src/render/waterRenderer.ts +5 -0
  231. package/src/render/windStreakRenderer.ts +5 -0
@@ -25,7 +25,9 @@ export declare class TextRenderer {
25
25
  *
26
26
  * Answers whether it drew, which the renderer counts on its frame budget.
27
27
  */
28
- draw(viewportWidth: number, viewportHeight: number, originX: number, originY: number, style: TextStyle, timeSec: number): boolean;
28
+ draw(viewportWidth: number, viewportHeight: number, originX: number, originY: number, style: TextStyle, timeSec: number,
29
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
30
+ outputTransform?: number, outputExposure?: number): boolean;
29
31
  dispose(): void;
30
32
  /** Both instance buffers, from the arrays the layout filled in place. */
31
33
  private upload;
@@ -67,11 +67,17 @@ export class TextRenderer {
67
67
  *
68
68
  * Answers whether it drew, which the renderer counts on its frame budget.
69
69
  */
70
- draw(viewportWidth, viewportHeight, originX, originY, style, timeSec) {
70
+ draw(viewportWidth, viewportHeight, originX, originY, style, timeSec,
71
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
72
+ outputTransform = 0, outputExposure = 1) {
71
73
  if (this.layout.instanceCount === 0 || style.alpha <= 0)
72
74
  return false;
73
75
  const gl = this.gl;
74
76
  gl.useProgram(this.program);
77
+ const grade = this.uniforms['uOutputTransform'];
78
+ if (grade !== undefined)
79
+ gl.uniform1i(grade, outputTransform);
80
+ setFloat(gl, this.uniforms['uOutputExposure'], outputExposure);
75
81
  gl.bindVertexArray(this.vao);
76
82
  /*
77
83
  * Text gets a fresh depth buffer rather than no depth test at all.
@@ -41,7 +41,7 @@ export declare const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>>;
41
41
  /**
42
42
  * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
43
43
  * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
44
- * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
44
+ * `passGrade.ts` reads this for both backends, so neither can hand a forward stage a code it lacks.
45
45
  */
46
46
  export declare function forwardTransformCode(transform: string): number;
47
47
  /**
@@ -115,7 +115,7 @@ export const OUTPUT_TRANSFORM_CODE = {
115
115
  /**
116
116
  * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
117
117
  * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
118
- * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
118
+ * `passGrade.ts` reads this for both backends, so neither can hand a forward stage a code it lacks.
119
119
  */
120
120
  export function forwardTransformCode(transform) {
121
121
  return transform === 'filmic' ? 2 : (OUTPUT_TRANSFORM_CODE[transform] ?? 0);
@@ -158,6 +158,6 @@ export declare class WaterRenderer {
158
158
  * by geometry in front of it, but its far edge fades out, and that fade needs
159
159
  * the sky already behind it to blend into.
160
160
  */
161
- draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, settings: WaterBody, directionalDir: Vec3, directionalColor: Vec3, ambient: Vec3, atmosphere: Atmosphere, underwaterEnabled: boolean, reflection: PlanarReflection | null, lights?: PointLightSet | null, lightFalloff?: 'smooth' | 'inverseSquare', windX?: number, windZ?: number): void;
161
+ draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, settings: WaterBody, directionalDir: Vec3, directionalColor: Vec3, ambient: Vec3, atmosphere: Atmosphere, underwaterEnabled: boolean, reflection: PlanarReflection | null, lights?: PointLightSet | null, lightFalloff?: 'smooth' | 'inverseSquare', windX?: number, windZ?: number, outputTransform?: number, outputExposure?: number): void;
162
162
  dispose(gl: WebGL2RenderingContext): void;
163
163
  }
@@ -95,10 +95,13 @@ export class WaterRenderer {
95
95
  * by geometry in front of it, but its far edge fades out, and that fade needs
96
96
  * the sky already behind it to blend into.
97
97
  */
98
- draw(gl, camera, timeSeconds, settings, directionalDir, directionalColor, ambient, atmosphere, underwaterEnabled, reflection, lights = null, lightFalloff = 'smooth', windX = 0, windZ = 0) {
98
+ draw(gl, camera, timeSeconds, settings, directionalDir, directionalColor, ambient, atmosphere, underwaterEnabled, reflection, lights = null, lightFalloff = 'smooth', windX = 0, windZ = 0, outputTransform = 0, outputExposure = 1) {
99
99
  const u = this.uniforms;
100
100
  const reflectionReady = reflection?.isReadyFor(settings.level) ?? false;
101
101
  gl.useProgram(this.program);
102
+ /* The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
103
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
104
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
102
105
  gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, camera.viewProjection);
103
106
  gl.uniformMatrix4fv(u['uReflectionViewProj'] ?? null, false, reflection?.viewProjection ?? camera.viewProjection);
104
107
  gl.uniform3fv(u['uCameraPos'] ?? null, camera.position);
@@ -33,6 +33,8 @@ export declare class WindStreakRenderer {
33
33
  /** Answers whether it drew, which the renderer counts on its frame budget. */
34
34
  draw(gl: WebGL2RenderingContext, camera: FrameView, wind: WindField, timeSeconds: number, tint: Vec3,
35
35
  /** True when the camera is below the waterline. */
36
- submerged?: boolean): boolean;
36
+ submerged?: boolean,
37
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
38
+ outputTransform?: number, outputExposure?: number): boolean;
37
39
  dispose(gl: WebGL2RenderingContext): void;
38
40
  }
@@ -51,7 +51,9 @@ export class WindStreakRenderer {
51
51
  /** Answers whether it drew, which the renderer counts on its frame budget. */
52
52
  draw(gl, camera, wind, timeSeconds, tint,
53
53
  /** True when the camera is below the waterline. */
54
- submerged = false) {
54
+ submerged = false,
55
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
56
+ outputTransform = 0, outputExposure = 1) {
55
57
  /* Chosen in `windStreakDraw.ts` so both backends show the same weather. */
56
58
  const settled = resolveWindStreaks(wind.speed, wind.driftX, wind.driftZ, this.onsetSpeed, this.fullSpeed, submerged, this.resolved);
57
59
  if (!settled.visible)
@@ -68,6 +70,8 @@ export class WindStreakRenderer {
68
70
  gl.uniform1f(u['uCellSize'] ?? null, this.cellSize);
69
71
  gl.uniform1f(u['uTime'] ?? null, timeSeconds);
70
72
  gl.uniform3fv(u['uTint'] ?? null, tint);
73
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
74
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
71
75
  gl.enable(gl.BLEND);
72
76
  blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
73
77
  gl.depthMask(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/core",
3
- "version": "4.11.1",
3
+ "version": "4.13.0",
4
4
  "description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@driftengine/drft": "4.11.1",
33
- "@driftengine/physics": "4.11.1",
32
+ "@driftengine/drft": "4.13.0",
33
+ "@driftengine/physics": "4.13.0",
34
34
  "gl-matrix": "^3.4.3"
35
35
  },
36
36
  "author": "Drift Technologies",
@@ -67,8 +67,8 @@
67
67
  "prepack": "node ../../scripts/build.mjs @driftengine/core"
68
68
  },
69
69
  "devDependencies": {
70
- "@driftengine/nav": "4.11.1",
71
- "@driftengine/network": "4.11.1",
72
- "@driftengine/texture": "4.11.1"
70
+ "@driftengine/nav": "4.13.0",
71
+ "@driftengine/network": "4.13.0",
72
+ "@driftengine/texture": "4.13.0"
73
73
  }
74
74
  }
@@ -7,6 +7,11 @@ export interface LaunchedBrowser {
7
7
  close(): Promise<void>;
8
8
  }
9
9
 
10
+ /** Where a Chromium-based browser lives on a platform, in the order `chromePath` tries them. */
11
+ export function browserCandidates(
12
+ platform?: string,
13
+ env?: Readonly<Record<string, string | undefined>>,
14
+ ): string[];
10
15
  export function chromePath(): string;
11
16
  export function launch(options?: {
12
17
  flags?: readonly string[];
@@ -27,22 +27,64 @@
27
27
  * it, and it is what turns "leaks accumulate forever" into "at most one leak per still-running
28
28
  * process."
29
29
  */
30
- import { spawn } from 'node:child_process';
30
+ import { spawn, spawnSync } from 'node:child_process';
31
31
  import { once } from 'node:events';
32
32
  import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
33
33
  import { tmpdir } from 'node:os';
34
34
  import path from 'node:path';
35
35
  import { sleep } from './cdp.mjs';
36
36
 
37
- /** Where a Chrome or Chromium lives, in the order worth trying. `CHROME_PATH` wins. */
38
- const CANDIDATES = [
39
- '/opt/google/chrome/chrome',
40
- '/usr/bin/google-chrome',
41
- '/usr/bin/google-chrome-stable',
42
- '/usr/bin/chromium',
43
- '/usr/bin/chromium-browser',
44
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
45
- ];
37
+ /**
38
+ * Where a Chromium-based browser lives on each platform, in the order worth trying.
39
+ *
40
+ * **Edge is last on every list and the only thing on Windows that is always there.** It is
41
+ * Chromium, it speaks the same DevTools protocol, and it ships with Windows 10 and 11, so a person
42
+ * who never installed Chrome still has a browser this module can drive. Chrome comes first where it
43
+ * exists because it is what the engine's captures are measured on.
44
+ *
45
+ * Windows paths are built from the environment rather than written out, because `Program Files`
46
+ * is wherever that machine put it and a per-user install sits under `LOCALAPPDATA`; a Mac's
47
+ * per-user install sits under `~/Applications`. Nothing here has been run on Windows or macOS by
48
+ * this repository yet; the lists are the browsers' documented install locations.
49
+ */
50
+ export function browserCandidates(platform = process.platform, env = process.env) {
51
+ if (platform === 'win32') {
52
+ const roots = [env['PROGRAMFILES'], env['PROGRAMFILES(X86)'], env['LOCALAPPDATA']].filter(
53
+ (root) => typeof root === 'string' && root !== '',
54
+ );
55
+ return [
56
+ ...roots.map((root) =>
57
+ path.win32.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe'),
58
+ ),
59
+ ...roots.map((root) =>
60
+ path.win32.join(root, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
61
+ ),
62
+ ];
63
+ }
64
+ if (platform === 'darwin') {
65
+ /* `/Applications` for an install by an administrator, `~/Applications` for one without. */
66
+ const roots = [
67
+ '/Applications',
68
+ typeof env['HOME'] === 'string' && env['HOME'] !== ''
69
+ ? path.posix.join(env['HOME'], 'Applications')
70
+ : null,
71
+ ].filter((root) => root !== null);
72
+ const apps = [
73
+ 'Google Chrome.app/Contents/MacOS/Google Chrome',
74
+ 'Chromium.app/Contents/MacOS/Chromium',
75
+ 'Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
76
+ ];
77
+ return apps.flatMap((app) => roots.map((root) => path.posix.join(root, app)));
78
+ }
79
+ return [
80
+ '/opt/google/chrome/chrome',
81
+ '/usr/bin/google-chrome',
82
+ '/usr/bin/google-chrome-stable',
83
+ '/usr/bin/chromium',
84
+ '/usr/bin/chromium-browser',
85
+ '/usr/bin/microsoft-edge',
86
+ ];
87
+ }
46
88
 
47
89
  /**
48
90
  * What it takes to reach the real card from a headless process.
@@ -80,7 +122,7 @@ const PROFILE_PREFIX = 'driftengine-shots-';
80
122
  const OWNER_FILE = 'driftengine-owner.json';
81
123
 
82
124
  /**
83
- * Signal an entire process group, escalating if it does not go.
125
+ * Stop a browser and every process it started, escalating if it does not go.
84
126
  *
85
127
  * **Chrome is a tree, not a process.** It forks a zygote, a GPU process and one renderer per
86
128
  * page, and signalling the parent alone leaves the rest running with the port closed and the
@@ -89,9 +131,20 @@ const OWNER_FILE = 'driftengine-owner.json';
89
131
  *
90
132
  * SIGTERM first because a browser asked politely flushes and exits; SIGKILL after a grace period
91
133
  * because one that is wedged never will, and a wedged browser is exactly the case this exists for.
134
+ *
135
+ * **Windows has no process groups to signal**, so there `taskkill /T` walks the tree from the
136
+ * browser's own PID and `/F` ends it at once. Synchronous, because this also runs from an `exit`
137
+ * handler, where nothing asynchronous gets to finish.
92
138
  */
93
139
  function killTree(pid, graceMs = 2000) {
94
140
  if (!Number.isInteger(pid) || pid <= 0) return;
141
+ if (process.platform === 'win32') {
142
+ spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], {
143
+ stdio: 'ignore',
144
+ windowsHide: true,
145
+ });
146
+ return;
147
+ }
95
148
  try {
96
149
  process.kill(-pid, 'SIGTERM');
97
150
  } catch {
@@ -194,10 +247,11 @@ function defaultIsAlive(pid) {
194
247
  export function chromePath() {
195
248
  const asked = process.env['CHROME_PATH'];
196
249
  if (asked !== undefined && asked !== '') return asked;
197
- const found = CANDIDATES.find((candidate) => existsSync(candidate));
250
+ const candidates = browserCandidates();
251
+ const found = candidates.find((candidate) => existsSync(candidate));
198
252
  if (found === undefined) {
199
253
  throw new Error(
200
- `no Chrome or Chromium found. Tried:\n ${CANDIDATES.join('\n ')}\nSet CHROME_PATH to one.`,
254
+ `no Chrome, Chromium or Edge found. Tried:\n ${candidates.join('\n ')}\nSet CHROME_PATH to one.`,
201
255
  );
202
256
  }
203
257
  return found;
@@ -227,7 +281,7 @@ export async function launch({ flags = GPU_FLAGS, headless = true, timeoutMs = 2
227
281
  process and every renderer with one signal to the negative PID. `unref()` is deliberately
228
282
  not called: the parent must stay attached, or its own exit and signal handlers below never
229
283
  fire, and those are half of what closes the other three holes. */
230
- const child = spawn(binary, args, { stdio: 'ignore', detached: true });
284
+ const child = spawn(binary, args, { stdio: 'ignore', detached: true, windowsHide: true });
231
285
  writeFileSync(
232
286
  path.join(profile, OWNER_FILE),
233
287
  JSON.stringify({ ownerPid: process.pid, chromePid: child.pid }),
@@ -0,0 +1,81 @@
1
+ import type { RgbaImage } from './png.d.mts';
2
+
3
+ /** The backends `look` photographs by default, in the order a consumer runs them. */
4
+ export const LOOK_BACKENDS: readonly ['webgpu', 'webgl2'];
5
+
6
+ /** At or above this share of one colour, a canvas is called empty. */
7
+ export const EMPTY_SHARE: number;
8
+
9
+ /** What a frame is made of. */
10
+ export interface FrameStats {
11
+ readonly width: number;
12
+ readonly height: number;
13
+ /** The most common colour, as `#rrggbb`. */
14
+ readonly dominant: string;
15
+ /** The share of the frame that colour covers, 0 to 1. */
16
+ readonly dominantShare: number;
17
+ /** Rec. 709 luminance averaged over the frame, 0 to 255. */
18
+ readonly meanLuminance: number;
19
+ }
20
+
21
+ /** One backend's photograph and everything the page said while it was taken. */
22
+ export interface LookCapture {
23
+ readonly backend: string;
24
+ readonly url: string;
25
+ /** What a player sees, interface included. */
26
+ readonly file: string;
27
+ /**
28
+ * The backend that actually drew, read from the context the page's canvas asked for: `'webgpu'`,
29
+ * `'webgl2'`, or `null` when no canvas in the page asked for either.
30
+ */
31
+ readonly drawnWith: 'webgpu' | 'webgl2' | null;
32
+ /** Measured over the canvas alone; `null` when the page never booted. */
33
+ readonly stats: FrameStats | null;
34
+ /** The share of the canvas that changed between two frames; `null` when the page never booted. */
35
+ readonly moved: number | null;
36
+ readonly errors: readonly string[];
37
+ readonly warnings: readonly string[];
38
+ /** Every console line, `level: text`. */
39
+ readonly logs: readonly string[];
40
+ readonly problems: readonly string[];
41
+ }
42
+
43
+ export interface LookReport {
44
+ readonly url: string;
45
+ /** The renderer string of the GPU that drew, read from a WebGL2 context. */
46
+ readonly gpu: string;
47
+ readonly captures: readonly LookCapture[];
48
+ readonly problems: readonly string[];
49
+ /** True when no capture found a problem. */
50
+ readonly ok: boolean;
51
+ }
52
+
53
+ export interface LookOptions {
54
+ /** The address of the running game. */
55
+ readonly url: string;
56
+ readonly backends?: readonly string[];
57
+ /** Where the PNGs and `look.json` go. Defaults to `.driftengine/look`. */
58
+ readonly out?: string;
59
+ readonly width?: number;
60
+ readonly height?: number;
61
+ /** How long to wait for a canvas in the page to ask for a WebGPU or WebGL2 context. */
62
+ readonly bootMs?: number;
63
+ /** A pause after that for the first frames. */
64
+ readonly settleMs?: number;
65
+ /** How long to wait after that for the canvas to show more than one colour. */
66
+ readonly drawMs?: number;
67
+ /** The gap between the two frames compared to tell whether the game moves. */
68
+ readonly moveMs?: number;
69
+ /** Browser flags; defaults to `gpuFlagsFor()`. */
70
+ readonly flags?: readonly string[] | null;
71
+ /** `false` opens a real window, for a machine whose headless browser cannot reach its GPU. */
72
+ readonly headless?: boolean;
73
+ }
74
+
75
+ export function gpuFlagsFor(platform?: string): string[] | null;
76
+ export function lookUrl(url: string, backend: string): string;
77
+ export function frameStats(image: RgbaImage): FrameStats;
78
+ export function changedShare(a: RgbaImage, b: RgbaImage, tolerance?: number): number;
79
+ export function sortConsole(lines: readonly string[]): { errors: string[]; warnings: string[] };
80
+ export function look(options: LookOptions): Promise<LookReport>;
81
+ export function formatLook(report: LookReport): string;