@carbonenginejs/runtime 0.1.0-alpha.6 → 0.1.0-alpha.7

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.

Potentially problematic release.


This version of @carbonenginejs/runtime might be problematic. Click here for more details.

Files changed (92) hide show
  1. package/dist/global/consts/effectPaths.js +4 -1
  2. package/dist/global/consts/effectPaths.js.map +1 -1
  3. package/dist/global/consts/index.js +1 -1
  4. package/dist/global/index.js +1 -1
  5. package/dist/index.js +3 -3
  6. package/dist/resource/format/carbonEffect/carbonEffectBackendBlock.js +19 -2
  7. package/dist/resource/format/carbonEffect/carbonEffectBackendBlock.js.map +1 -1
  8. package/dist/resource/format/index.js +1 -1
  9. package/dist/resource/formats/hlsl/core/carbonTypedViews.js +121 -65
  10. package/dist/resource/formats/hlsl/core/carbonTypedViews.js.map +1 -1
  11. package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js +10 -7
  12. package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js.map +1 -1
  13. package/dist/resource/formats/webgpu/core/effectBackendBodySet.js +3 -1
  14. package/dist/resource/formats/webgpu/core/effectBackendBodySet.js.map +1 -1
  15. package/dist/resource/formats/webgpu/core/packageEffect.js +5 -0
  16. package/dist/resource/formats/webgpu/core/packageEffect.js.map +1 -1
  17. package/dist/resource/formats/webgpu/core/wgsl/emitWgsl.js +20 -0
  18. package/dist/resource/formats/webgpu/core/wgsl/emitWgsl.js.map +1 -1
  19. package/dist/resource/formats/webgpu/core/wgsl/emulatedAddressing.js +93 -4
  20. package/dist/resource/formats/webgpu/core/wgsl/emulatedAddressing.js.map +1 -1
  21. package/dist/resource/formats/webgpu/core/wgsl/lowerBindingLayout.js +74 -64
  22. package/dist/resource/formats/webgpu/core/wgsl/lowerBindingLayout.js.map +1 -1
  23. package/dist/resource/formats/webgpu/core/wgsl/lowerFragmentProgram.js +16 -7
  24. package/dist/resource/formats/webgpu/core/wgsl/lowerFragmentProgram.js.map +1 -1
  25. package/dist/resource/formats/webgpu/core/wgsl/wgslTypedViews.js +69 -27
  26. package/dist/resource/formats/webgpu/core/wgsl/wgslTypedViews.js.map +1 -1
  27. package/dist/resource/geometry/registerGeometryResources.js +10 -1
  28. package/dist/resource/geometry/registerGeometryResources.js.map +1 -1
  29. package/dist/sof/EveSOFDNA.js +4 -2
  30. package/dist/sof/EveSOFDNA.js.map +1 -1
  31. package/dist/sof/EveSOFDataMgr.js +83 -69
  32. package/dist/sof/EveSOFDataMgr.js.map +1 -1
  33. package/dist/trinity/core/Tr2Denoiser.js +6 -1
  34. package/dist/trinity/core/Tr2Denoiser.js.map +1 -1
  35. package/dist/trinity/core/Tr2ShadowMap.js +7 -2
  36. package/dist/trinity/core/Tr2ShadowMap.js.map +1 -1
  37. package/dist/trinity/core/context/Tr2RenderContext.js +43 -0
  38. package/dist/trinity/core/context/Tr2RenderContext.js.map +1 -1
  39. package/dist/trinity/core/device/Tr2GpuStructuredBuffer.js +134 -0
  40. package/dist/trinity/core/device/Tr2GpuStructuredBuffer.js.map +1 -0
  41. package/dist/trinity/core/index.js +1 -0
  42. package/dist/trinity/core/index.js.map +1 -1
  43. package/dist/trinity/core/lighting/Tr2LightManager.js +410 -124
  44. package/dist/trinity/core/lighting/Tr2LightManager.js.map +1 -1
  45. package/dist/trinity/eve/effect/lensflare/EveLensflare.js +303 -312
  46. package/dist/trinity/eve/effect/lensflare/EveLensflare.js.map +1 -1
  47. package/dist/trinity/eve/effect/lensflare/EveOccluder.js +117 -0
  48. package/dist/trinity/eve/effect/lensflare/EveOccluder.js.map +1 -0
  49. package/dist/trinity/eve/index.js +1 -0
  50. package/dist/trinity/eve/index.js.map +1 -1
  51. package/dist/trinity/eve/scene/EveSpaceScene.js +286 -6
  52. package/dist/trinity/eve/scene/EveSpaceScene.js.map +1 -1
  53. package/dist/trinity/eve/scene/EveSpaceSceneRenderDriver.js +212 -32
  54. package/dist/trinity/eve/scene/EveSpaceSceneRenderDriver.js.map +1 -1
  55. package/dist/trinity/generated/eve/effect/index.js +0 -1
  56. package/dist/trinity/generated/eve/effect/index.js.map +1 -1
  57. package/dist/trinity/generated/eve/index.js +0 -1
  58. package/dist/trinity/generated/eve/index.js.map +1 -1
  59. package/dist/trinity/generated/index.js +0 -2
  60. package/dist/trinity/generated/index.js.map +1 -1
  61. package/dist/trinity/generated/trinityCore/index.js +0 -1
  62. package/dist/trinity/generated/trinityCore/index.js.map +1 -1
  63. package/dist/trinity/index.js +2 -2
  64. package/dist/trinity/postProcess/Tr2PostProcessRenderer.js +4 -1
  65. package/dist/trinity/postProcess/Tr2PostProcessRenderer.js.map +1 -1
  66. package/dist/trinity/postProcess/Tr2SSAO.js +282 -21
  67. package/dist/trinity/postProcess/Tr2SSAO.js.map +1 -1
  68. package/dist/trinity/shader/parameter/Tr2RuntimeTextureParameter.js +71 -53
  69. package/dist/trinity/shader/parameter/Tr2RuntimeTextureParameter.js.map +1 -1
  70. package/dist/trinityal/webgpu/CjsWebgpuRenderContextAL.js +81 -18
  71. package/dist/trinityal/webgpu/CjsWebgpuRenderContextAL.js.map +1 -1
  72. package/dist/trinityal/webgpu/CjsWebgpuResourceSetAL.js +46 -4
  73. package/dist/trinityal/webgpu/CjsWebgpuResourceSetAL.js.map +1 -1
  74. package/dist/trinityal/webgpu/CjsWebgpuShaderAL.js +32 -1
  75. package/dist/trinityal/webgpu/CjsWebgpuShaderAL.js.map +1 -1
  76. package/dist/trinityal/webgpu/CjsWebgpuShaderProgramAL.js +27 -0
  77. package/dist/trinityal/webgpu/CjsWebgpuShaderProgramAL.js.map +1 -1
  78. package/dist/trinityal/webgpu/CjsWebgpuTextureAL.js +29 -4
  79. package/dist/trinityal/webgpu/CjsWebgpuTextureAL.js.map +1 -1
  80. package/dist/trinityal/webgpu/core/CjsWebgpuPsoDescription.js +32 -2
  81. package/dist/trinityal/webgpu/core/CjsWebgpuPsoDescription.js.map +1 -1
  82. package/dist/trinityal/webgpu/core/CjsWebgpuUtils.js +57 -5
  83. package/dist/trinityal/webgpu/core/CjsWebgpuUtils.js.map +1 -1
  84. package/dist/trinityal/webgpu/core/CjsWebgpuWorkQueue.js +111 -5
  85. package/dist/trinityal/webgpu/core/CjsWebgpuWorkQueue.js.map +1 -1
  86. package/docs/trinity/reference/classes/generated.md +20 -20
  87. package/docs/trinity/reference/classes/post-process.md +1 -1
  88. package/package.json +1 -1
  89. package/dist/trinity/generated/eve/effect/EveOccluder.js +0 -38
  90. package/dist/trinity/generated/eve/effect/EveOccluder.js.map +0 -1
  91. package/dist/trinity/generated/trinityCore/Tr2GpuStructuredBuffer.js +0 -48
  92. package/dist/trinity/generated/trinityCore/Tr2GpuStructuredBuffer.js.map +0 -1
