@driftengine/core 4.8.1 → 4.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 (62) hide show
  1. package/CHANGELOG.json +86 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +2 -0
  4. package/dist/input/actionMap.d.ts +6 -1
  5. package/dist/input/actionMap.js +20 -7
  6. package/dist/input/input.d.ts +28 -0
  7. package/dist/input/input.js +71 -9
  8. package/dist/input/inputHarness.d.ts +9 -0
  9. package/dist/input/inputHarness.js +34 -2
  10. package/dist/render/backend/webgl2/renderer.d.ts +52 -3
  11. package/dist/render/backend/webgl2/renderer.js +151 -5
  12. package/dist/render/backend/webgpu/buffers.d.ts +2 -0
  13. package/dist/render/backend/webgpu/buffers.js +2 -0
  14. package/dist/render/backend/webgpu/flatPass.js +16 -7
  15. package/dist/render/backend/webgpu/pipelineCache.d.ts +40 -8
  16. package/dist/render/backend/webgpu/pipelineCache.js +76 -9
  17. package/dist/render/backend/webgpu/renderer.d.ts +56 -1
  18. package/dist/render/backend/webgpu/renderer.js +151 -26
  19. package/dist/render/mapsWithoutUvs.d.ts +24 -0
  20. package/dist/render/mapsWithoutUvs.js +24 -0
  21. package/dist/render/mesh.d.ts +2 -0
  22. package/dist/render/mesh.js +3 -0
  23. package/dist/render/particlePool.d.ts +15 -0
  24. package/dist/render/particlePool.js +72 -37
  25. package/dist/render/pass.d.ts +12 -0
  26. package/dist/render/renderQuality.d.ts +7 -6
  27. package/dist/render/shaders/flat/glassTint.d.ts +28 -0
  28. package/dist/render/shaders/flat/glassTint.js +31 -0
  29. package/dist/render/shaders/flat/index.d.ts +12 -0
  30. package/dist/render/shaders/flat/index.js +7 -1
  31. package/dist/render/shaders/flat/main.d.ts +1 -1
  32. package/dist/render/shaders/flat/main.js +60 -25
  33. package/dist/render/shaders/flat/surfaceEffects.d.ts +1 -1
  34. package/dist/render/shaders/flat/surfaceEffects.js +6 -2
  35. package/dist/render/shaders/generated/flat.wgsl.d.ts +64 -0
  36. package/dist/render/shaders/generated/flat.wgsl.js +102 -38
  37. package/dist/render/surfaceTexture.d.ts +2 -0
  38. package/dist/render/surfaceTexture.js +4 -0
  39. package/dist/render/viewModel.d.ts +29 -0
  40. package/dist/render/viewModel.js +35 -0
  41. package/package.json +6 -6
  42. package/src/index.ts +9 -1
  43. package/src/input/actionMap.ts +23 -8
  44. package/src/input/input.ts +79 -9
  45. package/src/input/inputHarness.ts +27 -2
  46. package/src/render/backend/webgl2/renderer.ts +166 -13
  47. package/src/render/backend/webgpu/buffers.ts +5 -0
  48. package/src/render/backend/webgpu/flatPass.ts +41 -25
  49. package/src/render/backend/webgpu/pipelineCache.ts +82 -8
  50. package/src/render/backend/webgpu/renderer.ts +151 -26
  51. package/src/render/mapsWithoutUvs.ts +43 -0
  52. package/src/render/mesh.ts +3 -0
  53. package/src/render/particlePool.ts +101 -54
  54. package/src/render/pass.ts +12 -0
  55. package/src/render/renderQuality.ts +7 -6
  56. package/src/render/shaders/flat/glassTint.ts +48 -0
  57. package/src/render/shaders/flat/index.ts +19 -1
  58. package/src/render/shaders/flat/main.ts +60 -25
  59. package/src/render/shaders/flat/surfaceEffects.ts +6 -2
  60. package/src/render/shaders/generated/flat.wgsl.ts +102 -38
  61. package/src/render/surfaceTexture.ts +5 -0
  62. package/src/render/viewModel.ts +41 -0
