@woosh/meep-engine 3.61.0 → 3.61.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 (170) hide show
  1. package/package.json +1 -1
  2. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  3. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
  4. package/src/engine/graphics/ecs/decal/v2/Decal.d.ts +46 -2
  5. package/src/engine/graphics/ecs/decal/v2/Decal.d.ts.map +1 -1
  6. package/src/engine/graphics/ecs/decal/v2/Decal.js +74 -5
  7. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.d.ts.map +1 -1
  8. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +13 -1
  9. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.d.ts +9 -0
  10. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.d.ts.map +1 -0
  11. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.js +37 -0
  12. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
  13. package/src/engine/graphics3/DecalSystem.js +462 -447
  14. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.d.ts +14 -0
  15. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.d.ts.map +1 -1
  16. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +53 -13
  17. package/src/engine/graphics3/decal/chunk_decal_record.d.ts +6 -1
  18. package/src/engine/graphics3/decal/chunk_decal_record.d.ts.map +1 -1
  19. package/src/engine/graphics3/decal/chunk_decal_record.js +28 -9
  20. package/src/engine/graphics3/decal/decal_gpu_records.d.ts +2 -1
  21. package/src/engine/graphics3/decal/decal_gpu_records.d.ts.map +1 -1
  22. package/src/engine/graphics3/decal/decal_gpu_records.js +26 -5
  23. package/src/engine/graphics3/decal/decal_material_samplers.d.ts +6 -0
  24. package/src/engine/graphics3/decal/decal_material_samplers.d.ts.map +1 -1
  25. package/src/engine/graphics3/decal/decal_material_samplers.js +13 -0
  26. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +1 -1
  27. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +1 -1
  28. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +5 -0
  29. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  30. package/src/engine/graphics3/decal/shader_decal_composite.js +69 -28
  31. package/src/engine/network/sim/SimActionExecutor.d.ts +4 -0
  32. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  33. package/src/engine/network/sim/SimActionExecutor.js +4 -0
  34. package/src/generation/foliage/material/foliage_atlas_cutout.d.ts.map +1 -1
  35. package/src/generation/foliage/material/foliage_atlas_cutout.js +21 -5
  36. package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts +8 -0
  37. package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts.map +1 -1
  38. package/src/generation/foliage/mesh/tree_cluster_representatives.js +178 -78
  39. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
  40. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +178 -97
  41. package/src/generation/foliage/skeleton/ShootLightField.d.ts +1 -0
  42. package/src/generation/foliage/skeleton/ShootLightField.d.ts.map +1 -1
  43. package/src/generation/foliage/skeleton/ShootLightField.js +88 -17
  44. package/src/generation/foliage/skeleton/ShootWoodHash.d.ts +8 -4
  45. package/src/generation/foliage/skeleton/ShootWoodHash.d.ts.map +1 -1
  46. package/src/generation/foliage/skeleton/ShootWoodHash.js +216 -27
  47. package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
  48. package/src/shade/playground/probe_clipmap/main.js +4 -0
  49. package/src/shade/playground/probe_clipmap_box/README.md +55 -0
  50. package/src/shade/playground/probe_clipmap_box/index.html +185 -0
  51. package/src/shade/playground/probe_clipmap_box/main.d.ts +8 -0
  52. package/src/shade/playground/probe_clipmap_box/main.d.ts.map +1 -0
  53. package/src/shade/playground/probe_clipmap_box/main.js +323 -0
  54. package/src/shade/playground/probe_clipmap_box/scene.d.ts +93 -0
  55. package/src/shade/playground/probe_clipmap_box/scene.d.ts.map +1 -0
  56. package/src/shade/playground/probe_clipmap_box/scene.js +256 -0
  57. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  58. package/src/shade/renderer/Renderer.js +6 -1
  59. package/src/shade/renderer/buffer/graph_read_buffer.d.ts +3 -1
  60. package/src/shade/renderer/buffer/graph_read_buffer.d.ts.map +1 -1
  61. package/src/shade/renderer/buffer/graph_read_buffer.js +9 -6
  62. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.d.ts +12 -0
  63. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.d.ts.map +1 -1
  64. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.js +49 -24
  65. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.d.ts +12 -0
  66. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.d.ts.map +1 -1
  67. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.js +50 -19
  68. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_sh3_compress.d.ts.map +1 -1
  69. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_sh3_compress.js +6 -2
  70. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts +5 -1
  71. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  72. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +7 -3
  73. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +6 -4
  74. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  75. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +10 -9
  76. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +12 -0
  77. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
  78. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +14 -0
  79. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +17 -22
  80. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
  81. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +53 -65
  82. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts +2 -3
  83. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts.map +1 -1
  84. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.js +9 -20
  85. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts +2 -2
  86. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -1
  87. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +14 -19
  88. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +1 -1
  89. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
  90. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +8 -4
  91. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.js +1 -1
  92. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.d.ts +2 -1
  93. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.d.ts.map +1 -1
  94. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.js +13 -18
  95. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.d.ts +1 -1
  96. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.js +4 -2
  98. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.d.ts.map +1 -1
  99. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.js +4 -6
  100. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.d.ts.map +1 -1
  101. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.js +4 -7
  102. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts +4 -3
  103. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts.map +1 -1
  104. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.js +14 -13
  105. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts +3 -4
  106. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts.map +1 -1
  107. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.js +7 -15
  108. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -1
  109. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +5 -1
  110. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  111. package/src/shade/renderer/particles/GPUParticleSystem.js +1 -7
  112. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts +26 -8
  113. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  114. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +170 -142
  115. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts +1 -1
  116. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts.map +1 -1
  117. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.js +5 -8
  118. package/src/shade/renderer/restir/di/chunk_restir_resolve.d.ts.map +1 -1
  119. package/src/shade/renderer/restir/di/chunk_restir_resolve.js +91 -94
  120. package/src/shade/renderer/restir/reservoir/chunk_rtxdi_stream_sample.js +1 -1
  121. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.d.ts +14 -0
  122. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.d.ts.map +1 -1
  123. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.js +36 -21
  124. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.d.ts +22 -0
  125. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.d.ts.map +1 -0
  126. package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.js +64 -0
  127. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts +15 -4
  128. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts.map +1 -1
  129. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.js +33 -7
  130. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_vertex_normal.d.ts.map +1 -1
  131. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_vertex_normal.js +13 -18
  132. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_encode_vertex_normal.d.ts.map +1 -1
  133. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_encode_vertex_normal.js +28 -30
  134. package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_u32_to_vec2.d.ts.map +1 -1
  135. package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_u32_to_vec2.js +15 -16
  136. package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.d.ts +0 -1
  137. package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.d.ts.map +1 -1
  138. package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.js +15 -18
  139. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.d.ts +40 -0
  140. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.d.ts.map +1 -0
  141. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.js +75 -0
  142. package/src/shade/renderer/shader/chunk/noise/chunk_vdc_radical_inverse.d.ts.map +1 -1
  143. package/src/shade/renderer/shader/chunk/noise/chunk_vdc_radical_inverse.js +3 -1
  144. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.d.ts +28 -0
  145. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.d.ts.map +1 -0
  146. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.js +41 -0
  147. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.d.ts +12 -0
  148. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.d.ts.map +1 -0
  149. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.js +24 -0
  150. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +3 -2
  151. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -1
  152. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +4 -0
  153. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +2 -5
  154. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -1
  155. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +3 -20
  156. package/src/shade/renderer/water/chunk_water_gi.d.ts +1 -1
  157. package/src/shade/renderer/water/chunk_water_gi.d.ts.map +1 -1
  158. package/src/shade/renderer/water/chunk_water_gi.js +6 -8
  159. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +8 -1
  160. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  161. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +79 -35
  162. package/src/core/math/hash/squirrel3.d.ts +0 -17
  163. package/src/core/math/hash/squirrel3.d.ts.map +0 -1
  164. package/src/core/math/hash/squirrel3.js +0 -30
  165. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.d.ts +0 -2
  166. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.d.ts.map +0 -1
  167. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.js +0 -6
  168. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.d.ts +0 -12
  169. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.d.ts.map +0 -1
  170. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.js +0 -30