@@ -5,6 +5,12 @@ import { num } from '#math/num';
5
5
  import { vec3 } from '#math/vec3';
6
6
  import { ShadowQuality } from '../../generated/trinityCore/enums.js';
7
7
  import { Tr2TextureArray as _Tr2TextureArray } from '../Tr2TextureArray/Tr2TextureArray.js';
8
+ import { Tr2GpuStructuredBuffer as _Tr2GpuStructuredBuff } from '../device/Tr2GpuStructuredBuffer.js';
9
+ import { TriDevice as _TriDevice } from '../device/TriDevice.js';
10
+ import { Tr2Renderer as _Tr2Renderer } from '../Tr2Renderer.js';
11
+ import { Tr2RenderContext_GetMainThreadRenderContext } from '../context/Tr2RenderContext.js';
12
+ import { Tr2VariableStore as _Tr2VariableStore } from '../variable/Tr2VariableStore.js';
13
+ import { Succeeded } from '#trinityal';
8
14
 
9
15
  let _initStatic, _initProto, _initClass;
10
16
 
@@ -19,6 +25,13 @@ const HIGH_QUALITY_ATLAS_SIZE_LOG2 = 14;
19
25
  const HIGH_QUALITY_ATLAS_ENTRY_MAX_SIZE = 1 << 13;
20
26
  const INFINITE_SIZE_CLAMP = 1 << 14; // 1 << HIGH_QUALITY_ATLAS_SIZE_LOG2
21
27
  const FLOATS_PER_LIGHT = 12; // 48 bytes / 3 RGBA32 texels
28
+ const BYTES_PER_LIGHT = FLOATS_PER_LIGHT * 4;
29
+ // Tr2LightManager.cpp:39-40 - the light-list tiles, 16x16 pixels.
30
+ const TILE_WIDTH = 16;
31
+ const TILE_HEIGHT = 16;
32
+ // Header words per tile: list0 head, list1 head, particle-list head
33
+ // (computelightlists writes all three; the hull shaders read +0).
34
+ const HEADER_WORDS_PER_TILE = 3;
22
35
 
23
36
  /** CCP_ALIGN: round up to a power-of-two alignment. */