@@ -23,3 +23,51 @@ vec4 glassUnmix(vec4 mip, float centreClarity) {
23
23
  }
24
24
  `;
25
25
  }
26
+
27
+ /**
28
+ * The lit stage's switches, by the name of the constant each is: the decision both backends make
29
+ * about which optional code a lit program carries. See `litSwitchesGlsl`.
30
+ */
31
+ export type LitSwitch =
32
+ 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT';
33
+
34
+ /** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
35
+ export function noLitSwitches(): Record<LitSwitch, boolean> {
36
+ return {
37
+ GLASS_SHADOWS: false,
38
+ CLUSTERED_LIGHTS: false,
39
+ LIGHT_FIXTURES: false,
40
+ SURFACE_EFFECTS: false,
41
+ DRIFT_LIGHT: false,
42
+ };
43
+ }
44
+
45
+ /** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
46
+ export interface LitSwitches {
47
+ readonly clusteredLights: boolean;
48
+ readonly lightFixtures: boolean;
49
+ readonly surfaceEffects: boolean;
50
+ readonly driftLight: boolean;
51
+ }
52
+
53
+ /**
54
+ * The lit stage's other switches, declared after `GLASS_SHADOWS` so that one keeps the id 0 the
55
+ * pipelines and their tests already name, each a pipeline-overridable constant on the generated
56
+ * WGSL and a written-in value on WebGL2 — the arrangement `GLASS_SHADOWS` has, for its reason:
57
+ * code a branch never runs still costs a shader its registers, and the device compiles it away
58
+ * only where the switch is a constant.
59
+ *
60
+ * **Clustering is the profile's and fixed.** The other three are content, off until a consumer
61
+ * first uses them — a measured profile or a cookie, a material with an effects table, a DriftLight
62
+ * volume — and then on for good; see `PipelineCache.enable` and the WebGL2 renderer's rebuild.
63
+ * Measured on RADV against the lit shader with glass already out: 6,852 instructions and 120
64
+ * registers, 4,641 and 96 with all four out.
65
+ */
66
+ export function litSwitchesGlsl(switches: LitSwitches): string {
67
+ return /* glsl */ `
68
+ const bool CLUSTERED_LIGHTS = ${switches.clusteredLights ? 'true' : 'false'}; // wgsl:override
69
+ const bool LIGHT_FIXTURES = ${switches.lightFixtures ? 'true' : 'false'}; // wgsl:override
70
+ const bool SURFACE_EFFECTS = ${switches.surfaceEffects ? 'true' : 'false'}; // wgsl:override
71
+ const bool DRIFT_LIGHT = ${switches.driftLight ? 'true' : 'false'}; // wgsl:override
72
+ `;
73
+ }
@@ -7,7 +7,7 @@ import { preambleGlsl } from './preamble.ts';
7
7
  import { LOBES_GLSL } from './lobes.ts';
8
8
  import { POINTSHADOW_GLSL } from './pointShadow.ts';
9
9
  import { DIRECTIONALSHADOW_GLSL } from './directionalShadow.ts';
10
- import { glassTintGlsl } from './glassTint.ts';
10
+ import { glassTintGlsl, litSwitchesGlsl } from './glassTint.ts';
11
11
  import { MORPH_GLSL } from '../morph.ts';
12
12
  import { SKINNING_GLSL } from '../skinning.ts';
13
13
  import { SURFACE_GLSL } from './surface.ts';
@@ -379,6 +379,18 @@ export interface FlatShaderOptions {
379
379
  * courtyard at night, measured, and nothing once compiled out.
380
380
  */
381
381
  readonly glassShadows?: boolean;
382
+ /**
383
+ * Whether this build has the clustered arm of the light loop: `clusteredLights`, fixed per
384
+ * renderer. True when absent, which is what the generated WGSL is built with. See
385
+ * `litSwitchesGlsl`.
386
+ */
387
+ readonly clusteredLights?: boolean;
388
+ /** Whether it reads measured profiles and cookies. True when absent. See `litSwitchesGlsl`. */
389
+ readonly lightFixtures?: boolean;
390
+ /** Whether it reads a material's effects table. True when absent. See `litSwitchesGlsl`. */
391
+ readonly surfaceEffects?: boolean;
392
+ /** Whether it reads a DriftLight volume. True when absent. See `litSwitchesGlsl`. */
393
+ readonly driftLight?: boolean;
382
394
  /**
383
395
  * How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
384
396
  *
@@ -484,6 +496,12 @@ export function flatFrag(options: FlatShaderOptions): string {
484
496
  /* Depth zero as well: both glass lookups, sun and lamps, call it, and every permutation
485
497
  declares its switch, which the pipelines set whether the permutation reads it or not. */
486
498
  glassTintGlsl(options.glassShadows ?? true),
499
+ litSwitchesGlsl({
500
+ clusteredLights: options.clusteredLights ?? true,
501
+ lightFixtures: options.lightFixtures ?? true,
502
+ surfaceEffects: options.surfaceEffects ?? true,
503
+ driftLight: options.driftLight ?? true,
504
+ }),
487
505
  LOBES_GLSL,
488
506
  POINTSHADOW_GLSL,
489
507
  DIRECTIONALSHADOW_GLSL,
@@ -14,8 +14,13 @@ export const MAIN_GLSL = `void main() {
14
14
  * the material moves together. The gradients are taken here, outside every branch, for the room
15
15
  * a window samples through textureGrad further down.
16
16
  */
17
- fxLoad(int(surfaceAt.z));
18
- surfaceAt = fxAnimate(surfaceAt);
17
+ /* Zero rows unless a material has carried a table: \`SURFACE_EFFECTS\` is off until one does, and
18
+ every effect below reads exactly nothing from zero rows, which the device folds away. */
19
+ fxClear();
20
+ if (SURFACE_EFFECTS) {
21
+ fxLoad(int(surfaceAt.z));
22
+ surfaceAt = fxAnimate(surfaceAt);
23
+ }
19
24
  vec2 fxDx = dFdx(surfaceAt.xy);
20
25
  vec2 fxDy = dFdy(surfaceAt.xy);
21
26
  if (uWriteMode.y != 0.0 && !ditherKeeps(uWriteMode.y)) discard;
@@ -908,7 +913,12 @@ export const MAIN_GLSL = `void main() {
908
913
  */
909
914
  vec3 lampOpen = vec3(0.0);
910
915
  vec3 lampShadowed = vec3(0.0);
911
- float driftShare = driftLightShare(vWorldPos);
916
+ /* \`DRIFT_LIGHT\` is off until a consumer gives the renderer a DriftLight volume, and the device
917
+ compiles the volume's reads away with it. */
918
+ float driftShare = 0.0;
919
+ if (DRIFT_LIGHT) {
920
+ driftShare = driftLightShare(vWorldPos);
921
+ }
912
922
 
913
923
  /*
914
924
  * Which froxel this fragment is in.
@@ -921,28 +931,53 @@ export const MAIN_GLSL = `void main() {
921
931
  * same order and with the same clamps, because a fragment reading a froxel the binner filled
922
932
  * differently is a fragment lit by somebody else's lights.
923
933
  */
924
- vec3 clusterView = (uView * vec4(vWorldPos, 1.0)).xyz;
925
- float clusterDepth = -clusterView.z;
926
- float clusterHalfH = max(clusterDepth, 1e-4) * uClusterFrustum.z;
927
- float clusterHalfW = clusterHalfH * uClusterFrustum.w;
928
- int clusterTileX = clamp(
929
- int(floor((clusterView.x / clusterHalfW + 1.0) * 0.5 * float(CLUSTER_X))), 0, CLUSTER_X - 1);
930
- int clusterTileY = clamp(
931
- int(floor((clusterView.y / clusterHalfH + 1.0) * 0.5 * float(CLUSTER_Y))), 0, CLUSTER_Y - 1);
932
- int clusterSlice = 0;
933
- if (clusterDepth > uClusterFrustum.x) {
934
- clusterSlice = clamp(
935
- int(floor(log(clusterDepth / uClusterFrustum.x)
936
- / log(uClusterFrustum.y / uClusterFrustum.x) * float(CLUSTER_Z))),
937
- 0, CLUSTER_Z - 1);
934
+ /*
935
+ * **Only in a build that clusters, and there only in a frame that does.** \`CLUSTERED_LIGHTS\` is
936
+ * the profile's \`clusteredLights\`, a pipeline constant the device compiles this whole arm away
937
+ * on: the view transform, two logarithms and the froxel fetch below ran on every lit pixel of
938
+ * every scene, clustered or not, and the arm's records held their registers through the loop.
939
+ */
940
+ int clusterBaseTexel = 0;
941
+ int clusterLights = 0;
942
+ bool clusteredArm = false;
943
+ if (CLUSTERED_LIGHTS) {
944
+ if (uClustered != 0) {
945
+ vec3 clusterView = (uView * vec4(vWorldPos, 1.0)).xyz;
946
+ float clusterDepth = -clusterView.z;
947
+ float clusterHalfH = max(clusterDepth, 1e-4) * uClusterFrustum.z;
948
+ float clusterHalfW = clusterHalfH * uClusterFrustum.w;
949
+ int clusterTileX = clamp(
950
+ int(floor((clusterView.x / clusterHalfW + 1.0) * 0.5 * float(CLUSTER_X))), 0, CLUSTER_X - 1);
951
+ int clusterTileY = clamp(
952
+ int(floor((clusterView.y / clusterHalfH + 1.0) * 0.5 * float(CLUSTER_Y))), 0, CLUSTER_Y - 1);
953
+ int clusterSlice = 0;
954
+ if (clusterDepth > uClusterFrustum.x) {
955
+ clusterSlice = clamp(
956
+ int(floor(log(clusterDepth / uClusterFrustum.x)
957
+ / log(uClusterFrustum.y / uClusterFrustum.x) * float(CLUSTER_Z))),
958
+ 0, CLUSTER_Z - 1);
959
+ }
960
+ clusterBaseTexel = LIGHT_REGION_TEXELS
961
+ + (clusterTileX + clusterTileY * CLUSTER_X + clusterSlice * CLUSTER_X * CLUSTER_Y)
962
+ * CLUSTER_TEXELS;
963
+ clusterLights = int(clusterTexel(clusterBaseTexel).x);
964
+ clusteredArm = true;
965
+ }
938
966
  }
939
- int clusterBaseTexel = LIGHT_REGION_TEXELS
940
- + (clusterTileX + clusterTileY * CLUSTER_X + clusterSlice * CLUSTER_X * CLUSTER_Y)
941
- * CLUSTER_TEXELS;
942
- int clusterLights = uClustered != 0 ? int(clusterTexel(clusterBaseTexel).x) : 0;
943
967
 
944
968
  /* The fixture atlas's size, asked once: its cookie band and its profile rows are found from it. */
945
969
  vec2 fixtureSize = vec2(textureSize(uFixtureAtlas, 0));
970
+ /*
971
+ * Whether any light can carry a measured profile or a cookie, decided once rather than per
972
+ * light. \`LIGHT_FIXTURES\` is off until a consumer loads one, and the device compiles the
973
+ * fixture lookups away with it, which they hold their registers for in every scene otherwise.
974
+ */
975
+ bool fixtureProfiles = false;
976
+ bool fixtureShapes = false;
977
+ if (LIGHT_FIXTURES) {
978
+ fixtureProfiles = uIesAtlasRows > 1.0;
979
+ fixtureShapes = uIesPlaneCount > 1.0 || uCookieTiles > 0.0;
980
+ }
946
981
 
947
982
  // Point lights. Inverse-square-ish falloff clipped to a finite radius, so a
948
983
  // light can be culled without a visible seam.
@@ -968,7 +1003,7 @@ export const MAIN_GLSL = `void main() {
968
1003
  float iesProfile;
969
1004
  vec3 iesAxis;
970
1005
  float cookie;
971
- if (uClustered != 0) {
1006
+ if (clusteredArm) {
972
1007
  if (slot >= clusterLights) break;
973
1008
  /*
974
1009
  * Four indices to a texel. Selected with compares rather than by indexing the vector,
@@ -1273,7 +1308,7 @@ export const MAIN_GLSL = `void main() {
1273
1308
  vec3 iesRay = -toLight / max(dist, 1e-4);
1274
1309
  /* The angle from the fixture's axis, as a fraction of the 0-to-180 arc every row spans. */
1275
1310
  float iesU = 0.0;
1276
- if (uIesAtlasRows > 1.0) {
1311
+ if (fixtureProfiles) {
1277
1312
  float iesAngle = acos(clamp(dot(iesRay, lightDir), -1.0, 1.0));
1278
1313
  /* Clamped to the row's first and last texel centres, which is what the edge clamp of a
1279
1314
  texture exactly one row wide gave; the atlas can be wider now, with cookies beside it. */
@@ -1297,7 +1332,7 @@ export const MAIN_GLSL = `void main() {
1297
1332
  * \`uLightIesAxis\` to answer it. Behind a branch on two uniforms, so a scene using neither
1298
1333
  * builds no frame at all.
1299
1334
  */
1300
- if (uIesPlaneCount > 1.0 || uCookieTiles > 0.0) {
1335
+ if (fixtureShapes) {
1301
1336
  vec3 iesRef = iesAxis - lightDir * dot(iesAxis, lightDir);
1302
1337
  float iesRefLen = length(iesRef);
1303
1338
  if (iesRefLen > 1e-5) {
@@ -1354,7 +1389,7 @@ export const MAIN_GLSL = `void main() {
1354
1389
  * bytes on \`core-only\`** across the sixteen fragment permutations.
1355
1390
  */
1356
1391
  float photometric = 1.0;
1357
- if (uIesAtlasRows > 1.0) {
1392
+ if (fixtureProfiles) {
1358
1393
  float iesBase = iesRow * (uIesPlaneCount + 1.0);
1359
1394
  /* The profiles are the atlas's bottom rows, below any cookies. */
1360
1395
  float iesTop = fixtureSize.y - uIesAtlasRows;
@@ -41,14 +41,18 @@ vec4 fx5;
41
41
  vec3 fxNormal;
42
42
  float fxDistance;
43
43
 
44
- /** Fill the rows for this layer, or leave them zero where the array carries no table. */
45
- void fxLoad(int layer) {
44
+ /** Every row zero: the layer carries no effect. */
45
+ void fxClear() {
46
46
  fx0 = vec4(0.0);
47
47
  fx1 = vec4(0.0);
48
48
  fx2 = vec4(0.0);
49
49
  fx3 = vec4(0.0);
50
50
  fx4 = vec4(0.0);
51
51
  fx5 = vec4(0.0);
52
+ }
53
+
54
+ /** Fill the rows for this layer, or leave them zero where the array carries no table. */
55
+ void fxLoad(int layer) {
52
56
  ivec2 size = textureSize(uSurfaceEffects, 0);
53
57
  if (size.x < 6 || layer < 0 || layer >= size.y) return;
54
58
  fx0 = texelFetch(uSurfaceEffects, ivec2(0, layer), 0);