react-native-cesium 1.2.15 → 1.2.17

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.
@@ -80,7 +80,9 @@ android {
80
80
 
81
81
  buildTypes {
82
82
  debug {
83
- cppFlags "-O1 -g"
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"
@@ -0,0 +1,41 @@
1
+ # spv_to_header.cmake — Convert a .spv binary to a C header with an embedded uint32_t array.
2
+ # Input variables: SPV_FILE, SPV_HEADER, ARRAY_NAME
3
+
4
+ file(READ "${SPV_FILE}" SPV_DATA HEX)
5
+ string(LENGTH "${SPV_DATA}" SPV_HEX_LEN)
6
+
7
+ # Validate SPIR-V magic (0x07230203, little-endian on disk → "03022307")
8
+ string(SUBSTRING "${SPV_DATA}" 0 8 SPV_MAGIC)
9
+ string(TOLOWER "${SPV_MAGIC}" SPV_MAGIC_LC)
10
+ if(NOT SPV_MAGIC_LC STREQUAL "03022307")
11
+ message(FATAL_ERROR "spv_to_header: '${SPV_FILE}' does not start with the SPIR-V magic number (got '${SPV_MAGIC}'). The file may be corrupt or not a valid SPIR-V binary.")
12
+ endif()
13
+ math(EXPR SPV_BYTE_COUNT "${SPV_HEX_LEN} / 2")
14
+ math(EXPR SPV_WORD_COUNT "${SPV_BYTE_COUNT} / 4")
15
+
16
+ set(HEADER_CONTENT "#pragma once\n#include <cstdint>\nstatic const uint32_t ${ARRAY_NAME}[] = {\n")
17
+
18
+ set(I 0)
19
+ while(I LESS SPV_HEX_LEN)
20
+ # Read 8 hex chars (4 bytes = 1 uint32_t word, little-endian in SPIR-V)
21
+ string(SUBSTRING "${SPV_DATA}" ${I} 8 WORD_HEX)
22
+ # SPIR-V is stored little-endian; file(READ HEX) gives bytes in order,
23
+ # so we need to reverse byte order for the uint32_t literal.
24
+ string(SUBSTRING "${WORD_HEX}" 0 2 B0)
25
+ string(SUBSTRING "${WORD_HEX}" 2 2 B1)
26
+ string(SUBSTRING "${WORD_HEX}" 4 2 B2)
27
+ string(SUBSTRING "${WORD_HEX}" 6 2 B3)
28
+ set(WORD "0x${B3}${B2}${B1}${B0}")
29
+
30
+ math(EXPR NEXT_I "${I} + 8")
31
+ if(NEXT_I LESS SPV_HEX_LEN)
32
+ string(APPEND HEADER_CONTENT " ${WORD},\n")
33
+ else()
34
+ string(APPEND HEADER_CONTENT " ${WORD}\n")
35
+ endif()
36
+ set(I ${NEXT_I})
37
+ endwhile()
38
+
39
+ string(APPEND HEADER_CONTENT "};\nstatic const uint32_t ${ARRAY_NAME}_size = sizeof(${ARRAY_NAME});\n")
40
+
41
+ file(WRITE "${SPV_HEADER}" "${HEADER_CONTENT}")
@@ -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
- // - Sky drawn first (no depth write), terrain drawn after.
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;
@@ -1065,6 +1090,7 @@ struct PipelineBuildSpec {
1065
1090
  const VkVertexInputAttributeDescription* attributes;
1066
1091
  bool depthTest;
1067
1092
  bool depthWrite;
1093
+ VkCompareOp depthCompare;
1068
1094
  VkCullModeFlags cull;
1069
1095
  VkSampleCountFlagBits samples;
1070
1096
  };
@@ -1115,7 +1141,7 @@ VkPipeline buildPipeline(VkDevice device,
1115
1141
  depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1116
1142
  depthStencil.depthTestEnable = spec.depthTest ? VK_TRUE : VK_FALSE;
1117
1143
  depthStencil.depthWriteEnable = spec.depthWrite ? VK_TRUE : VK_FALSE;
1118
- depthStencil.depthCompareOp = VK_COMPARE_OP_GREATER; // Reversed-Z
1144
+ depthStencil.depthCompareOp = spec.depthCompare; // Reversed-Z (GREATER family)
1119
1145
 
1120
1146
  VkPipelineColorBlendAttachmentState colorBlendAttach{};
1121
1147
  colorBlendAttach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
@@ -1166,8 +1192,15 @@ void VulkanBackend::createSkyPipeline() {
1166
1192
  spec.bindings = nullptr;
1167
1193
  spec.attributeCount = 0;
1168
1194
  spec.attributes = nullptr;
1169
- spec.depthTest = false;
1195
+ // Depth-test the sky against the terrain (which is drawn first and writes
1196
+ // depth) so the expensive atmospheric raymarch only runs on pixels that
1197
+ // aren't covered by geometry. sky.vert emits z=0 (the reversed-Z far plane)
1198
+ // and the depth buffer is cleared to 0, so GREATER_OR_EQUAL passes only where
1199
+ // no terrain wrote a closer (>0) depth. No depth write — the sky never
1200
+ // occludes anything.
1201
+ spec.depthTest = true;
1170
1202
  spec.depthWrite = false;
1203
+ spec.depthCompare = VK_COMPARE_OP_GREATER_OR_EQUAL;
1171
1204
  spec.cull = VK_CULL_MODE_NONE;
1172
1205
  spec.samples = sampleCount_;
1173
1206
  skyPipeline_ = buildPipeline(device_, skyPipelineLayout_, renderPass_,
@@ -1207,6 +1240,7 @@ void VulkanBackend::createTerrainPipeline() {
1207
1240
  spec.attributes = attrs;
1208
1241
  spec.depthTest = true;
1209
1242
  spec.depthWrite = true;
1243
+ spec.depthCompare = VK_COMPARE_OP_GREATER; // Reversed-Z
1210
1244
  spec.cull = VK_CULL_MODE_BACK_BIT;
1211
1245
  spec.samples = sampleCount_;
1212
1246
  return buildPipeline(device_, terrainPipelineLayout_, renderPass_,
@@ -2100,31 +2134,11 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
2100
2134
 
2101
2135
  VkCommandBuffer cmd = commandBuffers_[frameIndex_];
2102
2136
 
2103
- // ── Sky pass ──────────────────────────────────────────────────────────────
2104
- if (skyPipeline_ && skyDescSet_) {
2105
- SkyUBO skyU{};
2106
- const float* iv = glm::value_ptr(frame.invVP);
2107
- for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
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
-
2137
+ // ── Terrain pass (opaque, writes depth) ─────────────────────────────────────
2138
+ // Drawn FIRST so the sky pass below can depth-test against the terrain and
2139
+ // skip the expensive full-screen atmospheric raymarch on every covered pixel.
2140
+ // Wrapped in a lambda so its early-outs still fall through to the sky pass.
2141
+ auto drawTerrain = [&]() {
2128
2142
  if (frame.draws.empty() || !terrainSolidPipeline_ || !terrainDitherPipeline_) {
2129
2143
  return;
2130
2144
  }
@@ -2277,6 +2291,36 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
2277
2291
  uint32_t firstIndex = draw.indexByteOffset / sizeof(uint32_t);
2278
2292
  vkCmdDrawIndexed(cmd, draw.indexCount, 1, firstIndex, 0, 0);
2279
2293
  }
2294
+ }; // drawTerrain
2295
+ drawTerrain();
2296
+
2297
+ // ── Sky pass ────────────────────────────────────────────────────────────────
2298
+ // Drawn LAST and depth-tested (no write): the raymarch only executes on pixels
2299
+ // where no terrain was rendered (depth still at the cleared reversed-Z far
2300
+ // plane). This avoids shading the full screen and then overdrawing it.
2301
+ if (skyPipeline_ && skyDescSet_) {
2302
+ SkyUBO skyU{};
2303
+ const float* iv = glm::value_ptr(frame.invVP);
2304
+ for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
2305
+ skyU.cameraEcef[0] = frame.cameraEcef.x;
2306
+ skyU.cameraEcef[1] = frame.cameraEcef.y;
2307
+ skyU.cameraEcef[2] = frame.cameraEcef.z;
2308
+ float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
2309
+ frame.cameraEcef.y * frame.cameraEcef.y +
2310
+ frame.cameraEcef.z * frame.cameraEcef.z);
2311
+ if (cl > 0.0f) {
2312
+ skyU.lightDir[0] = frame.cameraEcef.x / cl;
2313
+ skyU.lightDir[1] = frame.cameraEcef.y / cl;
2314
+ skyU.lightDir[2] = frame.cameraEcef.z / cl;
2315
+ }
2316
+
2317
+ memcpy(skyUboMapped_, &skyU, sizeof(SkyUBO));
2318
+
2319
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline_);
2320
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipelineLayout_,
2321
+ 0, 1, &skyDescSet_, 0, nullptr);
2322
+ vkCmdDraw(cmd, 3, 1, 0, 0);
2323
+ }
2280
2324
  }
2281
2325
 
2282
2326
  void VulkanBackend::endFrame() {
@@ -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 halved from 16/4 to 8/2 — visually negligible at typical
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), saves ~half the
44
- // expensive `exp` calls per pixel.
45
- const int S = 8, L = 2;
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
- // • Sky drawn first (no depth write), terrain drawn after.
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
- // Halved from 16/4 to 8/2 — visually negligible at typical phone DPI
339
- // (atmospheric scattering banding is sub-pixel at this sample count).
340
- const int S=8,L=2;
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
- sd.depthCompareFunction = MTLCompareFunctionAlways;
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
- // Drain in-flight frames: wait until all GPU completion handlers have fired
598
- // before releasing the semaphore and the persistent buffers they reference.
599
- // Bounded wait — if a slot doesn't release in 1 s we are almost certainly
600
- // looking at a hung GPU command. Forcing release lets the app exit/teardown
601
- // cleanly rather than ANRing on backgrounding.
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
- dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW,
606
- (int64_t)NSEC_PER_SEC);
607
- if (dispatch_semaphore_wait(sem, deadline) != 0) {
608
- NSLog(@"[CesiumMetal] shutdown semaphore wait timed out at slot %d; "
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
- CFRelease(frameSemaphore_);
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
- dispatch_semaphore_signal((__bridge dispatch_semaphore_t)semPtr);
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.15",
3
+ "version": "1.2.17",
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",
@@ -41,6 +41,7 @@
41
41
  "android/fix-prefab.gradle",
42
42
  "android/gradle.properties",
43
43
  "android/CMakeLists.txt",
44
+ "android/cmake",
44
45
  "android/src",
45
46
  "example/react-native-cesium-example.jpg",
46
47
  "ios/**/*.h",
@@ -75,13 +76,13 @@
75
76
  "@semantic-release/changelog": "^6.0.3",
76
77
  "@semantic-release/git": "^10.0.1",
77
78
  "@types/jest": "^29.5.14",
78
- "@types/react": "19.2.14",
79
+ "@types/react": "19.2.17",
79
80
  "conventional-changelog-conventionalcommits": "^9.3.1",
80
81
  "nitrogen": "0.35.9",
81
82
  "react": "19.2.3",
82
- "react-native": "0.85.3",
83
- "react-native-builder-bob": "^0.41.0",
84
- "semantic-release": "^25.0.3",
83
+ "react-native": "0.86.0",
84
+ "react-native-builder-bob": "^0.43.0",
85
+ "semantic-release": "^25.0.5",
85
86
  "typescript": "^5.9.3"
86
87
  },
87
88
  "peerDependencies": {