24
37
  function align(value, alignment) {
@@ -88,39 +101,39 @@ new class extends _identity {
88
101
  } = _applyDecs2311(this, [type.define({
89
102
  className: "Tr2LightManager",
90
103
  family: "trinityCore"
91
- })], [[[carbon, carbon.method, impl, impl.implemented], 26, "areLightFlagsValid"], [[carbon, carbon.method, impl, impl.implemented], 18, "SetShadowQuality"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetShadowMapAtlasSettings"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetUnpackedShadowMapData"], [[carbon, carbon.method, impl, impl.adapted, void 0, impl.reason("ClearLightIndices' UAV clear is AL realization; the vector clears are ported. The render-context parameter is accepted for signature parity and unused.")], 18, "Clear"], [[carbon, carbon.method, impl, impl.implemented], 18, "SetFrustum"], [[carbon, carbon.method, impl, impl.implemented], 18, "AdjustLightCutoff"], [[carbon, carbon.method, impl, impl.implemented], 18, "AddPointLight"], [[carbon, carbon.method, impl, impl.implemented], 18, "AddLight"], [[carbon, carbon.method, impl, impl.implemented], 18, "ResolveLightData"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetCurrentSpaceSceneShadowQuality"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetLightData"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetVolumetricLights"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetShadowCastingLights"], [[impl, impl.custom, void 0, impl.reason("AL seam: Trinity owns the packed description bytes; the AL realizes LightBuffer from this view. Carbon has no such seam; Tr2DataTextureManager now writes its own texture, so this has no precedent.")], 18, "GetLightBufferData"], [[impl, impl.custom, void 0, impl.reason("AL seam companion to GetLightBufferData.")], 18, "GetLightCount"], [[impl, impl.custom, void 0, impl.reason("Non-Carbon extension: cheaper than the AL diffing a typed array; Carbon re-uploads unconditionally.")], 18, "GetDataRevision"]], 0, void 0, CjsModel));
104
+ })], [[[carbon, carbon.method, impl, impl.implemented], 26, "areLightFlagsValid"], [[carbon, carbon.method, impl, impl.implemented], 18, "SetShadowQuality"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetShadowMapAtlasSettings"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetUnpackedShadowMapData"], [[carbon, carbon.method, impl, impl.implemented], 18, "Clear"], [[carbon, carbon.method, impl, impl.implemented], 18, "SetFrustum"], [[carbon, carbon.method, impl, impl.implemented], 18, "AdjustLightCutoff"], [[carbon, carbon.method, impl, impl.implemented], 18, "AddPointLight"], [[carbon, carbon.method, impl, impl.implemented], 18, "AddLight"], [[carbon, carbon.method, impl, impl.implemented], 18, "ResolveLightData"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetCurrentSpaceSceneShadowQuality"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetLightData"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetVolumetricLights"], [[carbon, carbon.method, impl, impl.implemented], 18, "GetShadowCastingLights"], [[carbon, carbon.method, impl, impl.implemented], 26, "getOrCreateInstance"], [[carbon, carbon.method, impl, impl.implemented], 26, "getInstance"], [[carbon, carbon.method, impl, impl.adapted], 26, "deleteInstance"], [[carbon, carbon.method, impl, impl.implemented], 18, "SetVariableStore"], [[carbon, carbon.method, impl, impl.implemented], 18, "PrepareResources"], [[carbon, carbon.method, impl, impl.adapted], 18, "OnPrepareResources"], [[carbon, carbon.method, impl, impl.implemented], 18, "ReleaseResources"], [[carbon, carbon.method, impl, impl.adapted], 18, "ClearLightIndices"], [[carbon, carbon.method, impl, impl.implemented], 18, "UpdateLightBuffer"], [[carbon, carbon.method, impl, impl.implemented], 18, "UpdateLists"], [[carbon, carbon.method, impl, impl.adapted], 18, "DoUpdateLists"], [[impl, impl.custom, void 0, impl.reason("AL seam: Trinity owns the packed description bytes; the AL realizes LightBuffer from this view. Carbon has no such seam; Tr2DataTextureManager now writes its own texture, so this has no precedent.")], 18, "GetLightBufferData"], [[impl, impl.custom, void 0, impl.reason("AL seam companion to GetLightBufferData.")], 18, "GetLightCount"], [[impl, impl.custom, void 0, impl.reason("Non-Carbon extension: cheaper than the AL diffing a typed array; Carbon re-uploads unconditionally.")], 18, "GetDataRevision"]], 0, void 0, CjsModel));
92
105
  _initStatic(this);
93
106
  }
94
107
  // Carbon m_lightData after the TLS flatten: the frame's accepted records,
95
108
  // plain-object copies (every producer reuses a static scratch record, so
96
109
  // AddLight copies by value exactly as std::vector::push_back does).
97
- #records = (_initProto(this), []);
110
+ _records = (_initProto(this), []);
98
111
 
99
- // Carbon m_volumetricLights / m_shadowCastingLights: indices into #records.
100
- #volumetricLights = [];
101
- #shadowCastingLights = [];
112
+ // Carbon m_volumetricLights / m_shadowCastingLights: indices into _records.
113
+ _volumetricLights = [];
114
+ _shadowCastingLights = [];
102
115
 
103
116
  // Carbon m_frustum, by reference (SetFrustum copies by value in C++; the
104
117
  // stamped frame frustum is not mutated during the gather, so a reference
105
118
  // carries the same guarantee here).
106
- #frustum = null;
119
+ _frustum = null;
107
120
 
108
121
  // Carbon m_adjustedCutoff (cpp:249-252).
109
- #adjustedCutoff = CUTOFF_PIXEL_SIZE;
122
+ _adjustedCutoff = CUTOFF_PIXEL_SIZE;
110
123
 
111
124
  // Carbon m_currentSpaceSceneShadowQuality (h:197).
112
- #currentSpaceSceneShadowQuality = ShadowQuality.SHADOW_DISABLED;
125
+ _currentSpaceSceneShadowQuality = ShadowQuality.SHADOW_DISABLED;
113
126
 
114
127
  // Carbon nextFrameShadowQuality (h:196) - a bitmask collecting every
115
128
  // scene's requested quality during the current frame.
116
- #nextFrameShadowQuality = 0;
129
+ _nextFrameShadowQuality = 0;
117
130
 
118
131
  // Carbon m_currentFrameCounter (h:198; ctor sets -1, cpp:164).
119
- #currentFrameCounter = -1;
132
+ _currentFrameCounter = -1;
120
133
 
121
134
  // Carbon's anonymous m_ShadowMap block (h:200-205): the atlas settings,
122
135
  // the guillotine node tree, and the quality the atlas was last sized for.
123
- #shadowMap = {
136
+ _shadowMap = {
124
137
  atlasSettings: calculateShadowMapAtlasSettings(ShadowQuality.SHADOW_DISABLED),
125
138
  atlasNodes: [],
126
139
  qualityUsedByAtlas: ShadowQuality.SHADOW_DISABLED
@@ -129,16 +142,16 @@ new class extends _identity {
129
142
  // Non-Carbon: profile-object -> slice index, assigned on first sight. The
130
143
  // slot registry is description-side (Carbon assigns slices through the
131
144
  // manager-owned Tr2TextureArray, cpp:682-686, whose realization is AL).
132
- #profileSlots = new Map();
145
+ _profileSlots = new Map();
133
146
 
134
147
  // The packed PerLightData buffer (contract: 48 bytes / 3 RGBA32 texels per
135
148
  // light), built by ResolveLightData, uploaded by the AL.
136
- #packed = new Float32Array(LIGHT_BUFFER_SIZE * FLOATS_PER_LIGHT);
137
- #packedBits = new Uint32Array(this.#packed.buffer);
138
- #packedCount = 0;
149
+ _packed = new Float32Array(LIGHT_BUFFER_SIZE * FLOATS_PER_LIGHT);
150
+ _packedBits = new Uint32Array(this._packed.buffer);
151
+ _packedCount = 0;
139
152
 
140
153
  // Non-Carbon: bumped by ResolveLightData so the AL can skip re-uploads.
141
- #revision = 0;
154
+ _revision = 0;
142
155
 
143
156
  /** Tr2LightManager.h:100-105 - the light flag bits, Carbon's raw uint16 spelling. */
144
157
 
@@ -175,30 +188,30 @@ new class extends _identity {
175
188
  */
176
189
  SetShadowQuality(quality, frameCounter = 0) {
177
190
  const shadowQuality = Number(quality) || 0;
178
- this.#currentSpaceSceneShadowQuality = shadowQuality;
179
- if (this.#currentFrameCounter !== frameCounter) {
191
+ this._currentSpaceSceneShadowQuality = shadowQuality;
192
+ if (this._currentFrameCounter !== frameCounter) {
180
193
  if (!_Tr2LightManager.useDynamicLightsShadows) {
181
- this.#nextFrameShadowQuality = 0;
194
+ this._nextFrameShadowQuality = 0;
182
195
  }
183
- if (this.#nextFrameShadowQuality & 1 << ShadowQuality.SHADOW_HIGH) {
184
- this.#shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_HIGH;
185
- } else if (this.#nextFrameShadowQuality & 1 << ShadowQuality.SHADOW_LOW) {
186
- this.#shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_LOW;
196
+ if (this._nextFrameShadowQuality & 1 << ShadowQuality.SHADOW_HIGH) {
197
+ this._shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_HIGH;
198
+ } else if (this._nextFrameShadowQuality & 1 << ShadowQuality.SHADOW_LOW) {
199
+ this._shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_LOW;
187
200
  } else {
188
- this.#shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_DISABLED;
201
+ this._shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_DISABLED;
189
202
  }
190
- this.#nextFrameShadowQuality = 1 << shadowQuality;
191
- this.#currentFrameCounter = frameCounter;
203
+ this._nextFrameShadowQuality = 1 << shadowQuality;
204
+ this._currentFrameCounter = frameCounter;
192
205
  }
193
- this.#nextFrameShadowQuality |= 1 << shadowQuality;
194
- const clamped = Math.min(shadowQuality, this.#shadowMap.qualityUsedByAtlas);
195
- this.#shadowMap.atlasSettings = calculateShadowMapAtlasSettings(clamped);
196
- this.#shadowMap.atlasSettings.actualTextureSize = calculateShadowMapAtlasSettings(this.#shadowMap.qualityUsedByAtlas).size;
206
+ this._nextFrameShadowQuality |= 1 << shadowQuality;
207
+ const clamped = Math.min(shadowQuality, this._shadowMap.qualityUsedByAtlas);
208
+ this._shadowMap.atlasSettings = calculateShadowMapAtlasSettings(clamped);
209
+ this._shadowMap.atlasSettings.actualTextureSize = calculateShadowMapAtlasSettings(this._shadowMap.qualityUsedByAtlas).size;
197
210
  }
198
211
 
199
212
  /** Carbon GetShadowMapAtlasSettings (cpp:708-711). */
200
213
  GetShadowMapAtlasSettings() {
201
- return this.#shadowMap.atlasSettings;
214
+ return this._shadowMap.atlasSettings;
202
215
  }
203
216
 
204
217
  /**
@@ -210,7 +223,7 @@ new class extends _identity {
210
223
  * @returns {object} out
211
224
  */
212
225
  GetUnpackedShadowMapData(record, out = {}) {
213
- const shift = this.#shadowMap.atlasSettings.entryMinSizeLog2;
226
+ const shift = this._shadowMap.atlasSettings.entryMinSizeLog2;
214
227
  out.shadowMapScale = (record.shadowMapScale ?? 0) << shift;
215
228
  out.shadowMapOffsetX = (record.shadowMapOffsetX ?? 0) << shift;
216
229
  out.shadowMapOffsetY = (record.shadowMapOffsetY ?? 0) << shift;
@@ -218,24 +231,39 @@ new class extends _identity {
218
231
  }
219
232
 
220
233
  /**
221
- * Carbon Clear (cpp:220-242), the CPU half: drops the frame's records and
222
- * selections. The index-buffer UAV clear (cpp:224) is device work the AL
223
- * performs when it consumes the frame.
234
+ * Carbon Clear (cpp:220-242): re-points the light-buffer globals, clears
235
+ * the light indices, drops the frame's records and selections, and resets
236
+ * the atlas to one empty root node.
237
+ *
238
+ * Without a render context (a CPU-only caller) the index clear is skipped;
239
+ * there is no buffer to clear then.
224
240
  */
225
- Clear(_renderContext = null) {
226
- this.#records.length = 0;
227
- this.#volumetricLights.length = 0;
228
- this.#shadowCastingLights.length = 0;
241
+ Clear(renderContext = null) {
242
+ this.SetVariableStore();
243
+ if (renderContext) this.ClearLightIndices(renderContext);
244
+ this._records.length = 0;
245
+ this._volumetricLights.length = 0;
246
+ this._shadowCastingLights.length = 0;
247
+ const size = this._shadowMap.atlasSettings.size;
248
+ this._shadowMap.atlasNodes.length = 0;
249
+ this._shadowMap.atlasNodes.push({
250
+ children: [-1, -1],
251
+ lightIndex: -1,
252
+ x: 0,
253
+ y: 0,
254
+ width: size,
255
+ height: size
256
+ });
229
257
  }
230
258
 
231
259
  /** Carbon SetFrustum (cpp:244-247). */
232
260
  SetFrustum(frustum) {
233
- this.#frustum = frustum ?? null;
261
+ this._frustum = frustum ?? null;
234
262
  }
235
263
 
236
264
  /** Carbon AdjustLightCutoff (cpp:249-252): the cull threshold is 7px * lodFactor. */
237
265
  AdjustLightCutoff(lodFactor) {
238
- this.#adjustedCutoff = CUTOFF_PIXEL_SIZE * (Number(lodFactor) || 0);
266
+ this._adjustedCutoff = CUTOFF_PIXEL_SIZE * (Number(lodFactor) || 0);
239
267
  }
240
268
 
241
269
  /**
@@ -250,10 +278,10 @@ new class extends _identity {
250
278
  if (!_Tr2LightManager.areLightFlagsValid(flags)) return;
251
279
  const brightness = Math.max(color[0], color[1], color[2]);
252
280
  if (!(brightness > 0)) return;
253
- const dimming = this.#CullAndDim(position, radius);
281
+ const dimming = this._CullAndDim(position, radius);
254
282
  if (dimming <= 0) return;
255
283
  const scale = radius * dimming;
256
- this.#records.push({
284
+ this._records.push({
257
285
  owner: null,
258
286
  lightData: null,
259
287
  lightProfile: null,
@@ -282,19 +310,19 @@ new class extends _identity {
282
310
  if (!_Tr2LightManager.areLightFlagsValid(record.flags)) return;
283
311
  const brightness = Math.max(record.color[0], record.color[1], record.color[2]);
284
312
  if (!(brightness > 0) || !(record.radius > 0)) return;
285
- const dimming = this.#CullAndDim(record.position, record.radius);
313
+ const dimming = this._CullAndDim(record.position, record.radius);
286
314
  if (dimming <= 0) return;
287
315
 
288
316
  // Carbon's conditional strip (cpp:359-365): shadows survive only when
289
317
  // the setting is on, the scene quality is not DISABLED, and a shadow-map
290
318
  // quality has an atlas actually sized for it (qualityUsedByAtlas lags a
291
319
  // frame behind). Always taken under the shipping default.
292
- const usingShadowMap = this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_LOW || this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_HIGH;
293
- if (this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_DISABLED || usingShadowMap && this.#shadowMap.qualityUsedByAtlas === ShadowQuality.SHADOW_DISABLED || !_Tr2LightManager.useDynamicLightsShadows) {
320
+ const usingShadowMap = this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_LOW || this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_HIGH;
321
+ if (this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_DISABLED || usingShadowMap && this._shadowMap.qualityUsedByAtlas === ShadowQuality.SHADOW_DISABLED || !_Tr2LightManager.useDynamicLightsShadows) {
294
322
  record.flags &= ~_Tr2LightManager.Flags.CASTS_SHADOWS;
295
323
  }
296
324
  const scale = record.radius * dimming;
297
- this.#records.push({
325
+ this._records.push({
298
326
  owner: record.owner ?? null,
299
327
  lightData: record.lightData ?? null,
300
328
  lightProfile: record.lightProfile ?? null,
@@ -322,20 +350,20 @@ new class extends _identity {
322
350
  */
323
351
  ResolveLightData() {
324
352
  const volumetric = [];
325
- for (let i = 0; i < this.#records.length; i++) {
326
- if (this.#records[i].flags & _Tr2LightManager.Flags.IS_VOLUMETRIC) {
327
- const size = this.#ScreenSize(this.#records[i]);
353
+ for (let i = 0; i < this._records.length; i++) {
354
+ if (this._records[i].flags & _Tr2LightManager.Flags.IS_VOLUMETRIC) {
355
+ const size = this._ScreenSize(this._records[i]);
328
356
  volumetric.push([i, Math.min(size, INFINITE_SIZE_CLAMP)]);
329
357
  }
330
358
  }
331
359
  volumetric.sort((a, b) => b[1] - a[1]);
332
- this.#volumetricLights.length = 0;
360
+ this._volumetricLights.length = 0;
333
361
  for (let i = 0; i < volumetric.length; i++) {
334
- if (i < MAX_NUM_VOLUMETRIC_LIGHTS) this.#volumetricLights.push(volumetric[i][0]);else this.#records[volumetric[i][0]].flags &= ~_Tr2LightManager.Flags.IS_VOLUMETRIC;
362
+ if (i < MAX_NUM_VOLUMETRIC_LIGHTS) this._volumetricLights.push(volumetric[i][0]);else this._records[volumetric[i][0]].flags &= ~_Tr2LightManager.Flags.IS_VOLUMETRIC;
335
363
  }
336
- this.#ResolveShadowCasters();
337
- this.#Pack();
338
- this.#revision += 1;
364
+ this._ResolveShadowCasters();
365
+ this._Pack();
366
+ this._revision += 1;
339
367
  }
340
368
 
341
369
  /**
@@ -348,15 +376,15 @@ new class extends _identity {
348
376
  * strip FLAG_CASTS_SHADOWS from the losers, assign raytracing masks by
349
377
  * rank under SHADOW_RAYTRACED, and pack the atlas for LOW/HIGH.
350
378
  */
351
- #ResolveShadowCasters() {
352
- this.#shadowCastingLights.length = 0;
353
- if (!_Tr2LightManager.useDynamicLightsShadows || this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_DISABLED) {
379
+ _ResolveShadowCasters() {
380
+ this._shadowCastingLights.length = 0;
381
+ if (!_Tr2LightManager.useDynamicLightsShadows || this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_DISABLED) {
354
382
  return;
355
383
  }
356
384
  const lightTuples = [];
357
- for (let i = 0; i < this.#records.length; i++) {
358
- if ((this.#records[i].flags & _Tr2LightManager.Flags.CASTS_SHADOWS) !== 0) {
359
- let sizeAcross = this.#frustum ? this.#frustum.GetPixelSizeAccrossEst(this.#records[i].position, this.#records[i].radius) : 0;
385
+ for (let i = 0; i < this._records.length; i++) {
386
+ if ((this._records[i].flags & _Tr2LightManager.Flags.CASTS_SHADOWS) !== 0) {
387
+ let sizeAcross = this._frustum ? this._frustum.GetPixelSizeAccrossEst(this._records[i].position, this._records[i].radius) : 0;
360
388
  if (!Number.isFinite(sizeAcross)) sizeAcross = 1 << HIGH_QUALITY_ATLAS_SIZE_LOG2;
361
389
  lightTuples.push({
362
390
  lightIndex: i,
@@ -365,24 +393,24 @@ new class extends _identity {
365
393
  }
366
394
  }
367
395
  lightTuples.sort((a, b) => b.sizeAcross - a.sizeAcross);
368
- const raytraced = this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_RAYTRACED;
396
+ const raytraced = this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_RAYTRACED;
369
397
  const numShadowCastingLights = Math.min(MAX_NUM_SHADOWCASTING_LIGHTS, lightTuples.length);
370
398
  let i = 0;
371
399
  for (; i < numShadowCastingLights; i++) {
372
- this.#shadowCastingLights.push(lightTuples[i].lightIndex);
400
+ this._shadowCastingLights.push(lightTuples[i].lightIndex);
373
401
  if (raytraced) {
374
- this.#records[lightTuples[i].lightIndex].raytracingShadowMask = 1 << i;
402
+ this._records[lightTuples[i].lightIndex].raytracingShadowMask = 1 << i;
375
403
  }
376
404
  }
377
405
  for (; i < lightTuples.length; i++) {
378
- const record = this.#records[lightTuples[i].lightIndex];
406
+ const record = this._records[lightTuples[i].lightIndex];
379
407
  record.flags &= ~_Tr2LightManager.Flags.CASTS_SHADOWS;
380
408
  if (raytraced) {
381
409
  record.raytracingShadowMask = 0;
382
410
  }
383
411
  }
384
- if (this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_LOW || this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_HIGH) {
385
- this.#CreateShadowMapAtlas(numShadowCastingLights, lightTuples);
412
+ if (this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_LOW || this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_HIGH) {
413
+ this._CreateShadowMapAtlas(numShadowCastingLights, lightTuples);
386
414
  }
387
415
  }
388
416
 
@@ -395,8 +423,8 @@ new class extends _identity {
395
423
  * scale in entry-min-size units into the record's 10-bit fields; failures
396
424
  * zero them.
397
425
  */
398
- #CreateShadowMapAtlas(numShadowCastingLights, lightTuples) {
399
- const settings = this.#shadowMap.atlasSettings;
426
+ _CreateShadowMapAtlas(numShadowCastingLights, lightTuples) {
427
+ const settings = this._shadowMap.atlasSettings;
400
428
  const entry = {
401
429
  x: 0,
402
430
  y: 0
@@ -406,8 +434,8 @@ new class extends _identity {
406
434
  everythingFit = true;
407
435
  const entryInverseScaleFactorLog2 = settings.entryInverseScaleFactorLog2 + j;
408
436
  const entryMaxSize = settings.entryMaxSize >> j;
409
- this.#shadowMap.atlasNodes.length = 0;
410
- this.#shadowMap.atlasNodes.push({
437
+ this._shadowMap.atlasNodes.length = 0;
438
+ this._shadowMap.atlasNodes.push({
411
439
  children: [-1, -1],
412
440
  lightIndex: -1,
413
441
  x: 0,
@@ -417,7 +445,7 @@ new class extends _identity {
417
445
  });
418
446
  for (let i = 0; i < numShadowCastingLights; i++) {
419
447
  const lightIndex = lightTuples[i].lightIndex;
420
- const record = this.#records[lightIndex];
448
+ const record = this._records[lightIndex];
421
449
  let size = lightTuples[i].sizeAcross >>> 0 >>> entryInverseScaleFactorLog2;
422
450
  size = Math.min(Math.max(size, settings.entryMinSize), entryMaxSize);
423
451
  size = align(size, settings.entryMinSize);
@@ -431,7 +459,7 @@ new class extends _identity {
431
459
  width = size;
432
460
  height = size;
433
461
  }
434
- if (this.#GetShadowMapAtlasEntry(lightIndex, width, height, entry)) {
462
+ if (this._GetShadowMapAtlasEntry(lightIndex, width, height, entry)) {
435
463
  record.shadowMapOffsetX = entry.x >>> settings.entryMinSizeLog2;
436
464
  record.shadowMapOffsetY = entry.y >>> settings.entryMinSizeLog2;
437
465
  record.shadowMapScale = size >>> settings.entryMinSizeLog2;
@@ -451,14 +479,14 @@ new class extends _identity {
451
479
  * success - the out object is untouched on failure, exactly as Carbon
452
480
  * leaves its out-references.
453
481
  */
454
- #GetShadowMapAtlasEntry(lightIndex, width, height, out) {
455
- const settings = this.#shadowMap.atlasSettings;
482
+ _GetShadowMapAtlasEntry(lightIndex, width, height, out) {
483
+ const settings = this._shadowMap.atlasSettings;
456
484
  width = align(width, settings.entryMinSize);
457
485
  height = align(height, settings.entryMinSize);
458
- const nodeId = this.#InsertAtlasNode(this.#shadowMap.atlasNodes, 0, lightIndex, width, height);
486
+ const nodeId = this._InsertAtlasNode(this._shadowMap.atlasNodes, 0, lightIndex, width, height);
459
487
  if (nodeId !== -1) {
460
- out.x = this.#shadowMap.atlasNodes[nodeId].x;
461
- out.y = this.#shadowMap.atlasNodes[nodeId].y;
488
+ out.x = this._shadowMap.atlasNodes[nodeId].x;
489
+ out.y = this._shadowMap.atlasNodes[nodeId].y;
462
490
  }
463
491
  return nodeId !== -1;
464
492
  }
@@ -470,14 +498,14 @@ new class extends _identity {
470
498
  * fits, otherwise split along the larger remainder axis and recurse into
471
499
  * the first child.
472
500
  */
473
- #InsertAtlasNode(atlasNodes, nodeId, lightIndex, width, height) {
501
+ _InsertAtlasNode(atlasNodes, nodeId, lightIndex, width, height) {
474
502
  const node = atlasNodes[nodeId];
475
503
  if (node.children[0] !== -1 || node.children[1] !== -1) {
476
- const newNode = this.#InsertAtlasNode(atlasNodes, node.children[0], lightIndex, width, height);
504
+ const newNode = this._InsertAtlasNode(atlasNodes, node.children[0], lightIndex, width, height);
477
505
  if (newNode !== -1) {
478
506
  return newNode;
479
507
  }
480
- return this.#InsertAtlasNode(atlasNodes, node.children[1], lightIndex, width, height);
508
+ return this._InsertAtlasNode(atlasNodes, node.children[1], lightIndex, width, height);
481
509
  }
482
510
  if (node.lightIndex !== -1) {
483
511
  return -1;
@@ -529,27 +557,27 @@ new class extends _identity {
529
557
  child1.width = node.width;
530
558
  child1.height = node.height - height;
531
559
  }
532
- return this.#InsertAtlasNode(atlasNodes, node.children[0], lightIndex, width, height);
560
+ return this._InsertAtlasNode(atlasNodes, node.children[0], lightIndex, width, height);
533
561
  }
534
562
 
535
563
  /** Carbon GetCurrentSpaceSceneShadowQuality (cpp:713-716): a bare field read; every record producer asks it before building. */
536
564
  GetCurrentSpaceSceneShadowQuality() {
537
- return this.#currentSpaceSceneShadowQuality;
565
+ return this._currentSpaceSceneShadowQuality;
538
566
  }
539
567
 
540
568
  /** Carbon GetLightData (cpp:695-698): the frame's resolved records, borrowed. */
541
569
  GetLightData() {
542
- return this.#records;
570
+ return this._records;
543
571
  }
544
572
 
545
573
  /** Carbon GetVolumetricLights (cpp:707-710): indices into GetLightData, borrowed. */
546
574
  GetVolumetricLights() {
547
- return this.#volumetricLights;
575
+ return this._volumetricLights;
548
576
  }
549
577
 
550
578
  /** Carbon GetShadowCastingLights (cpp:688-691): empty under the shipping pin, kept for signature parity. */
551
579
  GetShadowCastingLights() {
552
- return this.#shadowCastingLights;
580
+ return this._shadowCastingLights;
553
581
  }
554
582
 
555
583
  // The frame clock used to be threaded through here, as a non-Carbon seam:
@@ -559,39 +587,296 @@ new class extends _identity {
559
587
  // that here now. Nothing in src ever called the setter, so the five readers
560
588
  // had been sampling a constant zero.
561
589
 
590
+ /** Carbon m_effect's path, computelightlists (EveSpaceScene.cpp:1380). */
591
+ _effectPath = "";
592
+
593
+ /** m_lightBuffer (Tr2GpuStructuredBufferPtr): one PerLightData per light. */
594
+ _lightBuffer = new _Tr2GpuStructuredBuff();
595
+
596
+ /** m_indexBuffer (Tr2GpuStructuredBufferPtr): tile headers, then list nodes. */
597
+ _indexBuffer = new _Tr2GpuStructuredBuff();
598
+
599
+ /** m_lightBufferVariable / m_indexBufferVariable (TriVariableHandle). */
600
+ _lightBufferVariable = null;
601
+ _indexBufferVariable = null;
602
+
603
+ /** The CPU-built index list, reused across frames (see DoUpdateLists). */
604
+ _indexList = new Uint32Array(0);
605
+
606
+ /**
607
+ * Carbon's constructor half that makes this a device resource and
608
+ * publishes the globals (cpp:145-176): LightBuffer and LightIndexBuffer are
609
+ * registered once and pointed at the buffers, and the buffers are made
610
+ * when a device allows it. Carbon also publishes LightProfileArray; the JS
611
+ * texture array is not yet a texture provider, so it is not registered.
612
+ * Profile-less lights, most of them, never sample it.
613
+ *
614
+ * @param {string} effectPath The light-list compute effect.
615
+ * @returns {Tr2LightManager} This manager.
616
+ */
617
+ _InitializeDevice(effectPath) {
618
+ this._effectPath = String(effectPath ?? "");
619
+ const store = _Tr2VariableStore.GlobalStore();
620
+ this._lightBufferVariable = store.RegisterVariable(_Tr2LightManager.LIGHT_BUFFER_NAME, this._lightBuffer);
621
+ this._indexBufferVariable = store.RegisterVariable(_Tr2LightManager.LIGHT_INDEX_BUFFER_NAME, this._indexBuffer);
622
+ _TriDevice.RegisterResource(this);
623
+ this.PrepareResources();
624
+ return this;
625
+ }
626
+
627
+ /** Carbon GetOrCreateInstance (cpp:192-200): the process-wide manager. */
628
+ static getOrCreateInstance(effectPath) {
629
+ _Tr2LightManager._instance ??= new _Tr2LightManager()._InitializeDevice(effectPath);
630
+ return _Tr2LightManager._instance;
631
+ }
632
+
633
+ /** Carbon GetInstance (cpp:202-205): the manager, or null when none exists. */
634
+ static getInstance() {
635
+ return _Tr2LightManager._instance;
636
+ }
637
+
638
+ /**
639
+ * Carbon DeleteInstance (cpp:207-212). The destructor's ResetVariableStore
640
+ * (cpp:174-190) republishes empty buffers, so the globals stop naming this
641
+ * manager's.
642
+ *
643
+ * Adapted: JavaScript has no destructor, so the manager unregisters from
644
+ * the device and resets the variable store here. ResetVariableStore's
645
+ * LightProfileArray and ShadowMapAtlas re-registrations are not made; the
646
+ * manager does not publish either.
647
+ */
648
+ static deleteInstance() {
649
+ const instance = _Tr2LightManager._instance;
650
+ if (!instance) return;
651
+ _Tr2LightManager._instance = null;
652
+ _TriDevice.UnregisterResource(instance);
653
+ const store = _Tr2VariableStore.GlobalStore();
654
+ store.RegisterVariable(_Tr2LightManager.LIGHT_BUFFER_NAME, new _Tr2GpuStructuredBuff());
655
+ store.RegisterVariable(_Tr2LightManager.LIGHT_INDEX_BUFFER_NAME, new _Tr2GpuStructuredBuff());
656
+ }
657
+ /** Carbon SetVariableStore (cpp:214-218): the globals name this manager's buffers. */
658
+ SetVariableStore() {
659
+ this._lightBufferVariable?.SetValue(this._lightBuffer);
660
+ this._indexBufferVariable?.SetValue(this._indexBuffer);
661
+ }
662
+
663
+ /** Carbon Tr2DeviceResource::PrepareResources (Tr2DeviceResource.cpp:21-32). */
664
+ PrepareResources() {
665
+ if (_Tr2Renderer.IsResourceCreationAllowed()) {
666
+ if (!this.OnPrepareResources()) return false;
667
+ }
668
+ return true;
669
+ }
670
+
671
+ /**
672
+ * Carbon OnPrepareResources (cpp:657-675): the light buffer, and the index
673
+ * buffer.
674
+ *
675
+ * The index buffer is sized by UpdateLists to what the CPU list needs, and
676
+ * created CPU-writable; see DoUpdateLists. Carbon makes it 8M elements,
677
+ * GPU-writable, for the compute kernel's allocator. The counter and the
678
+ * per-frame constants belong to that kernel and are not made.
679
+ *
680
+ * Adapted: the light lists are filled on the CPU, not by
681
+ * computelightlists, so the index buffer is CPU-writable and sized per
682
+ * screen.
683
+ */
684
+ OnPrepareResources() {
685
+ const renderContext = Tr2RenderContext_GetMainThreadRenderContext();
686
+ const {
687
+ CPU_WRITABLE
688
+ } = _Tr2GpuStructuredBuff.CreationFlag;
689
+ this._lightBuffer.Create(LIGHT_BUFFER_SIZE, BYTES_PER_LIGHT, CPU_WRITABLE, renderContext);
690
+ this._lightBuffer.SetName("Light buffer");
691
+ this._indexBuffer.SetName("Light indices");
692
+ this.ClearLightIndices(renderContext);
693
+ return true;
694
+ }
695
+
696
+ /** Carbon ReleaseResources (cpp:641-655): nothing of the light lists is released. */
697
+ ReleaseResources(_storage) {}
698
+
699
+ /**
700
+ * Carbon ClearLightIndices (cpp:370-378): zero indices, so every tile head
701
+ * reads "no lights".
702
+ *
703
+ * Adapted: Carbon clears the whole buffer as a UAV; the CPU-written buffer
704
+ * is zeroed through a map.
705
+ *
706
+ * @param {Tr2RenderContext} renderContext The context to write through.
707
+ * @returns {boolean} Whether the clear was written.
708
+ */
709
+ ClearLightIndices(renderContext) {
710
+ const buffer = this._indexBuffer.GetGpuBuffer(0);
711
+ if (!buffer) return true;
712
+ this._indexList.fill(0);
713
+ return _Tr2LightManager._WriteMapped(buffer, null, renderContext);
714
+ }
715
+
716
+ /**
717
+ * Carbon UpdateLightBuffer (cpp:380-399): grows the light buffer to the
718
+ * frame's light count, then uploads the packed records.
719
+ *
720
+ * @param {Tr2RenderContext} renderContext The context to write through.
721
+ * @returns {boolean} Whether the upload succeeded.
722
+ */
723
+ UpdateLightBuffer(renderContext) {
724
+ const count = this._packedCount;
725
+ if (this._lightBuffer.GetCount() < count) {
726
+ const grown = Math.max(this._lightBuffer.GetCount() + 1024, count);
727
+ this._lightBuffer.Create(grown, BYTES_PER_LIGHT, _Tr2GpuStructuredBuff.CreationFlag.CPU_WRITABLE, renderContext);
728
+ this._lightBuffer.SetName("Light buffer");
729
+ }
730
+ const buffer = this._lightBuffer.GetGpuBuffer(0);
731
+ if (!buffer) return false;
732
+ // cpp:393-396: a WRITE_OFTEN buffer is written through a map.
733
+ return _Tr2LightManager._WriteMapped(buffer, this.GetLightBufferData(), renderContext);
734
+ }
735
+
736
+ /**
737
+ * Carbon UpdateLists (cpp:622-639): the globals name the buffers, then the
738
+ * lists are built - or cleared when there are no lights or the build fails.
739
+ *
740
+ * @param {object} depthMap The scene depth, the render target's size.
741
+ * @param {Tr2RenderContext} renderContext The frame's context.
742
+ * @returns {boolean} Whether the lists were built.
743
+ */
744
+ UpdateLists(depthMap, renderContext) {
745
+ this.SetVariableStore();
746
+ if (this._packedCount === 0) {
747
+ this.ClearLightIndices(renderContext);
748
+ return true;
749
+ }
750
+ const built = this.DoUpdateLists(depthMap, renderContext);
751
+ if (!built) this.ClearLightIndices(renderContext);
752
+ return built;
753
+ }
754
+
755
+ /**
756
+ * Carbon DoUpdateLists (cpp:401-440) builds each 16x16 tile's light lists
757
+ * on the GPU with computelightlists. Here they are built on the CPU, in
758
+ * the kernel's own layout (decoded from build 3498825): three header words
759
+ * per tile - list0 head, list1 head, particle-list head - then
760
+ * [lightIndex, next] nodes, 0-based light indices, 0 ending a list.
761
+ *
762
+ * Every tile's heads point at one shared chain instead of a tile-culled
763
+ * one. The hull shaders test each light per pixel (dist < radius and
764
+ * FLAG_AFFECTS_SURFACES), so the picture is the same; only the tile cull's
765
+ * saving is lost. The particle list holds the lights flagged
766
+ * AFFECTS_PARTICLES.
767
+ *
768
+ * The tile count follows the render target, as the kernel's does; the
769
+ * depth map is that size.
770
+ *
771
+ * Adapted: computelightlists does not translate to WebGPU yet (its UAV
772
+ * shapes), so the CPU builds the lists.
773
+ *
774
+ * @param {object} depthMap The scene depth.
775
+ * @param {Tr2RenderContext} renderContext The frame's context.
776
+ * @returns {boolean} Whether the lists were written.
777
+ */
778
+ DoUpdateLists(depthMap, renderContext) {
779
+ if (!this.UpdateLightBuffer(renderContext)) return false;
780
+ const width = depthMap ? depthMap.GetWidth() : 0;
781
+ const height = depthMap ? depthMap.GetHeight() : 0;
782
+ if (!width || !height) return false;
783
+ const tiles = Math.ceil(width / TILE_WIDTH) * Math.ceil(height / TILE_HEIGHT);
784
+ const headerWords = tiles * HEADER_WORDS_PER_TILE;
785
+ const count = this._packedCount;
786
+ let particleCount = 0;
787
+ for (let index = 0; index < count; index++) {
788
+ if (this._LightFlags(index) & _Tr2LightManager.Flags.AFFECTS_PARTICLES) particleCount++;
789
+ }
790
+ const words = headerWords + 2 * (count + particleCount);
791
+ if (this._indexList.length < words) this._indexList = new Uint32Array(words);
792
+ if (this._indexBuffer.GetCount() < words) {
793
+ this._indexBuffer.Create(words, 4, _Tr2GpuStructuredBuff.CreationFlag.CPU_WRITABLE, renderContext);
794
+ this._indexBuffer.SetName("Light indices");
795
+ this.SetVariableStore();
796
+ }
797
+ const list = this._indexList;
798
+ list.fill(0);
799
+
800
+ // The shared chain of every light, then the chain of particle lights.
801
+ let node = headerWords;
802
+ const surfaceHead = node;
803
+ for (let index = 0; index < count; index++, node += 2) {
804
+ list[node] = index;
805
+ list[node + 1] = index + 1 < count ? node + 2 : 0;
806
+ }
807
+ const particleHead = particleCount ? node : 0;
808
+ for (let index = 0, seen = 0; index < count; index++) {
809
+ if (!(this._LightFlags(index) & _Tr2LightManager.Flags.AFFECTS_PARTICLES)) continue;
810
+ seen++;
811
+ list[node] = index;
812
+ list[node + 1] = seen < particleCount ? node + 2 : 0;
813
+ node += 2;
814
+ }
815
+ for (let tile = 0; tile < tiles; tile++) {
816
+ const header = tile * HEADER_WORDS_PER_TILE;
817
+ list[header] = surfaceHead;
818
+ list[header + 1] = surfaceHead;
819
+ list[header + 2] = particleHead;
820
+ }
821
+ const buffer = this._indexBuffer.GetGpuBuffer(0);
822
+ if (!buffer) return false;
823
+ return _Tr2LightManager._WriteMapped(buffer, list.subarray(0, words), renderContext);
824
+ }
825
+
826
+ /**
827
+ * MapForWriting, copy, UnmapForWriting - Carbon's upload for a
828
+ * CPU_WRITABLE (WRITE_OFTEN) buffer, which UpdateBuffer refuses. A null
829
+ * source zeroes the mapping.
830
+ */
831
+ static _WriteMapped(buffer, source, renderContext) {
832
+ const {
833
+ result,
834
+ data
835
+ } = buffer.MapForWriting(renderContext);
836
+ if (!Succeeded(result)) return false;
837
+ if (source) data.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));else data.fill(0);
838
+ buffer.UnmapForWriting(renderContext);
839
+ return true;
840
+ }
841
+
842
+ /** The packed flags of light `index`: the high 16 bits of row 1's w (contract). */
843
+ _LightFlags(index) {
844
+ return this._packedBits[index * FLOATS_PER_LIGHT + 7] >>> 16;
845
+ }
846
+
562
847
  /** The packed PerLightData bytes for the AL to upload, borrowed (contract layout, 3 RGBA32 texels per light). */
563
848
  GetLightBufferData() {
564
- return this.#packed.subarray(0, this.#packedCount * FLOATS_PER_LIGHT);
849
+ return this._packed.subarray(0, this._packedCount * FLOATS_PER_LIGHT);
565
850
  }
566
851
 
567
852
  /** The number of packed lights in GetLightBufferData. */
568
853
  GetLightCount() {
569
- return this.#packedCount;
854
+ return this._packedCount;
570
855
  }
571
856
 
572
857
  /** Monotonic revision of the packed data, bumped by ResolveLightData, so the AL can skip unchanged re-uploads. */
573
858
  GetDataRevision() {
574
- return this.#revision;
859
+ return this._revision;
575
860
  }
576
861
 
577
862
  /** Frustum cull + pixel-size cutoff, returning the fade-band dimming factor (0 = rejected). Cull applies only when a frustum was set. */
578
- #CullAndDim(position, radius) {
579
- if (!this.#frustum) return 1;
580
- if (!this.#frustum.IsSphereVisible(position, radius)) return 0;
581
- const size = this.#frustum.GetPixelSizeAccross(position, radius);
582
- if (!(size > this.#adjustedCutoff)) return 0;
863
+ _CullAndDim(position, radius) {
864
+ if (!this._frustum) return 1;
865
+ if (!this._frustum.IsSphereVisible(position, radius)) return 0;
866
+ const size = this._frustum.GetPixelSizeAccross(position, radius);
867
+ if (!(size > this._adjustedCutoff)) return 0;
583
868
  // Contract §"Colour carries the radius": the fade band sits ABOVE the
584
869
  // cutoff - absent at the cutoff, full brightness FADE_SIZE above it.
585
- return Math.min((size - this.#adjustedCutoff) / FADE_SIZE, 1);
870
+ return Math.min((size - this._adjustedCutoff) / FADE_SIZE, 1);
586
871
  }
587
872
 
588
873
  /**
589
874
  * Returns the light's projected pixel size, or its radius when no frustum is
590
875
  * set.
591
876
  */
592
- #ScreenSize(record) {
593
- if (!this.#frustum) return record.radius;
594
- return this.#frustum.GetPixelSizeAccross(record.position, record.radius);
877
+ _ScreenSize(record) {
878
+ if (!this._frustum) return record.radius;
879
+ return this._frustum.GetPixelSizeAccross(record.position, record.radius);
595
880
  }
596
881
 
597
882
  /**
@@ -611,7 +896,7 @@ new class extends _identity {
611
896
  * The first-sight map remains only for foreign profile objects that
612
897
  * expose no GetTextureIndex.
613
898
  */
614
- #ProfileSlot(profile) {
899
+ _ProfileSlot(profile) {
615
900
  if (!profile) return 0;
616
901
  if (typeof profile.GetTextureIndex === "function") {
617
902
  let index = profile.GetTextureIndex();
@@ -628,10 +913,10 @@ new class extends _identity {
628
913
  }
629
914
  return index >= 0 ? index + 1 : 0;
630
915
  }
631
- let slot = this.#profileSlots.get(profile);
916
+ let slot = this._profileSlots.get(profile);
632
917
  if (slot === undefined) {
633
- slot = this.#profileSlots.size;
634
- this.#profileSlots.set(profile, slot);
918
+ slot = this._profileSlots.size;
919
+ this._profileSlots.set(profile, slot);
635
920
  }
636
921
  return slot + 1;
637
922
  }
@@ -650,44 +935,44 @@ new class extends _identity {
650
935
  * @returns {Tr2TextureArray} The shared profile array.
651
936
  */
652
937
  static getLightProfileArray() {
653
- if (!_Tr2LightManager.#lightProfileArray) {
654
- _Tr2LightManager.#lightProfileArray = new _Tr2TextureArray();
938
+ if (!_Tr2LightManager._lightProfileArray) {
939
+ _Tr2LightManager._lightProfileArray = new _Tr2TextureArray();
655
940
  }
656
- return _Tr2LightManager.#lightProfileArray;
941
+ return _Tr2LightManager._lightProfileArray;
657
942
  }
658
- /** Packs #records into the 48-byte-per-light buffer per the contract layout. */
659
- #Pack() {
660
- const count = Math.min(this.#records.length, LIGHT_BUFFER_SIZE);
943
+ /** Packs _records into the 48-byte-per-light buffer per the contract layout. */
944
+ _Pack() {
945
+ const count = Math.min(this._records.length, LIGHT_BUFFER_SIZE);
661
946
  for (let i = 0; i < count; i++) {
662
- const record = this.#records[i];
947
+ const record = this._records[i];
663
948
  const f = i * FLOATS_PER_LIGHT;
664
949
 
665
950
  // Texel 0: position + radius, four f32.
666
- this.#packed[f] = record.position[0];
667
- this.#packed[f + 1] = record.position[1];
668
- this.#packed[f + 2] = record.position[2];
669
- this.#packed[f + 3] = record.radius;
951
+ this._packed[f] = record.position[0];
952
+ this._packed[f + 1] = record.position[1];
953
+ this._packed[f + 2] = record.position[2];
954
+ this._packed[f + 3] = record.radius;
670
955
 
671
956
  // Texel 1: colour (premultiplied at Add time) + the packed word -
672
957
  // innerRadius f16 low, flags u16 high with the biased profile slot in
673
958
  // bits 4-15 (contract §"The packed flag word").
674
- this.#packed[f + 4] = record.color[0];
675
- this.#packed[f + 5] = record.color[1];
676
- this.#packed[f + 6] = record.color[2];
677
- const flagsWord = record.flags & 0xF | this.#ProfileSlot(record.lightProfile) << 4;
678
- this.#packedBits[f + 7] = toHalf(record.innerRadius) | (flagsWord & 0xFFFF) << 16;
959
+ this._packed[f + 4] = record.color[0];
960
+ this._packed[f + 5] = record.color[1];
961
+ this._packed[f + 6] = record.color[2];
962
+ const flagsWord = record.flags & 0xF | this._ProfileSlot(record.lightProfile) << 4;
963
+ this._packedBits[f + 7] = toHalf(record.innerRadius) | (flagsWord & 0xFFFF) << 16;
679
964
 
680
965
  // Texel 2: direction as three f16, projectionPlaneDistance, the two
681
966
  // angles, then the shadow union (h:70-84, MSVC low-bit-first layout:
682
967
  // bits 0-1 padding, 2-11 scale, 12-21 offsetX, 22-31 offsetY; the
683
968
  // raytraced mask shares the word - the paths are exclusive by
684
969
  // quality). All zeros under the shipping pin, exactly as before.
685
- this.#packedBits[f + 8] = toHalf(record.direction[0]) | toHalf(record.direction[1]) << 16;
686
- this.#packedBits[f + 9] = toHalf(record.direction[2]) | toHalf(record.projectionPlaneDistance) << 16;
687
- this.#packedBits[f + 10] = toHalf(record.outerAngle) | toHalf(record.innerAngle) << 16;
688
- this.#packedBits[f + 11] = this.#currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_RAYTRACED ? (record.raytracingShadowMask ?? 0) & 0xFFFF : ((record.shadowMapScale ?? 0) & 0x3FF) << 2 | ((record.shadowMapOffsetX ?? 0) & 0x3FF) << 12 | ((record.shadowMapOffsetY ?? 0) & 0x3FF) << 22;
970
+ this._packedBits[f + 8] = toHalf(record.direction[0]) | toHalf(record.direction[1]) << 16;
971
+ this._packedBits[f + 9] = toHalf(record.direction[2]) | toHalf(record.projectionPlaneDistance) << 16;
972
+ this._packedBits[f + 10] = toHalf(record.outerAngle) | toHalf(record.innerAngle) << 16;
973
+ this._packedBits[f + 11] = this._currentSpaceSceneShadowQuality === ShadowQuality.SHADOW_RAYTRACED ? (record.raytracingShadowMask ?? 0) & 0xFFFF : ((record.shadowMapScale ?? 0) & 0x3FF) << 2 | ((record.shadowMapOffsetX ?? 0) & 0x3FF) << 12 | ((record.shadowMapOffsetY ?? 0) & 0x3FF) << 22;
689
974
  }
690
- this.#packedCount = count;
975
+ this._packedCount = count;
691
976
  }
692
977
  }];
693
978
  Flags = Object.freeze({
@@ -701,7 +986,8 @@ new class extends _identity {
701
986
  LIGHT_INDEX_BUFFER_NAME = "LightIndexBuffer";
702
987
  useDynamicLightsShadows = false;
703
988
  calculateShadowMapAtlasSettings = calculateShadowMapAtlasSettings;
704
- #lightProfileArray = null;
989
+ _instance = null;
990
+ _lightProfileArray = null;
705
991
  constructor() {
706
992
  super(_Tr2LightManager), _initClass();
707
993
  }