react-native-cesium 1.2.7 → 1.2.9
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.
- package/android/CMakeLists.txt +24 -9
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +16 -3
- package/android/src/main/cpp/CesiumBridgeAndroid.h +2 -1
- package/android/src/main/cpp/vulkan/VulkanBackend.cpp +166 -67
- package/android/src/main/cpp/vulkan/VulkanBackend.h +60 -19
- package/android/src/main/cpp/vulkan/shaders/terrain.frag +36 -8
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +26 -7
- package/cpp/engine/CesiumEngine.cpp +487 -34
- package/cpp/engine/CesiumEngine.hpp +73 -4
- package/cpp/engine/GlobeCamera.cpp +0 -1
- package/cpp/renderer/RenderTypes.hpp +13 -0
- package/ios/CesiumBridge.h +14 -3
- package/ios/CesiumBridge.mm +72 -5
- package/ios/HybridCesiumView.swift +30 -13
- package/ios/metal/MetalBackend.h +22 -2
- package/ios/metal/MetalBackend.mm +248 -93
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +11 -3
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/specs/cesium-view.nitro.ts +17 -3
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#include <CesiumAsync/IAssetAccessor.h>
|
|
12
12
|
#include <CesiumUtility/IntrusivePointer.h>
|
|
13
13
|
|
|
14
|
+
#include <chrono>
|
|
14
15
|
#include <memory>
|
|
15
16
|
#include <string>
|
|
16
17
|
|
|
@@ -54,9 +55,18 @@ struct EngineConfig {
|
|
|
54
55
|
bool preloadSiblings = true;
|
|
55
56
|
|
|
56
57
|
// Refuse to render holes in the terrain (Cesium will keep ancestor tiles
|
|
57
|
-
// visible until all children load).
|
|
58
|
-
//
|
|
59
|
-
|
|
58
|
+
// visible until all children load). Cesium Native's default is `false`.
|
|
59
|
+
//
|
|
60
|
+
// We default to `false` (matching Cesium Native) because `true` serialises
|
|
61
|
+
// the load tree on the deepest-child critical path: a parent at LOD N
|
|
62
|
+
// cannot be rendered until every descendant down to the selected LOD has
|
|
63
|
+
// arrived. For a typical Alps view that's 5-6 levels deep × ~4 tiles per
|
|
64
|
+
// level fan-out = a ~30-60 s critical path even with cache hits. Setting
|
|
65
|
+
// this to `false` lets the renderer show the best tile it has *now* and
|
|
66
|
+
// refine progressively — the same UX as CesiumJS / Google Earth.
|
|
67
|
+
// Brief low-LOD pop-ins during fast pans are an acceptable trade for
|
|
68
|
+
// first-frame correctness in seconds rather than tens of seconds.
|
|
69
|
+
bool forbidHoles = false;
|
|
60
70
|
|
|
61
71
|
// Water-mask textures (carries coastline shading data). Cheap to disable on
|
|
62
72
|
// memory-constrained devices.
|
|
@@ -71,12 +81,25 @@ struct EngineConfig {
|
|
|
71
81
|
// Seconds an unused tile remains in memory before being evicted.
|
|
72
82
|
double tileCacheUnloadTimeInSeconds = 5.0;
|
|
73
83
|
|
|
84
|
+
// Soft cap (ms) on per-frame main-thread tile finalisation work. Cesium
|
|
85
|
+
// Native's default of 0.0 means "complete every pending main-thread load
|
|
86
|
+
// every tick", which on a heavy startup burst can stall the render thread
|
|
87
|
+
// for hundreds of ms at a time — the user sees the scene "lock up" while
|
|
88
|
+
// tiles upload. 20 ms keeps the render thread responsive at 30 Hz minimum
|
|
89
|
+
// (16.67 ms = one frame at 60 Hz; we sacrifice the worst-case frame during
|
|
90
|
+
// startup bursts but never go below 30 Hz interactivity). Lower values
|
|
91
|
+
// (5–10 ms) make individual frames smoother but visibly extend total load
|
|
92
|
+
// time because finalisation is throughput-bound: ~5 ms per tile, so a
|
|
93
|
+
// 5 ms cap → only 1 tile finalised per frame → ~60 tiles/sec ceiling
|
|
94
|
+
// regardless of how many CPUs are decoding in parallel.
|
|
95
|
+
double mainThreadLoadingTimeLimitMs = 20.0;
|
|
96
|
+
|
|
74
97
|
// ── Disk cache (SqliteCache) ─────────────────────────────────────────
|
|
75
98
|
int32_t sqliteCacheMaxRows = tunables::kDefaultSqliteCacheMaxRows;
|
|
76
99
|
|
|
77
100
|
// ── Worker pool ──────────────────────────────────────────────────────
|
|
78
101
|
// 0 means "auto-detect" via std::thread::hardware_concurrency() clamped to
|
|
79
|
-
// [2,
|
|
102
|
+
// [2, 16]. Override only for benchmarking / regression testing.
|
|
80
103
|
int32_t taskProcessorThreads = 0;
|
|
81
104
|
|
|
82
105
|
// ── Camera constraints ───────────────────────────────────────────────
|
|
@@ -165,9 +188,55 @@ private:
|
|
|
165
188
|
// frame. Initialised to 0 (sea level) until a terrain tile is first loaded.
|
|
166
189
|
float terrainFloorEllipsoidM_ = 0.0f;
|
|
167
190
|
|
|
191
|
+
// Wall-clock timestamp of the previous updateFrame call. Used to compute
|
|
192
|
+
// deltaTime for Tileset::updateViewGroup, which drives LOD transition fading.
|
|
193
|
+
// Zero-initialised "not yet set" state — first frame passes 0 deltaTime.
|
|
194
|
+
std::chrono::steady_clock::time_point lastFrameTime_{};
|
|
195
|
+
bool hasLastFrameTime_ = false;
|
|
196
|
+
|
|
168
197
|
// Pre-computed fallback ellipsoid geometry (ECEF, absolute).
|
|
169
198
|
std::vector<glm::dvec3> ellipsoidPositions_;
|
|
170
199
|
std::vector<uint32_t> ellipsoidIndices_;
|
|
200
|
+
|
|
201
|
+
// ── frame-signature cache ─────────────────────────────────────────
|
|
202
|
+
// Signature is hashed over the tilesToRenderThisFrame primitive pointers
|
|
203
|
+
// and counts. When the same signature appears for kMaxFramesInFlight
|
|
204
|
+
// consecutive frames *and* no LOD fades are active, we know every backend
|
|
205
|
+
// slot's persistent geometry buffer already holds the right bytes, so we
|
|
206
|
+
// can skip rebuilding the merged CPU arrays entirely. On the fast path we
|
|
207
|
+
// still emit one DrawPrimitive per visible primitive (re-reading overlay /
|
|
208
|
+
// water-mask state from TileGPUResources and recomputing per-tile MVP for
|
|
209
|
+
// camera motion), but the heavy per-vertex memcpys are gone.
|
|
210
|
+
struct CachedDraw {
|
|
211
|
+
const TileGPUResources* res;
|
|
212
|
+
glm::dvec3 rtcCenter;
|
|
213
|
+
uint32_t indexByteOffset;
|
|
214
|
+
uint32_t indexCount;
|
|
215
|
+
bool hasUVs;
|
|
216
|
+
glm::vec4 wmTileBounds;
|
|
217
|
+
};
|
|
218
|
+
std::vector<CachedDraw> cachedDraws_;
|
|
219
|
+
uint64_t lastDrawSignature_ = 0;
|
|
220
|
+
// Consecutive frames where geometrySignature has been the same non-zero
|
|
221
|
+
// value. Once this reaches kMaxFramesInFlight every persistent buffer slot
|
|
222
|
+
// is guaranteed to contain the right bytes — fast path becomes legal.
|
|
223
|
+
int stableSigFrames_ = 0;
|
|
224
|
+
|
|
225
|
+
// ── terrain-floor scan cache ──────────────────────────────────────
|
|
226
|
+
// The minAltitudeAboveTerrain clamp scans every vertex of every tile whose
|
|
227
|
+
// bounding region contains the camera lon/lat and picks the altitude of
|
|
228
|
+
// the vertex with the smallest 3D distance to the camera. That's an O(NV)
|
|
229
|
+
// scan over the visible primitives — dominant CPU cost when the camera
|
|
230
|
+
// sits over a dense LOD chain. Result depends only on the camera lon/lat
|
|
231
|
+
// and the set of visible primitives, so we cache it keyed on
|
|
232
|
+
// (camLon, camLat, geometrySignature). Threshold of ~1e-6 degrees (~10 cm)
|
|
233
|
+
// is far below the inter-vertex spacing of the densest tiles served by
|
|
234
|
+
// Cesium Ion world terrain, so the cached nearest vertex stays correct.
|
|
235
|
+
bool floorCacheValid_ = false;
|
|
236
|
+
double floorCacheCamLonDeg_ = 0.0;
|
|
237
|
+
double floorCacheCamLatDeg_ = 0.0;
|
|
238
|
+
uint64_t floorCacheSignature_ = 0;
|
|
239
|
+
float floorCacheValue_ = 0.0f;
|
|
171
240
|
};
|
|
172
241
|
|
|
173
242
|
} // namespace reactnativecesium
|
|
@@ -71,6 +71,11 @@ struct DrawPrimitive {
|
|
|
71
71
|
// reconstruct the world position as wp = localPos + rtcCenterEcef, which
|
|
72
72
|
// avoids the catastrophic cancellation of eyeRelPos + cameraEcef at altitude.
|
|
73
73
|
glm::vec3 rtcCenterEcef{0.0f};
|
|
74
|
+
|
|
75
|
+
// LOD transition dither threshold from TileRenderContent::getLodTransitionFadePercentage().
|
|
76
|
+
// 1.0 = fully visible (no dither); 0.0 = fully hidden. Both tilesToRenderThisFrame
|
|
77
|
+
// (fading in, 0→1) and tilesFadingOut (fading out, →0) use this field.
|
|
78
|
+
float lodFade = 1.0f;
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
struct FrameResult {
|
|
@@ -101,6 +106,14 @@ struct FrameResult {
|
|
|
101
106
|
bool tilesetActive = false;
|
|
102
107
|
double verticalFovDeg = 60.0;
|
|
103
108
|
std::vector<std::string> creditHtmlLines;
|
|
109
|
+
|
|
110
|
+
// hash over the bytes that get merged into localPositions / altitudes /
|
|
111
|
+
// uvs / indices. Backends compare this against their per-slot cache and
|
|
112
|
+
// skip the host→GPU memcpy when it matches (the buffers already hold the
|
|
113
|
+
// exact same bytes). 0 means "force upload" — set by the engine whenever
|
|
114
|
+
// anything that affects the merged arrays may have changed (active LOD
|
|
115
|
+
// fade, tileset rebuild, overlay swap, ellipsoid fallback path, etc).
|
|
116
|
+
uint64_t geometrySignature = 0;
|
|
104
117
|
};
|
|
105
118
|
|
|
106
119
|
// ── Frame setup ─────────────────────────────────────────────────────────────
|
package/ios/CesiumBridge.h
CHANGED
|
@@ -8,10 +8,21 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
8
8
|
// in shared C++ next to CesiumEngine.
|
|
9
9
|
@interface CesiumBridge : NSObject
|
|
10
10
|
|
|
11
|
+
/// Designated initialiser.
|
|
12
|
+
/// Pass `ionAccessToken` and `ionAssetId` here so the engine can start the
|
|
13
|
+
/// async tileset.json network round-trip immediately, before Metal pipeline
|
|
14
|
+
/// compilation begins — letting the two operations overlap in time.
|
|
15
|
+
/// `sqliteCacheMaxRows` and `taskProcessorThreads` must also be provided here
|
|
16
|
+
/// because they are consumed once when the SqliteCache / TaskProcessor are
|
|
17
|
+
/// constructed inside `buildEngine`; setting them after init has no effect.
|
|
11
18
|
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
width:(int)width
|
|
20
|
+
height:(int)height
|
|
21
|
+
cacheDir:(NSString *)cacheDir
|
|
22
|
+
ionAccessToken:(NSString *)token
|
|
23
|
+
ionAssetId:(int64_t)assetId
|
|
24
|
+
sqliteCacheMaxRows:(int32_t)sqliteRows
|
|
25
|
+
taskProcessorThreads:(int32_t)threads;
|
|
15
26
|
|
|
16
27
|
- (void)updateIonAccessToken:(NSString *)token assetId:(int64_t)assetId;
|
|
17
28
|
- (void)updateImageryAssetId:(int64_t)assetId;
|
package/ios/CesiumBridge.mm
CHANGED
|
@@ -41,13 +41,24 @@
|
|
|
41
41
|
// and recreates the bridge does not pollute the new bridge's first frame.
|
|
42
42
|
double _lastTickSec;
|
|
43
43
|
|
|
44
|
+
// Last time tilesLoading was > 0. Used to keep the render loop at full speed
|
|
45
|
+
// for a grace period after loading finishes so LOD transitions can complete
|
|
46
|
+
// quickly and updateViewGroup can immediately discover the next refinement
|
|
47
|
+
// level — preventing the cascade of 5-second idle gaps between load phases.
|
|
48
|
+
// Sentinel (epoch) means "no loading seen yet".
|
|
49
|
+
std::chrono::steady_clock::time_point _lastLoadingTime;
|
|
50
|
+
|
|
44
51
|
NSString* _cacheDir;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
- (instancetype)initWithMetalLayer:(CAMetalLayer *)layer
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
width:(int)width
|
|
56
|
+
height:(int)height
|
|
57
|
+
cacheDir:(NSString *)cacheDir
|
|
58
|
+
ionAccessToken:(NSString *)token
|
|
59
|
+
ionAssetId:(int64_t)assetId
|
|
60
|
+
sqliteCacheMaxRows:(int32_t)sqliteRows
|
|
61
|
+
taskProcessorThreads:(int32_t)threads {
|
|
51
62
|
self = [super init];
|
|
52
63
|
if (self) {
|
|
53
64
|
_viewportWidth = width;
|
|
@@ -72,10 +83,33 @@
|
|
|
72
83
|
@"ios/cacert.pem (run pod install).");
|
|
73
84
|
}
|
|
74
85
|
|
|
86
|
+
// Seed the ion credentials before buildEngine so createTileset() can
|
|
87
|
+
// immediately start the async tileset.json HTTP round-trip. This request
|
|
88
|
+
// (~1.7 s network) will then run concurrently with Metal PSO compilation
|
|
89
|
+
// below rather than serially after it.
|
|
90
|
+
if (token.length > 0) {
|
|
91
|
+
_config.ionAccessToken = std::string([token UTF8String]);
|
|
92
|
+
_config.ionAssetId = assetId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// These two params are consumed once inside CesiumEngine::initialize()
|
|
96
|
+
// (SqliteCache constructor and TaskProcessor thread-pool sizing). Setting
|
|
97
|
+
// them after buildEngine() has no effect, so they must be seeded here.
|
|
98
|
+
if (sqliteRows > 0) {
|
|
99
|
+
_config.sqliteCacheMaxRows = std::max<int32_t>(64, sqliteRows);
|
|
100
|
+
}
|
|
101
|
+
if (threads >= 0) {
|
|
102
|
+
_config.taskProcessorThreads = threads;
|
|
103
|
+
}
|
|
104
|
+
|
|
75
105
|
_metalBackend = std::make_unique<reactnativecesium::MetalBackend>();
|
|
76
|
-
_metalBackend->initialize((__bridge void*)layer, width, height);
|
|
77
106
|
|
|
107
|
+
// Start the async tileset.json fetch before Metal PSO compilation so both
|
|
108
|
+
// operations run concurrently. The network round-trip (~1.7 s) overlaps
|
|
109
|
+
// with pipeline compilation rather than waiting behind it.
|
|
78
110
|
[self buildEngine];
|
|
111
|
+
_metalBackend->initialize((__bridge void*)layer, width, height);
|
|
112
|
+
|
|
79
113
|
_initialized = YES;
|
|
80
114
|
|
|
81
115
|
[[NSNotificationCenter defaultCenter]
|
|
@@ -306,8 +340,34 @@
|
|
|
306
340
|
- (BOOL)shouldRenderNextFrame {
|
|
307
341
|
if (!_initialized || _suspended || !_engine) return NO;
|
|
308
342
|
if (_forceRender.load(std::memory_order_acquire)) return YES;
|
|
309
|
-
if (
|
|
343
|
+
if (!_frameResult.tilesetActive) return YES;
|
|
344
|
+
if (_frameResult.tilesLoading > 0) return YES;
|
|
310
345
|
if (_integrator && _integrator->isActive()) return YES;
|
|
346
|
+
// ── Post-loading grace period ─────────────────────────────────────────────
|
|
347
|
+
// Keep the render loop at full speed for 2 seconds after the last non-zero
|
|
348
|
+
// loading frame. This fixes a cascade of multi-second idle gaps that
|
|
349
|
+
// otherwise appear between load phases:
|
|
350
|
+
//
|
|
351
|
+
// 1. Loading queue drains → shouldRenderNextFrame returns NO
|
|
352
|
+
// 2. Loop drops to 4 fps (0.25 s idle probe)
|
|
353
|
+
// 3. deltaTime is clamped to 100 ms → a 0.5 s LOD fade takes 5 idle
|
|
354
|
+
// frames × 0.25 s = 1.25 s of wall-clock time instead of 0.5 s
|
|
355
|
+
// 4. kickDescendantsWhileFadingIn=true means descendants can't be
|
|
356
|
+
// *requested* until the parent finishes fading
|
|
357
|
+
// 5. loadTiles() is only called inside renderFrame, so tile requests are
|
|
358
|
+
// throttled to 4 per second during idle
|
|
359
|
+
// The net effect: each of the ~5 refinement levels adds a 5-second gap.
|
|
360
|
+
//
|
|
361
|
+
// With a 2-second grace period:
|
|
362
|
+
// • LOD fades complete at full speed (0.5 s real, not 1.25 s)
|
|
363
|
+
// • updateViewGroup discovers the next level immediately after the fade
|
|
364
|
+
// • Workers start downloading before we ever slow down
|
|
365
|
+
// → The 5-second inter-phase gaps collapse to ~0.5 s
|
|
366
|
+
if (_lastLoadingTime.time_since_epoch().count() > 0) {
|
|
367
|
+
const double secSinceLoad = std::chrono::duration<double>(
|
|
368
|
+
std::chrono::steady_clock::now() - _lastLoadingTime).count();
|
|
369
|
+
if (secSinceLoad < 2.0) return YES;
|
|
370
|
+
}
|
|
311
371
|
return NO;
|
|
312
372
|
}
|
|
313
373
|
|
|
@@ -344,6 +404,13 @@
|
|
|
344
404
|
_engine->camera().setParams(actual);
|
|
345
405
|
|
|
346
406
|
_engine->updateFrame(_viewportWidth, _viewportHeight, _frameResult);
|
|
407
|
+
|
|
408
|
+
// Track the last time tiles were actively loading so shouldRenderNextFrame
|
|
409
|
+
// can suppress the idle transition for a grace period (see that method).
|
|
410
|
+
if (_frameResult.tilesLoading > 0) {
|
|
411
|
+
_lastLoadingTime = std::chrono::steady_clock::now();
|
|
412
|
+
}
|
|
413
|
+
|
|
347
414
|
// For metrics purposes only — keeps the FPS EMA in something close to the
|
|
348
415
|
// wall-clock cadence. Not used by the integrator.
|
|
349
416
|
double dt = (_lastTickSec > 0.0) ? (nowSeconds - _lastTickSec) : (1.0 / 60.0);
|
|
@@ -372,20 +372,24 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
372
372
|
.cachesDirectory, .userDomainMask, true
|
|
373
373
|
).first ?? NSTemporaryDirectory()
|
|
374
374
|
|
|
375
|
+
// Pass the ion token at construction time so the engine can start the
|
|
376
|
+
// tileset.json network round-trip immediately, before Metal PSO compilation
|
|
377
|
+
// begins (the two operations then overlap rather than run in series).
|
|
375
378
|
bridge = CesiumBridge(
|
|
376
379
|
metalLayer: metalLayer,
|
|
377
380
|
width: Int32(w),
|
|
378
381
|
height: Int32(h),
|
|
379
|
-
cacheDir: cacheDir
|
|
382
|
+
cacheDir: cacheDir,
|
|
383
|
+
ionAccessToken: ionAccessToken,
|
|
384
|
+
ionAssetId: Int64(ionAssetId),
|
|
385
|
+
sqliteCacheMaxRows: Int32(sqliteCacheMaxRows ?? 0),
|
|
386
|
+
taskProcessorThreads: Int32(taskProcessorThreads ?? 0)
|
|
380
387
|
)
|
|
381
388
|
|
|
382
389
|
syncBridgeOptionsFromProps()
|
|
383
390
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
// Seed from initialCamera, then replay anything the consumer set before
|
|
388
|
-
// we were ready.
|
|
391
|
+
// Seed the camera so the very first updateViewGroup call (which fires in
|
|
392
|
+
// the first renderFrame after init) uses the correct view position.
|
|
389
393
|
let seed = pendingTeleport ?? initialCamera
|
|
390
394
|
bridge?.teleportLatitude(seed.latitude,
|
|
391
395
|
longitude: seed.longitude,
|
|
@@ -396,6 +400,13 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
396
400
|
verticalFovDeg: seed.verticalFovDeg)
|
|
397
401
|
pendingTeleport = nil
|
|
398
402
|
|
|
403
|
+
// updateIonAccessToken is a no-op here (token already in config from the
|
|
404
|
+
// constructor) but is kept for safety in case of hot-reload where the
|
|
405
|
+
// token changes between bridge teardown and reconstruction.
|
|
406
|
+
if !ionAccessToken.isEmpty {
|
|
407
|
+
bridge?.updateIonAccessToken(ionAccessToken, assetId: Int64(ionAssetId))
|
|
408
|
+
}
|
|
409
|
+
|
|
399
410
|
if let la = pendingLat, let lo = pendingLon {
|
|
400
411
|
bridge?.setPositionLatitude(la, longitude: lo); pendingLat = nil; pendingLon = nil
|
|
401
412
|
}
|
|
@@ -407,9 +418,7 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
407
418
|
if let f = pendingVfov { bridge?.setVerticalFovDeg(f); pendingVfov = nil }
|
|
408
419
|
if let q = pendingViewCorrection { bridge?.setViewCorrectionW(q.w, x: q.x, y: q.y, z: q.z); pendingViewCorrection = nil }
|
|
409
420
|
|
|
410
|
-
|
|
411
|
-
bridge?.updateImageryAssetId(Int64(ionImageryAssetId))
|
|
412
|
-
}
|
|
421
|
+
bridge?.updateImageryAssetId(Int64(ionImageryAssetId))
|
|
413
422
|
|
|
414
423
|
let sc = Int32(msaaSampleCount)
|
|
415
424
|
bridge?.setMsaaSampleCount(sc)
|
|
@@ -448,8 +457,8 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
448
457
|
if let v = culledScreenSpaceError { bridge?.setCulledScreenSpaceError(v) }
|
|
449
458
|
if let v = enableLodTransitionPeriod { bridge?.setEnableLodTransitionPeriod(v) }
|
|
450
459
|
if let v = lodTransitionLength { bridge?.setLodTransitionLength(v) }
|
|
451
|
-
|
|
452
|
-
|
|
460
|
+
// sqliteCacheMaxRows and taskProcessorThreads are applied at construction
|
|
461
|
+
// time via the CesiumBridge initializer; calling them here has no effect.
|
|
453
462
|
if let v = minAltitudeAboveTerrain { bridge?.setMinAltitudeAboveTerrain(Float(v)) }
|
|
454
463
|
}
|
|
455
464
|
|
|
@@ -471,9 +480,17 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
471
480
|
if hasConfiguredFrameRate && usingLowRefreshRate == idle { return }
|
|
472
481
|
hasConfiguredFrameRate = true
|
|
473
482
|
usingLowRefreshRate = idle
|
|
483
|
+
// Active range tops out at ProMotion's 120 Hz — on a 60 Hz panel iOS just
|
|
484
|
+
// clamps to 60 and ignores the upper bound, so this is safe on every
|
|
485
|
+
// device. The lower bound (60 Hz) prevents iOS from dynamically dropping
|
|
486
|
+
// to 30 Hz mid-gesture if the system thinks the scene is "static enough"
|
|
487
|
+
// (which is what produced the visible camera/terrain judder during
|
|
488
|
+
// continuous pitch/roll motion — the input was smooth, the render rate
|
|
489
|
+
// wasn't). Idle range stays low to preserve power when the camera and
|
|
490
|
+
// tileset are at rest.
|
|
474
491
|
dl.preferredFrameRateRange = idle
|
|
475
|
-
? CAFrameRateRange(minimum: 5,
|
|
476
|
-
: CAFrameRateRange(minimum:
|
|
492
|
+
? CAFrameRateRange(minimum: 5, maximum: 15, preferred: 10)
|
|
493
|
+
: CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120)
|
|
477
494
|
}
|
|
478
495
|
|
|
479
496
|
/// Invoked by `DisplayLinkProxy` on every CADisplayLink tick (main thread).
|
package/ios/metal/MetalBackend.h
CHANGED
|
@@ -43,8 +43,13 @@ private:
|
|
|
43
43
|
void* device_; // id<MTLDevice>
|
|
44
44
|
void* commandQueue_; // id<MTLCommandQueue>
|
|
45
45
|
void* metalLayer_; // CAMetalLayer* (weak — owned by the view)
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// Two terrain pipelines built from the same MSL source. The solid pipeline
|
|
47
|
+
// omits the LOD-fade discard so Apple TBDR Hidden Surface Removal stays
|
|
48
|
+
// enabled at full speed for the 99% of tiles not currently transitioning.
|
|
49
|
+
// The dither pipeline is used only for draws where DrawPrimitive::lodFade<1.
|
|
50
|
+
void* terrainSolidPipeline_; // id<MTLRenderPipelineState> (no discard)
|
|
51
|
+
void* terrainDitherPipeline_; // id<MTLRenderPipelineState> (LOD-fade dither)
|
|
52
|
+
void* skyPipeline_; // id<MTLRenderPipelineState>
|
|
48
53
|
void* terrainDepthState_; // reversed-Z GREATER, write=YES
|
|
49
54
|
void* skyDepthState_; // Always, write=NO
|
|
50
55
|
void* depthTexture_; // id<MTLTexture>
|
|
@@ -77,6 +82,21 @@ private:
|
|
|
77
82
|
size_t idxCaps_[kMaxFramesInFlight];
|
|
78
83
|
size_t uvCaps_[kMaxFramesInFlight];
|
|
79
84
|
size_t altCaps_[kMaxFramesInFlight];
|
|
85
|
+
|
|
86
|
+
// per-draw uniforms ring. One MTLBuffer per frame slot, filled with
|
|
87
|
+
// [draw0 VS uniforms][draw0 FS uniforms][draw1 VS][draw1 FS]… each padded
|
|
88
|
+
// to kUniformAlign so setVertexBufferOffset / setFragmentBufferOffset can
|
|
89
|
+
// index into it without re-binding the buffer each draw. Replaces the
|
|
90
|
+
// per-draw setVertexBytes/setFragmentBytes calls that show up as encoder
|
|
91
|
+
// overhead on the GPU frame capture.
|
|
92
|
+
static constexpr size_t kUniformAlign = 256;
|
|
93
|
+
void* uniformsBufs_[kMaxFramesInFlight]; // id<MTLBuffer>
|
|
94
|
+
size_t uniformsCaps_[kMaxFramesInFlight];
|
|
95
|
+
|
|
96
|
+
// per-slot geometry signature cache. Same semantics as the Vulkan
|
|
97
|
+
// backend's geomSlotSig_: when FrameResult::geometrySignature matches the
|
|
98
|
+
// value last written to this slot we skip the merged-geometry memcpy.
|
|
99
|
+
uint64_t geomSlotSig_[kMaxFramesInFlight] = {};
|
|
80
100
|
};
|
|
81
101
|
|
|
82
102
|
} // namespace reactnativecesium
|