@@ -1,447 +1,462 @@
1
- import { assert } from "../../core/assert.js";
2
- import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
3
- import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
4
- import { GameAssetType } from "../asset/GameAssetType.js";
5
- import { ImageRGBADataLoader } from "../asset/loaders/image/ImageRGBADataLoader.js";
6
- import { System } from "../ecs/System.js";
7
- import { Transform64 } from "../ecs/transform/Transform64.js";
8
- import { Decal } from "../graphics/ecs/decal/v2/Decal.js";
9
- import { ManagedAtlas } from "../graphics/texture/atlas/ManagedTextureAtlas.js";
10
- import { SpriteAtlas } from "../graphics/texture/atlas/SpriteAtlas.js";
11
- import { SpriteAtlasFill } from "../graphics/texture/atlas/SpriteAtlasFill.js";
12
- import { sprite_atlas_max_lod } from "../graphics/texture/atlas/sprite_atlas_border.js";
13
- import { sampler2d_ensure_uint8_RGBA } from "../graphics/texture/sampler/sampler2d_ensure_uint8_RGBA.js";
14
- import {
15
- decal_identity_normal,
16
- decal_identity_orm,
17
- decal_pack_orm
18
- } from "./decal/decal_material_samplers.js";
19
- import { DECAL_RECORD_WORD_COUNT } from "./decal/DECAL_GPU_RECORD.js";
20
- import { sort_decals_by_priority, write_decal_record } from "./decal/decal_gpu_records.js";
21
- import { GPUDecalRenderer } from "./decal/GPUDecalRenderer.js";
22
-
23
- /**
24
- * Paints the game's decals into the G-buffer under Shade.
25
- *
26
- * **A decal is a surface override, not a tint** (D22): it carries albedo, a tangent-space normal,
27
- * roughness, metalness and emission, and it writes them into the G-buffer before anything is lit —
28
- * so ambient occlusion, screen-space reflection and shading all see a decal exactly as they see the
29
- * surface it is painted on. The renderer this replaces could only lerp diffuse colour inside a
30
- * forward shader.
31
- *
32
- * **Decals reach opaque surfaces only, by construction.** The G-buffer is all they touch and
33
- * transparency composites later, so a decal will never appear on water.
34
- *
35
- * The records are rebuilt every frame. That sounds wasteful and is the cheaper arrangement: the
36
- * froxel binning depends on the camera and so has to run every frame regardless, and rebuilding the
37
- * records alongside it means nothing has to watch a decal's colour for changes — which matters,
38
- * because the signal that would be watched does not fire for alpha-only edits.
39
- */
40
- export class DecalSystem extends System {
41
- dependencies = [Decal, Transform64];
42
-
43
- /**
44
- * @type {GraphicsEngine}
45
- */
46
- #graphics;
47
-
48
- /**
49
- * @type {AssetManager}
50
- */
51
- #assets;
52
-
53
- /**
54
- * @type {GPUDecalRenderer}
55
- */
56
- #renderer = new GPUDecalRenderer();
57
-
58
- /**
59
- * Three atlases, because their contents want different mip filters and different border fills —
60
- * and because albedo is sRGB while the other two are not.
61
- *
62
- * @type {SpriteAtlas}
63
- */
64
- #atlas_albedo = new SpriteAtlas(SpriteAtlasFill.PremultipliedCoverage);
65
-
66
- /**
67
- * @type {SpriteAtlas}
68
- */
69
- #atlas_normal = new SpriteAtlas(SpriteAtlasFill.EdgeExtend);
70
-
71
- /**
72
- * @type {SpriteAtlas}
73
- */
74
- #atlas_orm = new SpriteAtlas(SpriteAtlasFill.EdgeExtend);
75
-
76
- /**
77
- * @type {ManagedAtlas}
78
- */
79
- #managed_albedo;
80
-
81
- /**
82
- * @type {ManagedAtlas}
83
- */
84
- #managed_normal;
85
-
86
- /**
87
- * @type {ManagedAtlas}
88
- */
89
- #managed_orm;
90
-
91
- /**
92
- * What every decal authoring no maps of its own shares. One patch each, packed once.
93
- *
94
- * @type {Sampler2D}
95
- */
96
- #identity_normal = decal_identity_normal();
97
-
98
- /**
99
- * @type {Sampler2D}
100
- */
101
- #identity_orm = decal_identity_orm();
102
-
103
- /**
104
- * The decals to draw, in link order until {@link update} sorts them into draw order.
105
- *
106
- * @type {Array<{entity: number, decal: Decal, transform: Transform, patches: object, listener: Function}>}
107
- */
108
- #entries = [];
109
-
110
- /**
111
- * @type {Map<number, object>}
112
- */
113
- #by_entity = new Map();
114
-
115
- /**
116
- * @type {Float32Array}
117
- */
118
- #records = new Float32Array(0);
119
-
120
- /**
121
- * @type {number}
122
- */
123
- #record_count = 0;
124
-
125
- /**
126
- * @type {RenderExtension|null}
127
- */
128
- #extension = null;
129
-
130
- /**
131
- * What the pass draws. Read-only — the decals follow the entities.
132
- *
133
- * @returns {Array}
134
- */
135
- get decals() {
136
- return this.#entries;
137
- }
138
-
139
- /**
140
- * @returns {number} decals packed for the GPU by the last update
141
- */
142
- get record_count() {
143
- return this.#record_count;
144
- }
145
-
146
- /**
147
- * What was packed, for a gate that wants to see it. Read-only.
148
- *
149
- * @returns {Float32Array}
150
- */
151
- get records() {
152
- return this.#records;
153
- }
154
-
155
- /**
156
- * @param {GraphicsEngine} graphics
157
- * @param {AssetManager} assets
158
- */
159
- constructor(graphics, assets) {
160
- super();
161
-
162
- assert.defined(graphics, 'graphics');
163
- assert.defined(assets, 'assets');
164
-
165
- this.#graphics = graphics;
166
- this.#assets = assets;
167
-
168
- this.#managed_albedo = new ManagedAtlas({
169
- assetManager: this.#assets,
170
- atlas: this.#atlas_albedo
171
- });
172
-
173
- this.#managed_normal = new ManagedAtlas({
174
- assetManager: this.#assets,
175
- atlas: this.#atlas_normal
176
- });
177
-
178
- this.#managed_orm = new ManagedAtlas({
179
- atlas: this.#atlas_orm,
180
- load: async (key) => {
181
- const separator = key.indexOf("\0");
182
- const uri_orm = key.slice(0, separator);
183
- const uri_emissive = key.slice(separator + 1);
184
-
185
- const [orm, emissive] = await Promise.all([
186
- uri_orm === "" ? null : this.#load_sampler(uri_orm),
187
- uri_emissive === "" ? null : this.#load_sampler(uri_emissive)
188
- ]);
189
-
190
- return decal_pack_orm(orm, emissive);
191
- }
192
- });
193
- }
194
-
195
- /**
196
- * @param {string} uri
197
- * @returns {Promise<import("../graphics/texture/sampler/Sampler2D.js").Sampler2D>}
198
- */
199
- async #load_sampler(uri) {
200
- const asset = await this.#assets.promise(uri, GameAssetType.Image);
201
-
202
- return sampler2d_ensure_uint8_RGBA(asset.create());
203
- }
204
-
205
- async startup(entityManager) {
206
- this.entityManager = entityManager;
207
-
208
- const assets = this.#assets;
209
-
210
- if (!assets.hasLoaderForType(GameAssetType.Image)) {
211
- await assets.registerLoader(GameAssetType.Image, new ImageRGBADataLoader());
212
- }
213
-
214
- this.#extension = this.#graphics.add_extension(new DecalExtension(this));
215
- }
216
-
217
- async shutdown(entityManager) {
218
- if (this.#extension !== null) {
219
- this.#graphics.remove_extension(this.#extension);
220
-
221
- this.#extension = null;
222
- }
223
-
224
- for (let i = 0; i < this.#entries.length; i++) {
225
- this.#release(this.#entries[i]);
226
- }
227
-
228
- this.#renderer.destroy();
229
-
230
- this.#entries.length = 0;
231
- this.#by_entity.clear();
232
- this.#record_count = 0;
233
- }
234
-
235
- /**
236
- * The `transform` IS the projection volume, and it projects along its local **+Z** — so the
237
- * surface being painted faces the other way. Get the sign backwards and every pixel of the
238
- * decal is rejected by the coverage fade, silently, with every CPU-side observable here
239
- * reporting success.
240
- *
241
- * @see Decal
242
- * @see chunk_decal_surface_frame
243
- *
244
- * @param {Decal} decal
245
- * @param {Transform64} transform
246
- * @param {number} entity
247
- */
248
- link(decal, transform, entity) {
249
- const entry = {
250
- entity,
251
- decal,
252
- transform,
253
- /**
254
- * Atlas patches, acquired as the textures arrive. A decal whose albedo has not loaded
255
- * yet is not packed at all — there is nothing to draw with, and drawing it with the
256
- * identity patch would put a white square on the ground until the image landed.
257
- */
258
- patches: { albedo: null, normal: null, orm: null }
259
- };
260
-
261
- this.#entries.push(entry);
262
- this.#by_entity.set(entity, entry);
263
-
264
- this.#acquire(entry);
265
- }
266
-
267
- /**
268
- * @param {Decal} decal
269
- * @param {Transform64} transform
270
- * @param {number} entity
271
- */
272
- unlink(decal, transform, entity) {
273
- const entry = this.#by_entity.get(entity);
274
-
275
- if (entry === undefined) {
276
- return;
277
- }
278
-
279
- this.#release(entry);
280
-
281
- const index = this.#entries.indexOf(entry);
282
-
283
- if (index !== -1) {
284
- this.#entries.splice(index, 1);
285
- }
286
-
287
- this.#by_entity.delete(entity);
288
- }
289
-
290
- /**
291
- * @param {object} entry
292
- */
293
- #release(entry) {
294
- const patches = entry.patches;
295
-
296
- if (patches.albedo !== null) {
297
- patches.albedo.release();
298
- patches.albedo = null;
299
- }
300
-
301
- if (patches.normal !== null) {
302
- patches.normal.release();
303
- patches.normal = null;
304
- }
305
-
306
- if (patches.orm !== null) {
307
- patches.orm.release();
308
- patches.orm = null;
309
- }
310
- }
311
-
312
- /**
313
- * Fetch this decal's textures and take a reference on the atlas patch each lands in.
314
- *
315
- * Reference counted and graveyard cached through {@link ManagedAtlas}, because combat spawns a
316
- * footprint decal per step and every one of them names the same file: packing or erasing a patch
317
- * per footprint would repack three atlases and regenerate their mip chains several times a second.
318
- *
319
- * @param {object} entry
320
- */
321
- #acquire(entry) {
322
- const decal = entry.decal;
323
-
324
- const albedo_uri = decal.albedo_uri;
325
-
326
- if (albedo_uri === "") {
327
- return;
328
- }
329
-
330
- entry.patches.albedo = this.#managed_albedo.acquire(albedo_uri);
331
-
332
- entry.patches.normal = decal.uri_normal === ""
333
- ? this.#managed_normal.acquire_sampler(this.#identity_normal)
334
- : this.#managed_normal.acquire(decal.uri_normal);
335
-
336
- entry.patches.orm = (decal.uri_orm === "" && decal.uri_emissive === "")
337
- ? this.#managed_orm.acquire_sampler(this.#identity_orm)
338
- : this.#managed_orm.acquire(`${decal.uri_orm}\0${decal.uri_emissive}`);
339
- }
340
-
341
- update(timeDelta) {
342
- const entries = this.#entries;
343
-
344
- sort_decals_by_priority(entries);
345
-
346
- this.#managed_albedo.update();
347
- this.#managed_normal.update();
348
- this.#managed_orm.update();
349
-
350
- const required = entries.length * DECAL_RECORD_WORD_COUNT;
351
-
352
- if (this.#records.length < required) {
353
- this.#records = new Float32Array(required);
354
- }
355
-
356
- const records = this.#records;
357
-
358
- let count = 0;
359
-
360
- for (let i = 0; i < entries.length; i++) {
361
- const entry = entries[i];
362
- const patches = entry.patches;
363
-
364
- const albedo = patches.albedo?.getValue() ?? null;
365
- const normal = patches.normal?.getValue() ?? null;
366
- const orm = patches.orm?.getValue() ?? null;
367
-
368
- if (albedo === null || normal === null || orm === null) {
369
- // still waiting on an image; a decal is packed once it can be drawn properly
370
- continue;
371
- }
372
-
373
- write_decal_record(
374
- records,
375
- count * DECAL_RECORD_WORD_COUNT,
376
- entry.decal,
377
- entry.transform.matrix,
378
- { albedo: albedo.uv, normal: normal.uv, orm: orm.uv },
379
- {
380
- albedo: sprite_atlas_max_lod(albedo.padding),
381
- normal: sprite_atlas_max_lod(normal.padding),
382
- orm: sprite_atlas_max_lod(orm.padding)
383
- }
384
- );
385
-
386
- count++;
387
- }
388
-
389
- this.#record_count = count;
390
- }
391
-
392
- /**
393
- * @param {FrameContext} frame
394
- */
395
- record(frame) {
396
- if (this.#record_count === 0) {
397
- /*
398
- Nothing to paint, so the frame keeps the G-buffer it already had. Skipping is what makes
399
- the replacement design free when no decal is on screen — the pass reads and rewrites
400
- every pixel when it runs.
401
- */
402
- return;
403
- }
404
-
405
- this.#renderer.graph_draw({
406
- frame,
407
- records: this.#records,
408
- record_count: this.#record_count,
409
- atlas_albedo: this.#atlas_albedo.sampler,
410
- atlas_normal: this.#atlas_normal.sampler,
411
- atlas_orm: this.#atlas_orm.sampler
412
- });
413
- }
414
- }
415
-
416
- /**
417
- * **Late on the phase**, because a decal composites over surfaces that are already written rather
418
- * than preceding them — anything another extension writes into the G-buffer at this phase. That
419
- * requirement used to be met by putting a registration call above an `await` and writing a comment
420
- * about it; here it is the declaration below. The textured ground no longer depends on it: terrain is
421
- * shaded in the G-buffer pass itself, before any extension runs.
422
- */
423
- export class DecalExtension extends RenderExtension {
424
- name = "decals";
425
-
426
- phase = FramePhase.AfterGBuffer;
427
-
428
- relative_order = 1;
429
-
430
- /**
431
- * @type {DecalSystem}
432
- */
433
- #system;
434
-
435
- /**
436
- * @param {DecalSystem} system
437
- */
438
- constructor(system) {
439
- super();
440
-
441
- this.#system = system;
442
- }
443
-
444
- record(frame) {
445
- this.#system.record(frame);
446
- }
447
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
3
+ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
4
+ import { GameAssetType } from "../asset/GameAssetType.js";
5
+ import { ImageRGBADataLoader } from "../asset/loaders/image/ImageRGBADataLoader.js";
6
+ import { System } from "../ecs/System.js";
7
+ import { Transform64 } from "../ecs/transform/Transform64.js";
8
+ import { Decal } from "../graphics/ecs/decal/v2/Decal.js";
9
+ import { ManagedAtlas } from "../graphics/texture/atlas/ManagedTextureAtlas.js";
10
+ import { SpriteAtlas } from "../graphics/texture/atlas/SpriteAtlas.js";
11
+ import { SpriteAtlasFill } from "../graphics/texture/atlas/SpriteAtlasFill.js";
12
+ import { sprite_atlas_max_lod } from "../graphics/texture/atlas/sprite_atlas_border.js";
13
+ import { sampler2d_ensure_uint8_RGBA } from "../graphics/texture/sampler/sampler2d_ensure_uint8_RGBA.js";
14
+ import {
15
+ decal_identity_albedo,
16
+ decal_identity_normal,
17
+ decal_identity_orm,
18
+ decal_pack_orm
19
+ } from "./decal/decal_material_samplers.js";
20
+ import { DECAL_RECORD_WORD_COUNT } from "./decal/DECAL_GPU_RECORD.js";
21
+ import { sort_decals_by_priority, write_decal_record } from "./decal/decal_gpu_records.js";
22
+ import { GPUDecalRenderer } from "./decal/GPUDecalRenderer.js";
23
+
24
+ /**
25
+ * Paints the game's decals into the G-buffer under Shade.
26
+ *
27
+ * **A decal is a surface override, not a tint** (D22): it carries albedo, a tangent-space normal,
28
+ * roughness, metalness and emission, and it writes them into the G-buffer before anything is lit —
29
+ * so ambient occlusion, screen-space reflection and shading all see a decal exactly as they see the
30
+ * surface it is painted on. The renderer this replaces could only lerp diffuse colour inside a
31
+ * forward shader.
32
+ *
33
+ * **Decals reach opaque surfaces only, by construction.** The G-buffer is all they touch and
34
+ * transparency composites later, so a decal will never appear on water.
35
+ *
36
+ * The records are rebuilt every frame. That sounds wasteful and is the cheaper arrangement: the
37
+ * froxel binning depends on the camera and so has to run every frame regardless, and rebuilding the
38
+ * records alongside it means nothing has to watch a decal's colour for changes — which matters,
39
+ * because the signal that would be watched does not fire for alpha-only edits.
40
+ */
41
+ export class DecalSystem extends System {
42
+ dependencies = [Decal, Transform64];
43
+
44
+ /**
45
+ * @type {GraphicsEngine}
46
+ */
47
+ #graphics;
48
+
49
+ /**
50
+ * @type {AssetManager}
51
+ */
52
+ #assets;
53
+
54
+ /**
55
+ * @type {GPUDecalRenderer}
56
+ */
57
+ #renderer = new GPUDecalRenderer();
58
+
59
+ /**
60
+ * Three atlases, because their contents want different mip filters and different border fills —
61
+ * and because albedo is sRGB while the other two are not.
62
+ *
63
+ * @type {SpriteAtlas}
64
+ */
65
+ #atlas_albedo = new SpriteAtlas(SpriteAtlasFill.PremultipliedCoverage);
66
+
67
+ /**
68
+ * @type {SpriteAtlas}
69
+ */
70
+ #atlas_normal = new SpriteAtlas(SpriteAtlasFill.EdgeExtend);
71
+
72
+ /**
73
+ * @type {SpriteAtlas}
74
+ */
75
+ #atlas_orm = new SpriteAtlas(SpriteAtlasFill.EdgeExtend);
76
+
77
+ /**
78
+ * @type {ManagedAtlas}
79
+ */
80
+ #managed_albedo;
81
+
82
+ /**
83
+ * @type {ManagedAtlas}
84
+ */
85
+ #managed_normal;
86
+
87
+ /**
88
+ * @type {ManagedAtlas}
89
+ */
90
+ #managed_orm;
91
+
92
+ /**
93
+ * What every decal authoring no maps of its own shares. One patch each, packed once.
94
+ *
95
+ * @type {Sampler2D}
96
+ */
97
+ #identity_albedo = decal_identity_albedo();
98
+
99
+ /**
100
+ * @type {Sampler2D}
101
+ */
102
+ #identity_normal = decal_identity_normal();
103
+
104
+ /**
105
+ * @type {Sampler2D}
106
+ */
107
+ #identity_orm = decal_identity_orm();
108
+
109
+ /**
110
+ * The decals to draw, in link order until {@link update} sorts them into draw order.
111
+ *
112
+ * @type {Array<{entity: number, decal: Decal, transform: Transform, patches: object, listener: Function}>}
113
+ */
114
+ #entries = [];
115
+
116
+ /**
117
+ * @type {Map<number, object>}
118
+ */
119
+ #by_entity = new Map();
120
+
121
+ /**
122
+ * @type {Float32Array}
123
+ */
124
+ #records = new Float32Array(0);
125
+
126
+ /**
127
+ * {@link #records} as words, for the fields that are bit patterns rather than floats.
128
+ *
129
+ * @type {Uint32Array}
130
+ */
131
+ #record_words = new Uint32Array(this.#records.buffer);
132
+
133
+ /**
134
+ * @type {number}
135
+ */
136
+ #record_count = 0;
137
+
138
+ /**
139
+ * @type {RenderExtension|null}
140
+ */
141
+ #extension = null;
142
+
143
+ /**
144
+ * What the pass draws. Read-only — the decals follow the entities.
145
+ *
146
+ * @returns {Array}
147
+ */
148
+ get decals() {
149
+ return this.#entries;
150
+ }
151
+
152
+ /**
153
+ * @returns {number} decals packed for the GPU by the last update
154
+ */
155
+ get record_count() {
156
+ return this.#record_count;
157
+ }
158
+
159
+ /**
160
+ * What was packed, for a gate that wants to see it. Read-only.
161
+ *
162
+ * @returns {Float32Array}
163
+ */
164
+ get records() {
165
+ return this.#records;
166
+ }
167
+
168
+ /**
169
+ * @param {GraphicsEngine} graphics
170
+ * @param {AssetManager} assets
171
+ */
172
+ constructor(graphics, assets) {
173
+ super();
174
+
175
+ assert.defined(graphics, 'graphics');
176
+ assert.defined(assets, 'assets');
177
+
178
+ this.#graphics = graphics;
179
+ this.#assets = assets;
180
+
181
+ this.#managed_albedo = new ManagedAtlas({
182
+ assetManager: this.#assets,
183
+ atlas: this.#atlas_albedo
184
+ });
185
+
186
+ this.#managed_normal = new ManagedAtlas({
187
+ assetManager: this.#assets,
188
+ atlas: this.#atlas_normal
189
+ });
190
+
191
+ this.#managed_orm = new ManagedAtlas({
192
+ atlas: this.#atlas_orm,
193
+ load: async (key) => {
194
+ const separator = key.indexOf("\0");
195
+ const uri_orm = key.slice(0, separator);
196
+ const uri_emissive = key.slice(separator + 1);
197
+
198
+ const [orm, emissive] = await Promise.all([
199
+ uri_orm === "" ? null : this.#load_sampler(uri_orm),
200
+ uri_emissive === "" ? null : this.#load_sampler(uri_emissive)
201
+ ]);
202
+
203
+ return decal_pack_orm(orm, emissive);
204
+ }
205
+ });
206
+ }
207
+
208
+ /**
209
+ * @param {string} uri
210
+ * @returns {Promise<import("../graphics/texture/sampler/Sampler2D.js").Sampler2D>}
211
+ */
212
+ async #load_sampler(uri) {
213
+ const asset = await this.#assets.promise(uri, GameAssetType.Image);
214
+
215
+ return sampler2d_ensure_uint8_RGBA(asset.create());
216
+ }
217
+
218
+ async startup(entityManager) {
219
+ this.entityManager = entityManager;
220
+
221
+ const assets = this.#assets;
222
+
223
+ if (!assets.hasLoaderForType(GameAssetType.Image)) {
224
+ await assets.registerLoader(GameAssetType.Image, new ImageRGBADataLoader());
225
+ }
226
+
227
+ this.#extension = this.#graphics.add_extension(new DecalExtension(this));
228
+ }
229
+
230
+ async shutdown(entityManager) {
231
+ if (this.#extension !== null) {
232
+ this.#graphics.remove_extension(this.#extension);
233
+
234
+ this.#extension = null;
235
+ }
236
+
237
+ for (let i = 0; i < this.#entries.length; i++) {
238
+ this.#release(this.#entries[i]);
239
+ }
240
+
241
+ this.#renderer.destroy();
242
+
243
+ this.#entries.length = 0;
244
+ this.#by_entity.clear();
245
+ this.#record_count = 0;
246
+ }
247
+
248
+ /**
249
+ * The `transform` IS the projection volume, and it projects along its local **+Z** — so the
250
+ * surface being painted faces the other way. Get the sign backwards and every pixel of the
251
+ * decal is rejected by the coverage fade, silently, with every CPU-side observable here
252
+ * reporting success.
253
+ *
254
+ * @see Decal
255
+ * @see chunk_decal_surface_frame
256
+ *
257
+ * @param {Decal} decal
258
+ * @param {Transform64} transform
259
+ * @param {number} entity
260
+ */
261
+ link(decal, transform, entity) {
262
+ const entry = {
263
+ entity,
264
+ decal,
265
+ transform,
266
+ /**
267
+ * Atlas patches, acquired as the textures arrive. A decal whose albedo has not loaded
268
+ * yet is not packed at all — its alpha is the decal's shape, and drawing it with the
269
+ * identity patch would put a white square on the ground until the image landed. Only a
270
+ * decal that names no albedo map at all draws with the identity patch.
271
+ */
272
+ patches: { albedo: null, normal: null, orm: null }
273
+ };
274
+
275
+ this.#entries.push(entry);
276
+ this.#by_entity.set(entity, entry);
277
+
278
+ this.#acquire(entry);
279
+ }
280
+
281
+ /**
282
+ * @param {Decal} decal
283
+ * @param {Transform64} transform
284
+ * @param {number} entity
285
+ */
286
+ unlink(decal, transform, entity) {
287
+ const entry = this.#by_entity.get(entity);
288
+
289
+ if (entry === undefined) {
290
+ return;
291
+ }
292
+
293
+ this.#release(entry);
294
+
295
+ const index = this.#entries.indexOf(entry);
296
+
297
+ if (index !== -1) {
298
+ this.#entries.splice(index, 1);
299
+ }
300
+
301
+ this.#by_entity.delete(entity);
302
+ }
303
+
304
+ /**
305
+ * @param {object} entry
306
+ */
307
+ #release(entry) {
308
+ const patches = entry.patches;
309
+
310
+ if (patches.albedo !== null) {
311
+ patches.albedo.release();
312
+ patches.albedo = null;
313
+ }
314
+
315
+ if (patches.normal !== null) {
316
+ patches.normal.release();
317
+ patches.normal = null;
318
+ }
319
+
320
+ if (patches.orm !== null) {
321
+ patches.orm.release();
322
+ patches.orm = null;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Fetch this decal's textures and take a reference on the atlas patch each lands in.
328
+ *
329
+ * Reference counted and graveyard cached through {@link ManagedAtlas}, because combat spawns a
330
+ * footprint decal per step and every one of them names the same file: packing or erasing a patch
331
+ * per footprint would repack three atlases and regenerate their mip chains several times a second.
332
+ *
333
+ * @param {object} entry
334
+ */
335
+ #acquire(entry) {
336
+ const decal = entry.decal;
337
+
338
+ const albedo_uri = decal.albedo_uri;
339
+
340
+ // no albedo map is a mask of one over the whole volume, coloured by the tint alone
341
+ entry.patches.albedo = albedo_uri === ""
342
+ ? this.#managed_albedo.acquire_sampler(this.#identity_albedo)
343
+ : this.#managed_albedo.acquire(albedo_uri);
344
+
345
+ entry.patches.normal = decal.uri_normal === ""
346
+ ? this.#managed_normal.acquire_sampler(this.#identity_normal)
347
+ : this.#managed_normal.acquire(decal.uri_normal);
348
+
349
+ entry.patches.orm = (decal.uri_orm === "" && decal.uri_emissive === "")
350
+ ? this.#managed_orm.acquire_sampler(this.#identity_orm)
351
+ : this.#managed_orm.acquire(`${decal.uri_orm}\0${decal.uri_emissive}`);
352
+ }
353
+
354
+ update(timeDelta) {
355
+ const entries = this.#entries;
356
+
357
+ sort_decals_by_priority(entries);
358
+
359
+ this.#managed_albedo.update();
360
+ this.#managed_normal.update();
361
+ this.#managed_orm.update();
362
+
363
+ const required = entries.length * DECAL_RECORD_WORD_COUNT;
364
+
365
+ if (this.#records.length < required) {
366
+ this.#records = new Float32Array(required);
367
+ this.#record_words = new Uint32Array(this.#records.buffer);
368
+ }
369
+
370
+ const records = this.#records;
371
+
372
+ let count = 0;
373
+
374
+ for (let i = 0; i < entries.length; i++) {
375
+ const entry = entries[i];
376
+ const patches = entry.patches;
377
+
378
+ const albedo = patches.albedo?.getValue() ?? null;
379
+ const normal = patches.normal?.getValue() ?? null;
380
+ const orm = patches.orm?.getValue() ?? null;
381
+
382
+ if (albedo === null || normal === null || orm === null) {
383
+ // still waiting on an image; a decal is packed once it can be drawn properly
384
+ continue;
385
+ }
386
+
387
+ write_decal_record(
388
+ records,
389
+ this.#record_words,
390
+ count * DECAL_RECORD_WORD_COUNT,
391
+ entry.decal,
392
+ entry.transform.matrix,
393
+ { albedo: albedo.uv, normal: normal.uv, orm: orm.uv },
394
+ {
395
+ albedo: sprite_atlas_max_lod(albedo.padding),
396
+ normal: sprite_atlas_max_lod(normal.padding),
397
+ orm: sprite_atlas_max_lod(orm.padding)
398
+ }
399
+ );
400
+
401
+ count++;
402
+ }
403
+
404
+ this.#record_count = count;
405
+ }
406
+
407
+ /**
408
+ * @param {FrameContext} frame
409
+ */
410
+ record(frame) {
411
+ if (this.#record_count === 0) {
412
+ /*
413
+ Nothing to paint, so the frame keeps the G-buffer it already had. Skipping is what makes
414
+ the replacement design free when no decal is on screen — the pass reads and rewrites
415
+ every pixel when it runs.
416
+ */
417
+ return;
418
+ }
419
+
420
+ this.#renderer.graph_draw({
421
+ frame,
422
+ records: this.#records,
423
+ record_count: this.#record_count,
424
+ atlas_albedo: this.#atlas_albedo.sampler,
425
+ atlas_normal: this.#atlas_normal.sampler,
426
+ atlas_orm: this.#atlas_orm.sampler
427
+ });
428
+ }
429
+ }
430
+
431
+ /**
432
+ * **Late on the phase**, because a decal composites over surfaces that are already written rather
433
+ * than preceding them — anything another extension writes into the G-buffer at this phase. That
434
+ * requirement used to be met by putting a registration call above an `await` and writing a comment
435
+ * about it; here it is the declaration below. The textured ground no longer depends on it: terrain is
436
+ * shaded in the G-buffer pass itself, before any extension runs.
437
+ */
438
+ export class DecalExtension extends RenderExtension {
439
+ name = "decals";
440
+
441
+ phase = FramePhase.AfterGBuffer;
442
+
443
+ relative_order = 1;
444
+
445
+ /**
446
+ * @type {DecalSystem}
447
+ */
448
+ #system;
449
+
450
+ /**
451
+ * @param {DecalSystem} system
452
+ */
453
+ constructor(system) {
454
+ super();
455
+
456
+ this.#system = system;
457
+ }
458
+
459
+ record(frame) {
460
+ this.#system.record(frame);
461
+ }
462
+ }