@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.
- package/dist/global/consts/effectPaths.js +4 -1
- package/dist/global/consts/effectPaths.js.map +1 -1
- package/dist/global/consts/index.js +1 -1
- package/dist/global/index.js +1 -1
- package/dist/index.js +3 -3
- package/dist/resource/format/carbonEffect/carbonEffectBackendBlock.js +19 -2
- package/dist/resource/format/carbonEffect/carbonEffectBackendBlock.js.map +1 -1
- package/dist/resource/format/index.js +1 -1
- package/dist/resource/formats/hlsl/core/carbonTypedViews.js +121 -65
- package/dist/resource/formats/hlsl/core/carbonTypedViews.js.map +1 -1
- package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js +10 -7
- package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js.map +1 -1
- package/dist/resource/formats/webgpu/core/effectBackendBodySet.js +3 -1
- package/dist/resource/formats/webgpu/core/effectBackendBodySet.js.map +1 -1
- package/dist/resource/formats/webgpu/core/packageEffect.js +5 -0
- package/dist/resource/formats/webgpu/core/packageEffect.js.map +1 -1
- package/dist/resource/formats/webgpu/core/wgsl/emitWgsl.js +20 -0
- package/dist/resource/formats/webgpu/core/wgsl/emitWgsl.js.map +1 -1
- package/dist/resource/formats/webgpu/core/wgsl/emulatedAddressing.js +93 -4
- package/dist/resource/formats/webgpu/core/wgsl/emulatedAddressing.js.map +1 -1
- package/dist/resource/formats/webgpu/core/wgsl/lowerBindingLayout.js +74 -64
- package/dist/resource/formats/webgpu/core/wgsl/lowerBindingLayout.js.map +1 -1
- package/dist/resource/formats/webgpu/core/wgsl/lowerFragmentProgram.js +16 -7
- package/dist/resource/formats/webgpu/core/wgsl/lowerFragmentProgram.js.map +1 -1
- package/dist/resource/formats/webgpu/core/wgsl/wgslTypedViews.js +69 -27
- package/dist/resource/formats/webgpu/core/wgsl/wgslTypedViews.js.map +1 -1
- package/dist/resource/geometry/registerGeometryResources.js +10 -1
- package/dist/resource/geometry/registerGeometryResources.js.map +1 -1
- package/dist/sof/EveSOFDNA.js +4 -2
- package/dist/sof/EveSOFDNA.js.map +1 -1
- package/dist/sof/EveSOFDataMgr.js +83 -69
- package/dist/sof/EveSOFDataMgr.js.map +1 -1
- package/dist/trinity/core/Tr2Denoiser.js +6 -1
- package/dist/trinity/core/Tr2Denoiser.js.map +1 -1
- package/dist/trinity/core/Tr2ShadowMap.js +7 -2
- package/dist/trinity/core/Tr2ShadowMap.js.map +1 -1
- package/dist/trinity/core/context/Tr2RenderContext.js +43 -0
- package/dist/trinity/core/context/Tr2RenderContext.js.map +1 -1
- package/dist/trinity/core/device/Tr2GpuStructuredBuffer.js +134 -0
- package/dist/trinity/core/device/Tr2GpuStructuredBuffer.js.map +1 -0
- package/dist/trinity/core/index.js +1 -0
- package/dist/trinity/core/index.js.map +1 -1
- package/dist/trinity/core/lighting/Tr2LightManager.js +410 -124
- package/dist/trinity/core/lighting/Tr2LightManager.js.map +1 -1
- package/dist/trinity/eve/effect/lensflare/EveLensflare.js +303 -312
- package/dist/trinity/eve/effect/lensflare/EveLensflare.js.map +1 -1
- package/dist/trinity/eve/effect/lensflare/EveOccluder.js +117 -0
- package/dist/trinity/eve/effect/lensflare/EveOccluder.js.map +1 -0
- package/dist/trinity/eve/index.js +1 -0
- package/dist/trinity/eve/index.js.map +1 -1
- package/dist/trinity/eve/scene/EveSpaceScene.js +286 -6
- package/dist/trinity/eve/scene/EveSpaceScene.js.map +1 -1
- package/dist/trinity/eve/scene/EveSpaceSceneRenderDriver.js +212 -32
- package/dist/trinity/eve/scene/EveSpaceSceneRenderDriver.js.map +1 -1
- package/dist/trinity/generated/eve/effect/index.js +0 -1
- package/dist/trinity/generated/eve/effect/index.js.map +1 -1
- package/dist/trinity/generated/eve/index.js +0 -1
- package/dist/trinity/generated/eve/index.js.map +1 -1
- package/dist/trinity/generated/index.js +0 -2
- package/dist/trinity/generated/index.js.map +1 -1
- package/dist/trinity/generated/trinityCore/index.js +0 -1
- package/dist/trinity/generated/trinityCore/index.js.map +1 -1
- package/dist/trinity/index.js +2 -2
- package/dist/trinity/postProcess/Tr2PostProcessRenderer.js +4 -1
- package/dist/trinity/postProcess/Tr2PostProcessRenderer.js.map +1 -1
- package/dist/trinity/postProcess/Tr2SSAO.js +282 -21
- package/dist/trinity/postProcess/Tr2SSAO.js.map +1 -1
- package/dist/trinity/shader/parameter/Tr2RuntimeTextureParameter.js +71 -53
- package/dist/trinity/shader/parameter/Tr2RuntimeTextureParameter.js.map +1 -1
- package/dist/trinityal/webgpu/CjsWebgpuRenderContextAL.js +81 -18
- package/dist/trinityal/webgpu/CjsWebgpuRenderContextAL.js.map +1 -1
- package/dist/trinityal/webgpu/CjsWebgpuResourceSetAL.js +46 -4
- package/dist/trinityal/webgpu/CjsWebgpuResourceSetAL.js.map +1 -1
- package/dist/trinityal/webgpu/CjsWebgpuShaderAL.js +32 -1
- package/dist/trinityal/webgpu/CjsWebgpuShaderAL.js.map +1 -1
- package/dist/trinityal/webgpu/CjsWebgpuShaderProgramAL.js +27 -0
- package/dist/trinityal/webgpu/CjsWebgpuShaderProgramAL.js.map +1 -1
- package/dist/trinityal/webgpu/CjsWebgpuTextureAL.js +29 -4
- package/dist/trinityal/webgpu/CjsWebgpuTextureAL.js.map +1 -1
- package/dist/trinityal/webgpu/core/CjsWebgpuPsoDescription.js +32 -2
- package/dist/trinityal/webgpu/core/CjsWebgpuPsoDescription.js.map +1 -1
- package/dist/trinityal/webgpu/core/CjsWebgpuUtils.js +57 -5
- package/dist/trinityal/webgpu/core/CjsWebgpuUtils.js.map +1 -1
- package/dist/trinityal/webgpu/core/CjsWebgpuWorkQueue.js +111 -5
- package/dist/trinityal/webgpu/core/CjsWebgpuWorkQueue.js.map +1 -1
- package/docs/trinity/reference/classes/generated.md +20 -20
- package/docs/trinity/reference/classes/post-process.md +1 -1
- package/package.json +1 -1
- package/dist/trinity/generated/eve/effect/EveOccluder.js +0 -38
- package/dist/trinity/generated/eve/effect/EveOccluder.js.map +0 -1
- package/dist/trinity/generated/trinityCore/Tr2GpuStructuredBuffer.js +0 -48
- 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.
|
|
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
|
-
|
|
110
|
+
_records = (_initProto(this), []);
|
|
98
111
|
|
|
99
|
-
// Carbon m_volumetricLights / m_shadowCastingLights: indices into
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
119
|
+
_frustum = null;
|
|
107
120
|
|
|
108
121
|
// Carbon m_adjustedCutoff (cpp:249-252).
|
|
109
|
-
|
|
122
|
+
_adjustedCutoff = CUTOFF_PIXEL_SIZE;
|
|
110
123
|
|
|
111
124
|
// Carbon m_currentSpaceSceneShadowQuality (h:197).
|
|
112
|
-
|
|
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
|
-
|
|
129
|
+
_nextFrameShadowQuality = 0;
|
|
117
130
|
|
|
118
131
|
// Carbon m_currentFrameCounter (h:198; ctor sets -1, cpp:164).
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
|
179
|
-
if (this
|
|
191
|
+
this._currentSpaceSceneShadowQuality = shadowQuality;
|
|
192
|
+
if (this._currentFrameCounter !== frameCounter) {
|
|
180
193
|
if (!_Tr2LightManager.useDynamicLightsShadows) {
|
|
181
|
-
this
|
|
194
|
+
this._nextFrameShadowQuality = 0;
|
|
182
195
|
}
|
|
183
|
-
if (this
|
|
184
|
-
this
|
|
185
|
-
} else if (this
|
|
186
|
-
this
|
|
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
|
|
201
|
+
this._shadowMap.qualityUsedByAtlas = ShadowQuality.SHADOW_DISABLED;
|
|
189
202
|
}
|
|
190
|
-
this
|
|
191
|
-
this
|
|
203
|
+
this._nextFrameShadowQuality = 1 << shadowQuality;
|
|
204
|
+
this._currentFrameCounter = frameCounter;
|
|
192
205
|
}
|
|
193
|
-
this
|
|
194
|
-
const clamped = Math.min(shadowQuality, this
|
|
195
|
-
this
|
|
196
|
-
this
|
|
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
|
|
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
|
|
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)
|
|
222
|
-
*
|
|
223
|
-
*
|
|
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(
|
|
226
|
-
this
|
|
227
|
-
this
|
|
228
|
-
this
|
|
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
|
|
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
|
|
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
|
|
281
|
+
const dimming = this._CullAndDim(position, radius);
|
|
254
282
|
if (dimming <= 0) return;
|
|
255
283
|
const scale = radius * dimming;
|
|
256
|
-
this
|
|
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
|
|
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
|
|
293
|
-
if (this
|
|
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
|
|
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
|
|
326
|
-
if (this
|
|
327
|
-
const size = this
|
|
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
|
|
360
|
+
this._volumetricLights.length = 0;
|
|
333
361
|
for (let i = 0; i < volumetric.length; i++) {
|
|
334
|
-
if (i < MAX_NUM_VOLUMETRIC_LIGHTS) this
|
|
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
|
|
337
|
-
this
|
|
338
|
-
this
|
|
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
|
-
|
|
352
|
-
this
|
|
353
|
-
if (!_Tr2LightManager.useDynamicLightsShadows || this
|
|
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
|
|
358
|
-
if ((this
|
|
359
|
-
let sizeAcross = this
|
|
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
|
|
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
|
|
400
|
+
this._shadowCastingLights.push(lightTuples[i].lightIndex);
|
|
373
401
|
if (raytraced) {
|
|
374
|
-
this
|
|
402
|
+
this._records[lightTuples[i].lightIndex].raytracingShadowMask = 1 << i;
|
|
375
403
|
}
|
|
376
404
|
}
|
|
377
405
|
for (; i < lightTuples.length; i++) {
|
|
378
|
-
const record = this
|
|
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
|
|
385
|
-
this
|
|
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
|
-
|
|
399
|
-
const settings = this
|
|
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
|
|
410
|
-
this
|
|
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
|
|
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
|
|
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
|
-
|
|
455
|
-
const settings = this
|
|
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
|
|
486
|
+
const nodeId = this._InsertAtlasNode(this._shadowMap.atlasNodes, 0, lightIndex, width, height);
|
|
459
487
|
if (nodeId !== -1) {
|
|
460
|
-
out.x = this
|
|
461
|
-
out.y = this
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
579
|
-
if (!this
|
|
580
|
-
if (!this
|
|
581
|
-
const size = this
|
|
582
|
-
if (!(size > this
|
|
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
|
|
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
|
-
|
|
593
|
-
if (!this
|
|
594
|
-
return this
|
|
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
|
-
|
|
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
|
|
916
|
+
let slot = this._profileSlots.get(profile);
|
|
632
917
|
if (slot === undefined) {
|
|
633
|
-
slot = this
|
|
634
|
-
this
|
|
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
|
|
654
|
-
_Tr2LightManager
|
|
938
|
+
if (!_Tr2LightManager._lightProfileArray) {
|
|
939
|
+
_Tr2LightManager._lightProfileArray = new _Tr2TextureArray();
|
|
655
940
|
}
|
|
656
|
-
return _Tr2LightManager
|
|
941
|
+
return _Tr2LightManager._lightProfileArray;
|
|
657
942
|
}
|
|
658
|
-
/** Packs
|
|
659
|
-
|
|
660
|
-
const count = Math.min(this
|
|
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
|
|
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
|
|
667
|
-
this
|
|
668
|
-
this
|
|
669
|
-
this
|
|
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
|
|
675
|
-
this
|
|
676
|
-
this
|
|
677
|
-
const flagsWord = record.flags & 0xF | this
|
|
678
|
-
this
|
|
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
|
|
686
|
-
this
|
|
687
|
-
this
|
|
688
|
-
this
|
|
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
|
|
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
|
-
|
|
989
|
+
_instance = null;
|
|
990
|
+
_lightProfileArray = null;
|
|
705
991
|
constructor() {
|
|
706
992
|
super(_Tr2LightManager), _initClass();
|
|
707
993
|
}
|