react-native-cesium 1.2.16 → 1.2.18
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/build.gradle
CHANGED
|
@@ -80,7 +80,9 @@ android {
|
|
|
80
80
|
|
|
81
81
|
buildTypes {
|
|
82
82
|
debug {
|
|
83
|
-
|
|
83
|
+
// -O2 (was -O1) so debug builds — the default dev/emulator workflow —
|
|
84
|
+
// aren't unfairly slow when profiling the renderer. -g keeps symbols.
|
|
85
|
+
cppFlags "-O2 -g"
|
|
84
86
|
}
|
|
85
87
|
release {
|
|
86
88
|
cppFlags "-O2"
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
// catastrophic cancellation in the shader.
|
|
11
11
|
// - Single merged vertex + index buffer uploaded each frame.
|
|
12
12
|
// - Reversed-Z infinite projection: depth clear = 0, compare = GREATER.
|
|
13
|
-
// -
|
|
13
|
+
// - Terrain drawn first (writes depth); sky drawn after with a depth test
|
|
14
|
+
// (no write) so the full-screen atmospheric shader only runs on pixels not
|
|
15
|
+
// covered by terrain.
|
|
14
16
|
// - Water mask: UV computed from geographic lat/lon + tile bounds (not fragUV).
|
|
15
17
|
// - Push constants: bytes 0-63 vertex (MVP), bytes 64-127 fragment.
|
|
16
18
|
// - Triple-buffered persistent VkBuffers guarded by VkFence.
|
|
@@ -447,6 +449,29 @@ void VulkanBackend::pickPhysicalDevice() {
|
|
|
447
449
|
supportsAnisotropy_ = feats.samplerAnisotropy == VK_TRUE;
|
|
448
450
|
maxAnisotropy_ = props.limits.maxSamplerAnisotropy;
|
|
449
451
|
|
|
452
|
+
// One-time log of the selected GPU so we can tell a real hardware device from
|
|
453
|
+
// a software rasterizer (e.g. the Android Emulator's SwiftShader / a slow
|
|
454
|
+
// gfxstream translation layer), which is the dominant factor in emulator FPS.
|
|
455
|
+
const char* typeStr = "OTHER";
|
|
456
|
+
switch (props.deviceType) {
|
|
457
|
+
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: typeStr = "DISCRETE_GPU"; break;
|
|
458
|
+
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: typeStr = "INTEGRATED_GPU"; break;
|
|
459
|
+
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: typeStr = "VIRTUAL_GPU"; break;
|
|
460
|
+
case VK_PHYSICAL_DEVICE_TYPE_CPU: typeStr = "CPU (software)"; break;
|
|
461
|
+
default: typeStr = "OTHER"; break;
|
|
462
|
+
}
|
|
463
|
+
LOGI("Vulkan GPU: \"%s\" type=%s apiVersion=%u.%u.%u driverVersion=0x%x",
|
|
464
|
+
props.deviceName, typeStr,
|
|
465
|
+
VK_VERSION_MAJOR(props.apiVersion),
|
|
466
|
+
VK_VERSION_MINOR(props.apiVersion),
|
|
467
|
+
VK_VERSION_PATCH(props.apiVersion),
|
|
468
|
+
props.driverVersion);
|
|
469
|
+
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
|
|
470
|
+
LOGW("Selected a CPU/software Vulkan device — expect very low FPS. "
|
|
471
|
+
"This is typical on the Android Emulator without host-GPU acceleration; "
|
|
472
|
+
"measure on a physical device for representative performance.");
|
|
473
|
+
}
|
|
474
|
+
|
|
450
475
|
const VkSampleCountFlags counts = props.limits.framebufferColorSampleCounts &
|
|
451
476
|
props.limits.framebufferDepthSampleCounts;
|
|
452
477
|
if (counts & VK_SAMPLE_COUNT_8_BIT) supportedMsaaMask_ = VK_SAMPLE_COUNT_8_BIT;
|
|
@@ -582,6 +607,32 @@ void VulkanBackend::createSwapchain() {
|
|
|
582
607
|
VkSwapchainCreateInfoKHR swapInfo{};
|
|
583
608
|
swapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
|
584
609
|
swapInfo.surface = surface_;
|
|
610
|
+
// ── Surface rotation (Android pre-rotation) ──────────────────────────────
|
|
611
|
+
// We deliberately let the Android Compositor handle device rotation instead
|
|
612
|
+
// of pre-rotating the scene ourselves: present with an IDENTITY transform so
|
|
613
|
+
// the presentation engine rotates the rendered image to the current device
|
|
614
|
+
// orientation before scan-out.
|
|
615
|
+
//
|
|
616
|
+
// The alternative — preTransform = caps.currentTransform — only renders
|
|
617
|
+
// correctly if the app ALSO swaps the swapchain extent for 90/270 rotations
|
|
618
|
+
// AND bakes a matching rotation into every MVP/VP matrix. Doing neither (as
|
|
619
|
+
// before) made the scene render sideways once the device left its identity
|
|
620
|
+
// orientation: in landscape Android reports ROTATE_90/270, the compositor
|
|
621
|
+
// was told "already handled", and the un-rotated terrain appeared rotated
|
|
622
|
+
// 90° (ground stuck on the side of the screen). Since the swapchain image is
|
|
623
|
+
// the engine's only render target and modern Android display processors
|
|
624
|
+
// rotate essentially for free, the compositor path is both correct and cheap.
|
|
625
|
+
//
|
|
626
|
+
// Consequence: while the device is rotated, preTransform != currentTransform,
|
|
627
|
+
// so vkAcquireNextImageKHR / vkQueuePresentKHR return VK_SUBOPTIMAL_KHR every
|
|
628
|
+
// frame. That is expected here and must NOT trigger swapchain recreation (see
|
|
629
|
+
// beginFrame() / endFrame()); genuine size/orientation changes are driven by
|
|
630
|
+
// surfaceChanged() -> resize() instead.
|
|
631
|
+
VkSurfaceTransformFlagBitsKHR preTransform = caps.currentTransform;
|
|
632
|
+
if (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
|
|
633
|
+
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
634
|
+
}
|
|
635
|
+
|
|
585
636
|
swapInfo.minImageCount = imageCount;
|
|
586
637
|
swapInfo.imageFormat = swapchainFormat_;
|
|
587
638
|
swapInfo.imageColorSpace = colorSpace;
|
|
@@ -589,7 +640,7 @@ void VulkanBackend::createSwapchain() {
|
|
|
589
640
|
swapInfo.imageArrayLayers = 1;
|
|
590
641
|
swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
591
642
|
swapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
592
|
-
swapInfo.preTransform =
|
|
643
|
+
swapInfo.preTransform = preTransform;
|
|
593
644
|
swapInfo.compositeAlpha = compositeAlpha;
|
|
594
645
|
swapInfo.presentMode = pickPresentMode();
|
|
595
646
|
swapInfo.clipped = VK_TRUE;
|
|
@@ -1065,6 +1116,7 @@ struct PipelineBuildSpec {
|
|
|
1065
1116
|
const VkVertexInputAttributeDescription* attributes;
|
|
1066
1117
|
bool depthTest;
|
|
1067
1118
|
bool depthWrite;
|
|
1119
|
+
VkCompareOp depthCompare;
|
|
1068
1120
|
VkCullModeFlags cull;
|
|
1069
1121
|
VkSampleCountFlagBits samples;
|
|
1070
1122
|
};
|
|
@@ -1115,7 +1167,7 @@ VkPipeline buildPipeline(VkDevice device,
|
|
|
1115
1167
|
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
|
1116
1168
|
depthStencil.depthTestEnable = spec.depthTest ? VK_TRUE : VK_FALSE;
|
|
1117
1169
|
depthStencil.depthWriteEnable = spec.depthWrite ? VK_TRUE : VK_FALSE;
|
|
1118
|
-
depthStencil.depthCompareOp =
|
|
1170
|
+
depthStencil.depthCompareOp = spec.depthCompare; // Reversed-Z (GREATER family)
|
|
1119
1171
|
|
|
1120
1172
|
VkPipelineColorBlendAttachmentState colorBlendAttach{};
|
|
1121
1173
|
colorBlendAttach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
|
@@ -1166,8 +1218,15 @@ void VulkanBackend::createSkyPipeline() {
|
|
|
1166
1218
|
spec.bindings = nullptr;
|
|
1167
1219
|
spec.attributeCount = 0;
|
|
1168
1220
|
spec.attributes = nullptr;
|
|
1169
|
-
|
|
1221
|
+
// Depth-test the sky against the terrain (which is drawn first and writes
|
|
1222
|
+
// depth) so the expensive atmospheric raymarch only runs on pixels that
|
|
1223
|
+
// aren't covered by geometry. sky.vert emits z=0 (the reversed-Z far plane)
|
|
1224
|
+
// and the depth buffer is cleared to 0, so GREATER_OR_EQUAL passes only where
|
|
1225
|
+
// no terrain wrote a closer (>0) depth. No depth write — the sky never
|
|
1226
|
+
// occludes anything.
|
|
1227
|
+
spec.depthTest = true;
|
|
1170
1228
|
spec.depthWrite = false;
|
|
1229
|
+
spec.depthCompare = VK_COMPARE_OP_GREATER_OR_EQUAL;
|
|
1171
1230
|
spec.cull = VK_CULL_MODE_NONE;
|
|
1172
1231
|
spec.samples = sampleCount_;
|
|
1173
1232
|
skyPipeline_ = buildPipeline(device_, skyPipelineLayout_, renderPass_,
|
|
@@ -1207,6 +1266,7 @@ void VulkanBackend::createTerrainPipeline() {
|
|
|
1207
1266
|
spec.attributes = attrs;
|
|
1208
1267
|
spec.depthTest = true;
|
|
1209
1268
|
spec.depthWrite = true;
|
|
1269
|
+
spec.depthCompare = VK_COMPARE_OP_GREATER; // Reversed-Z
|
|
1210
1270
|
spec.cull = VK_CULL_MODE_BACK_BIT;
|
|
1211
1271
|
spec.samples = sampleCount_;
|
|
1212
1272
|
return buildPipeline(device_, terrainPipelineLayout_, renderPass_,
|
|
@@ -2042,13 +2102,18 @@ void VulkanBackend::beginFrame(const FrameParams& /*params*/) {
|
|
|
2042
2102
|
frameBegan_ = false;
|
|
2043
2103
|
return;
|
|
2044
2104
|
}
|
|
2045
|
-
|
|
2105
|
+
// Only OUT_OF_DATE forces a recreate. VK_SUBOPTIMAL_KHR is expected on every
|
|
2106
|
+
// frame while the device is rotated: we present with an IDENTITY preTransform
|
|
2107
|
+
// and let the compositor rotate (see createSwapchain). The acquired image is
|
|
2108
|
+
// still valid, so we proceed and use it. Genuine size/orientation changes
|
|
2109
|
+
// arrive via surfaceChanged() -> resize() -> needsSwapchainRecreate_.
|
|
2110
|
+
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
2046
2111
|
recreateSwapchain();
|
|
2047
2112
|
result = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX,
|
|
2048
2113
|
imageAvailableSemaphores_[frameIndex_],
|
|
2049
2114
|
VK_NULL_HANDLE, &imageIndex_);
|
|
2050
2115
|
}
|
|
2051
|
-
if (result != VK_SUCCESS) {
|
|
2116
|
+
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
|
2052
2117
|
frameBegan_ = false;
|
|
2053
2118
|
return;
|
|
2054
2119
|
}
|
|
@@ -2100,31 +2165,11 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2100
2165
|
|
|
2101
2166
|
VkCommandBuffer cmd = commandBuffers_[frameIndex_];
|
|
2102
2167
|
|
|
2103
|
-
// ──
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
skyU.cameraEcef[0] = frame.cameraEcef.x;
|
|
2109
|
-
skyU.cameraEcef[1] = frame.cameraEcef.y;
|
|
2110
|
-
skyU.cameraEcef[2] = frame.cameraEcef.z;
|
|
2111
|
-
float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
|
|
2112
|
-
frame.cameraEcef.y * frame.cameraEcef.y +
|
|
2113
|
-
frame.cameraEcef.z * frame.cameraEcef.z);
|
|
2114
|
-
if (cl > 0.0f) {
|
|
2115
|
-
skyU.lightDir[0] = frame.cameraEcef.x / cl;
|
|
2116
|
-
skyU.lightDir[1] = frame.cameraEcef.y / cl;
|
|
2117
|
-
skyU.lightDir[2] = frame.cameraEcef.z / cl;
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
memcpy(skyUboMapped_, &skyU, sizeof(SkyUBO));
|
|
2121
|
-
|
|
2122
|
-
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline_);
|
|
2123
|
-
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipelineLayout_,
|
|
2124
|
-
0, 1, &skyDescSet_, 0, nullptr);
|
|
2125
|
-
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2168
|
+
// ── Terrain pass (opaque, writes depth) ─────────────────────────────────────
|
|
2169
|
+
// Drawn FIRST so the sky pass below can depth-test against the terrain and
|
|
2170
|
+
// skip the expensive full-screen atmospheric raymarch on every covered pixel.
|
|
2171
|
+
// Wrapped in a lambda so its early-outs still fall through to the sky pass.
|
|
2172
|
+
auto drawTerrain = [&]() {
|
|
2128
2173
|
if (frame.draws.empty() || !terrainSolidPipeline_ || !terrainDitherPipeline_) {
|
|
2129
2174
|
return;
|
|
2130
2175
|
}
|
|
@@ -2277,6 +2322,36 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2277
2322
|
uint32_t firstIndex = draw.indexByteOffset / sizeof(uint32_t);
|
|
2278
2323
|
vkCmdDrawIndexed(cmd, draw.indexCount, 1, firstIndex, 0, 0);
|
|
2279
2324
|
}
|
|
2325
|
+
}; // drawTerrain
|
|
2326
|
+
drawTerrain();
|
|
2327
|
+
|
|
2328
|
+
// ── Sky pass ────────────────────────────────────────────────────────────────
|
|
2329
|
+
// Drawn LAST and depth-tested (no write): the raymarch only executes on pixels
|
|
2330
|
+
// where no terrain was rendered (depth still at the cleared reversed-Z far
|
|
2331
|
+
// plane). This avoids shading the full screen and then overdrawing it.
|
|
2332
|
+
if (skyPipeline_ && skyDescSet_) {
|
|
2333
|
+
SkyUBO skyU{};
|
|
2334
|
+
const float* iv = glm::value_ptr(frame.invVP);
|
|
2335
|
+
for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
|
|
2336
|
+
skyU.cameraEcef[0] = frame.cameraEcef.x;
|
|
2337
|
+
skyU.cameraEcef[1] = frame.cameraEcef.y;
|
|
2338
|
+
skyU.cameraEcef[2] = frame.cameraEcef.z;
|
|
2339
|
+
float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
|
|
2340
|
+
frame.cameraEcef.y * frame.cameraEcef.y +
|
|
2341
|
+
frame.cameraEcef.z * frame.cameraEcef.z);
|
|
2342
|
+
if (cl > 0.0f) {
|
|
2343
|
+
skyU.lightDir[0] = frame.cameraEcef.x / cl;
|
|
2344
|
+
skyU.lightDir[1] = frame.cameraEcef.y / cl;
|
|
2345
|
+
skyU.lightDir[2] = frame.cameraEcef.z / cl;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
memcpy(skyUboMapped_, &skyU, sizeof(SkyUBO));
|
|
2349
|
+
|
|
2350
|
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline_);
|
|
2351
|
+
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipelineLayout_,
|
|
2352
|
+
0, 1, &skyDescSet_, 0, nullptr);
|
|
2353
|
+
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
2354
|
+
}
|
|
2280
2355
|
}
|
|
2281
2356
|
|
|
2282
2357
|
void VulkanBackend::endFrame() {
|
|
@@ -2311,7 +2386,12 @@ void VulkanBackend::endFrame() {
|
|
|
2311
2386
|
presentInfo.pImageIndices = &imageIndex_;
|
|
2312
2387
|
|
|
2313
2388
|
VkResult result = vkQueuePresentKHR(presentQueue_, &presentInfo);
|
|
2314
|
-
|
|
2389
|
+
// VK_SUBOPTIMAL_KHR is expected every frame while the device is rotated (we
|
|
2390
|
+
// present an IDENTITY preTransform and let the compositor rotate — see
|
|
2391
|
+
// createSwapchain), so it must NOT trigger a recreate or we'd thrash the
|
|
2392
|
+
// swapchain every frame. Real changes come through surfaceChanged() ->
|
|
2393
|
+
// resize() -> needsSwapchainRecreate_.
|
|
2394
|
+
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
2315
2395
|
needsSwapchainRecreate_ = true;
|
|
2316
2396
|
}
|
|
2317
2397
|
|
|
@@ -38,11 +38,11 @@ void main() {
|
|
|
38
38
|
float tmin = max(at.x, 0.0);
|
|
39
39
|
if (tmin >= tmax) { outColor = vec4(0, 0, 0, 1); return; }
|
|
40
40
|
|
|
41
|
-
// Sample counts
|
|
41
|
+
// Sample counts reduced 16/4 -> 8/2 -> 6/2 — visually negligible at typical
|
|
42
42
|
// phone DPI / viewport sizes (a 24-bit screen can't resolve the difference
|
|
43
|
-
// in atmospheric scattering past ~6 primary samples),
|
|
44
|
-
//
|
|
45
|
-
const int S =
|
|
43
|
+
// in atmospheric scattering past ~6 primary samples), and cuts the expensive
|
|
44
|
+
// per-pixel `exp` calls further (helps weak/software GPUs most).
|
|
45
|
+
const int S = 6, L = 2;
|
|
46
46
|
float ss = (tmax - tmin) / float(S);
|
|
47
47
|
vec3 rayl = vec3(0), miel = vec3(0);
|
|
48
48
|
float oR = 0.0, oM = 0.0;
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
// ellipsoidHeightMeters(wp) in the old shader.
|
|
17
17
|
// • Single merged vertex + index buffer uploaded each frame.
|
|
18
18
|
// • Reversed-Z infinite projection: depth clear = 0, compare = GREATER.
|
|
19
|
-
// •
|
|
19
|
+
// • Terrain drawn first (writes depth); sky drawn after with a depth test
|
|
20
|
+
// (no write) so the full-screen atmospheric shader only runs on pixels not
|
|
21
|
+
// covered by terrain.
|
|
20
22
|
// • Triple-buffered persistent MTLBuffers: one slot per in-flight frame.
|
|
21
23
|
|
|
22
24
|
#import <Foundation/Foundation.h>
|
|
@@ -335,9 +337,10 @@ fragment float4 skyFragment(SkyVOut in [[stage_in]],
|
|
|
335
337
|
// underground sample heights to zero (ground-level density).
|
|
336
338
|
float tmax=at.y;
|
|
337
339
|
float tmin=max(at.x,0.f);if(tmin>=tmax)return float4(0,0,0,1);
|
|
338
|
-
//
|
|
339
|
-
// (atmospheric scattering banding is sub-pixel at this sample count)
|
|
340
|
-
|
|
340
|
+
// Reduced 16/4 -> 8/2 -> 6/2 — visually negligible at typical phone DPI
|
|
341
|
+
// (atmospheric scattering banding is sub-pixel at this sample count) and
|
|
342
|
+
// cuts per-pixel `exp` calls further. Kept in sync with the Vulkan sky.frag.
|
|
343
|
+
const int S=6,L=2;
|
|
341
344
|
float ss=(tmax-tmin)/float(S);
|
|
342
345
|
float3 rayl=0,miel=0;float oR=0,oM=0;
|
|
343
346
|
for(int i=0;i<S;++i){
|
|
@@ -542,7 +545,11 @@ void MetalBackend::buildRenderPipelines() {
|
|
|
542
545
|
}
|
|
543
546
|
if (!skyDepthState_) {
|
|
544
547
|
MTLDepthStencilDescriptor* sd = [MTLDepthStencilDescriptor new];
|
|
545
|
-
|
|
548
|
+
// Sky is drawn after terrain with a depth test (no write). skyVertex emits
|
|
549
|
+
// z=0 (the reversed-Z far plane) and the depth buffer clears to 0, so
|
|
550
|
+
// GreaterEqual passes only where no terrain wrote a closer (>0) depth —
|
|
551
|
+
// i.e. the raymarch only runs on uncovered pixels.
|
|
552
|
+
sd.depthCompareFunction = MTLCompareFunctionGreaterEqual;
|
|
546
553
|
sd.depthWriteEnabled = NO;
|
|
547
554
|
skyDepthState_ = (__bridge_retained void*)
|
|
548
555
|
[dev newDepthStencilStateWithDescriptor:sd];
|
|
@@ -550,6 +557,8 @@ void MetalBackend::buildRenderPipelines() {
|
|
|
550
557
|
}
|
|
551
558
|
|
|
552
559
|
void MetalBackend::setMsaaSampleCount(int sc) {
|
|
560
|
+
if (!device_) return;
|
|
561
|
+
|
|
553
562
|
int n = sc;
|
|
554
563
|
if (n != 2 && n != 4) n = 1;
|
|
555
564
|
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
@@ -594,24 +603,26 @@ void MetalBackend::resize(int width, int height) {
|
|
|
594
603
|
}
|
|
595
604
|
|
|
596
605
|
void MetalBackend::shutdown() {
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
|
|
601
|
-
|
|
606
|
+
// Idempotent — CesiumBridge calls shutdown() explicitly and the unique_ptr
|
|
607
|
+
// dtor calls it again; during Fast Refresh teardown may also overlap with
|
|
608
|
+
// in-flight Metal completion handlers that captured the frame semaphore.
|
|
609
|
+
if (!device_) return;
|
|
610
|
+
|
|
611
|
+
// Drain in-flight frame slots: each beginFrame wait consumes a token and
|
|
612
|
+
// endFrame's GPU completion handler signals it back. Blocking here until
|
|
613
|
+
// every committed command buffer has finished ensures no handler signals
|
|
614
|
+
// the semaphore after we CFRelease it (Fast Refresh / unmount crash).
|
|
602
615
|
if (frameSemaphore_) {
|
|
603
616
|
dispatch_semaphore_t sem = (__bridge dispatch_semaphore_t)frameSemaphore_;
|
|
604
617
|
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
@"force-releasing and continuing teardown", i);
|
|
610
|
-
break;
|
|
611
|
-
}
|
|
618
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
619
|
+
}
|
|
620
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
621
|
+
dispatch_semaphore_signal(sem);
|
|
612
622
|
}
|
|
613
|
-
|
|
623
|
+
void* semToRelease = frameSemaphore_;
|
|
614
624
|
frameSemaphore_ = nullptr;
|
|
625
|
+
CFRelease(semToRelease);
|
|
615
626
|
}
|
|
616
627
|
|
|
617
628
|
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
@@ -733,6 +744,8 @@ void* MetalBackend::createRasterTexture(const uint8_t* pixels,
|
|
|
733
744
|
// ── Per-frame ─────────────────────────────────────────────────────────────────
|
|
734
745
|
|
|
735
746
|
void MetalBackend::beginFrame(const FrameParams& /*params*/) {
|
|
747
|
+
if (!device_ || !frameSemaphore_) return;
|
|
748
|
+
|
|
736
749
|
// Acquire a frame slot. Blocks if kMaxFramesInFlight frames are already
|
|
737
750
|
// queued on the GPU. The completion handler in endFrame() signals the
|
|
738
751
|
// semaphore to release the slot.
|
|
@@ -797,32 +810,11 @@ void MetalBackend::drawScene(const FrameResult& frame) {
|
|
|
797
810
|
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)currentEncoder_;
|
|
798
811
|
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
799
812
|
|
|
800
|
-
// ── Sky pass ──────────────────────────────────────────────────────────────
|
|
801
|
-
if (skyPipeline_ && skyDepthState_) {
|
|
802
|
-
SkyUniforms skyU{};
|
|
803
|
-
const float* iv = glm::value_ptr(frame.invVP);
|
|
804
|
-
for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
|
|
805
|
-
skyU.cameraEcef[0] = frame.cameraEcef.x;
|
|
806
|
-
skyU.cameraEcef[1] = frame.cameraEcef.y;
|
|
807
|
-
skyU.cameraEcef[2] = frame.cameraEcef.z;
|
|
808
|
-
float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
|
|
809
|
-
frame.cameraEcef.y * frame.cameraEcef.y +
|
|
810
|
-
frame.cameraEcef.z * frame.cameraEcef.z);
|
|
811
|
-
if (cl > 0.0f) {
|
|
812
|
-
skyU.lightDir[0] = frame.cameraEcef.x / cl;
|
|
813
|
-
skyU.lightDir[1] = frame.cameraEcef.y / cl;
|
|
814
|
-
skyU.lightDir[2] = frame.cameraEcef.z / cl;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
[enc setRenderPipelineState:(__bridge id<MTLRenderPipelineState>)skyPipeline_];
|
|
818
|
-
[enc setDepthStencilState:(__bridge id<MTLDepthStencilState>)skyDepthState_];
|
|
819
|
-
[enc setCullMode:MTLCullModeNone];
|
|
820
|
-
[enc setVertexBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
821
|
-
[enc setFragmentBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
822
|
-
[enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
|
823
|
-
}
|
|
824
|
-
|
|
825
813
|
// ── Terrain pass ──────────────────────────────────────────────────────────
|
|
814
|
+
// Drawn FIRST (writes depth) so the sky pass below can depth-test against the
|
|
815
|
+
// terrain and skip the expensive full-screen atmospheric raymarch on every
|
|
816
|
+
// covered pixel. Wrapped in a lambda so its early-outs fall through to sky.
|
|
817
|
+
auto drawTerrain = [&]() {
|
|
826
818
|
if (frame.draws.empty() ||
|
|
827
819
|
!terrainSolidPipeline_ || !terrainDitherPipeline_ || !terrainDepthState_) {
|
|
828
820
|
return;
|
|
@@ -1013,6 +1005,35 @@ void MetalBackend::drawScene(const FrameResult& frame) {
|
|
|
1013
1005
|
indexBuffer:idxBuf
|
|
1014
1006
|
indexBufferOffset:draw.indexByteOffset];
|
|
1015
1007
|
}
|
|
1008
|
+
}; // drawTerrain
|
|
1009
|
+
drawTerrain();
|
|
1010
|
+
|
|
1011
|
+
// ── Sky pass ──────────────────────────────────────────────────────────────
|
|
1012
|
+
// Drawn LAST, depth-tested (no write): only shades pixels not covered by
|
|
1013
|
+
// terrain (depth still at the cleared reversed-Z far plane).
|
|
1014
|
+
if (skyPipeline_ && skyDepthState_) {
|
|
1015
|
+
SkyUniforms skyU{};
|
|
1016
|
+
const float* iv = glm::value_ptr(frame.invVP);
|
|
1017
|
+
for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
|
|
1018
|
+
skyU.cameraEcef[0] = frame.cameraEcef.x;
|
|
1019
|
+
skyU.cameraEcef[1] = frame.cameraEcef.y;
|
|
1020
|
+
skyU.cameraEcef[2] = frame.cameraEcef.z;
|
|
1021
|
+
float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
|
|
1022
|
+
frame.cameraEcef.y * frame.cameraEcef.y +
|
|
1023
|
+
frame.cameraEcef.z * frame.cameraEcef.z);
|
|
1024
|
+
if (cl > 0.0f) {
|
|
1025
|
+
skyU.lightDir[0] = frame.cameraEcef.x / cl;
|
|
1026
|
+
skyU.lightDir[1] = frame.cameraEcef.y / cl;
|
|
1027
|
+
skyU.lightDir[2] = frame.cameraEcef.z / cl;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
[enc setRenderPipelineState:(__bridge id<MTLRenderPipelineState>)skyPipeline_];
|
|
1031
|
+
[enc setDepthStencilState:(__bridge id<MTLDepthStencilState>)skyDepthState_];
|
|
1032
|
+
[enc setCullMode:MTLCullModeNone];
|
|
1033
|
+
[enc setVertexBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
1034
|
+
[enc setFragmentBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
1035
|
+
[enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
|
1036
|
+
}
|
|
1016
1037
|
}
|
|
1017
1038
|
|
|
1018
1039
|
void MetalBackend::endFrame() {
|
|
@@ -1031,7 +1052,9 @@ void MetalBackend::endFrame() {
|
|
|
1031
1052
|
// Signal the frame semaphore when the GPU is done with this frame's buffers.
|
|
1032
1053
|
void* semPtr = frameSemaphore_;
|
|
1033
1054
|
[cb addCompletedHandler:^(id<MTLCommandBuffer> __unused) {
|
|
1034
|
-
|
|
1055
|
+
if (semPtr) {
|
|
1056
|
+
dispatch_semaphore_signal((__bridge dispatch_semaphore_t)semPtr);
|
|
1057
|
+
}
|
|
1035
1058
|
}];
|
|
1036
1059
|
|
|
1037
1060
|
[cb presentDrawable:drawable];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-cesium",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"description": "Experimental Cesium rendering for React Native via Nitro",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -76,13 +76,13 @@
|
|
|
76
76
|
"@semantic-release/changelog": "^6.0.3",
|
|
77
77
|
"@semantic-release/git": "^10.0.1",
|
|
78
78
|
"@types/jest": "^29.5.14",
|
|
79
|
-
"@types/react": "19.2.
|
|
79
|
+
"@types/react": "19.2.17",
|
|
80
80
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
81
81
|
"nitrogen": "0.35.9",
|
|
82
82
|
"react": "19.2.3",
|
|
83
|
-
"react-native": "0.
|
|
84
|
-
"react-native-builder-bob": "^0.
|
|
85
|
-
"semantic-release": "^25.0.
|
|
83
|
+
"react-native": "0.86.0",
|
|
84
|
+
"react-native-builder-bob": "^0.43.0",
|
|
85
|
+
"semantic-release": "^25.0.5",
|
|
86
86
|
"typescript": "^5.9.3"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|