react-native-cesium 1.0.1
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/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +308 -0
- package/ReactNativeCesium.podspec +54 -0
- package/android/CMakeLists.txt +146 -0
- package/android/build.gradle +178 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/assets/cacert.pem +3529 -0
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +487 -0
- package/android/src/main/cpp/CesiumBridgeAndroid.h +94 -0
- package/android/src/main/cpp/cpp-adapter.cpp +17 -0
- package/android/src/main/cpp/vulkan/VulkanBackend.cpp +1781 -0
- package/android/src/main/cpp/vulkan/VulkanBackend.h +190 -0
- package/android/src/main/cpp/vulkan/shaders/sky.frag +86 -0
- package/android/src/main/cpp/vulkan/shaders/sky.vert +9 -0
- package/android/src/main/cpp/vulkan/shaders/terrain.frag +144 -0
- package/android/src/main/cpp/vulkan/shaders/terrain.vert +25 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +84 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +225 -0
- package/android/src/main/java/com/reactnativecesium/ReactNativeCesiumPackage.kt +28 -0
- package/cpp/engine/CesiumEngine.cpp +413 -0
- package/cpp/engine/CesiumEngine.hpp +90 -0
- package/cpp/engine/DeletionQueue.cpp +28 -0
- package/cpp/engine/DeletionQueue.hpp +28 -0
- package/cpp/engine/GlobeCamera.cpp +181 -0
- package/cpp/engine/GlobeCamera.hpp +64 -0
- package/cpp/engine/GltfToMesh.cpp +154 -0
- package/cpp/engine/GltfToMesh.hpp +20 -0
- package/cpp/engine/ResourcePreparer.cpp +224 -0
- package/cpp/engine/ResourcePreparer.hpp +96 -0
- package/cpp/engine/TaskProcessor.cpp +61 -0
- package/cpp/engine/TaskProcessor.hpp +43 -0
- package/cpp/engine/TileLifecycleManager.cpp +30 -0
- package/cpp/engine/TileLifecycleManager.hpp +25 -0
- package/cpp/fmt/format.h +9 -0
- package/cpp/renderer/IGPUBackend.hpp +22 -0
- package/cpp/renderer/RenderTypes.hpp +112 -0
- package/cpp/renderer/ShaderTypes.hpp +29 -0
- package/example/README.md +43 -0
- package/example/react-native-cesium-example.jpg +0 -0
- package/ios/CesiumBridge.h +55 -0
- package/ios/CesiumBridge.mm +393 -0
- package/ios/HybridCesiumView.swift +299 -0
- package/ios/ReactNativeCesium-umbrella.h +16 -0
- package/ios/metal/MetalBackend.h +82 -0
- package/ios/metal/MetalBackend.mm +839 -0
- package/ios/react_native_cesium_ensure_native.rb +12 -0
- package/ios/react_native_cesium_post_install.rb +30 -0
- package/lib/commonjs/CesiumView.js +13 -0
- package/lib/commonjs/CesiumView.js.map +1 -0
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/cesium-view.nitro.js +6 -0
- package/lib/commonjs/specs/cesium-view.nitro.js.map +1 -0
- package/lib/module/CesiumView.js +9 -0
- package/lib/module/CesiumView.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/cesium-view.nitro.js +4 -0
- package/lib/module/specs/cesium-view.nitro.js.map +1 -0
- package/lib/typescript/src/CesiumView.d.ts +5 -0
- package/lib/typescript/src/CesiumView.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +43 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -0
- package/nitro.json +30 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/ReactNativeCesium+autolinking.cmake +83 -0
- package/nitrogen/generated/android/ReactNativeCesium+autolinking.gradle +27 -0
- package/nitrogen/generated/android/ReactNativeCesiumOnLoad.cpp +58 -0
- package/nitrogen/generated/android/ReactNativeCesiumOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JCameraState.hpp +81 -0
- package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +81 -0
- package/nitrogen/generated/android/c++/JFunc_void_CesiumMetrics.hpp +78 -0
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +178 -0
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +83 -0
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +92 -0
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CesiumMetrics.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +126 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/ReactNativeCesiumOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewManager.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewStateUpdater.kt +23 -0
- package/nitrogen/generated/ios/ReactNativeCesium+autolinking.rb +60 -0
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.cpp +57 -0
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +164 -0
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Umbrella.hpp +53 -0
- package/nitrogen/generated/ios/ReactNativeCesiumAutolinking.mm +33 -0
- package/nitrogen/generated/ios/ReactNativeCesiumAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +157 -0
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +167 -0
- package/nitrogen/generated/ios/swift/CameraState.swift +59 -0
- package/nitrogen/generated/ios/swift/CesiumMetrics.swift +59 -0
- package/nitrogen/generated/ios/swift/Func_void_CameraState.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CesiumMetrics.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +65 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +306 -0
- package/nitrogen/generated/shared/c++/CameraState.hpp +107 -0
- package/nitrogen/generated/shared/c++/CesiumMetrics.hpp +107 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +42 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +90 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +182 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +122 -0
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +19 -0
- package/package.json +140 -0
- package/react-native.config.js +16 -0
- package/scripts/cesium/build.mjs +901 -0
- package/scripts/cesium/clean.mjs +35 -0
- package/scripts/cesium/clone.mjs +32 -0
- package/scripts/cesium/ensure-native.mjs +116 -0
- package/scripts/cesium/vcpkg-ports/zstd/fix-emscripten-and-clang-cl.patch +31 -0
- package/scripts/cesium/vcpkg-ports/zstd/fix-windows-rc-compile.patch +13 -0
- package/scripts/cesium/vcpkg-ports/zstd/no-static-suffix.patch +13 -0
- package/scripts/cesium/vcpkg-ports/zstd/portfile.cmake +77 -0
- package/scripts/cesium/vcpkg-ports/zstd/usage +4 -0
- package/scripts/cesium/vcpkg-ports/zstd/vcpkg.json +23 -0
- package/scripts/cesium/vcpkg-triplets/arm64-ios-simulator.cmake +10 -0
- package/scripts/cesium/vcpkg-triplets/arm64-ios.cmake +14 -0
- package/src/CesiumView.tsx +13 -0
- package/src/index.ts +7 -0
- package/src/specs/cesium-view.nitro.ts +51 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
// MetalBackend.mm — iOS Metal rendering backend
|
|
2
|
+
//
|
|
3
|
+
// Architecture:
|
|
4
|
+
// • RTC (Relative-to-Centre) tile-local float positions on the GPU.
|
|
5
|
+
// Vertex positions are stored relative to each tile's RTC centre so that
|
|
6
|
+
// the values remain small (tile radius, not full ECEF magnitude), giving
|
|
7
|
+
// full float32 precision for geometry at any altitude.
|
|
8
|
+
// • Per-tile MVP matrix computed on the CPU in double precision:
|
|
9
|
+
// MVP = P_double * R_double * T_double(rtcCentre − camera)
|
|
10
|
+
// then cast to float32 for the GPU. This avoids the large float32
|
|
11
|
+
// cancellation error that would arise from computing the camera offset
|
|
12
|
+
// on the GPU.
|
|
13
|
+
// • Vertex altitude attribute (metres, computed from double-precision Bowring
|
|
14
|
+
// formula on the CPU). Passed straight to the fragment shader — eliminates
|
|
15
|
+
// the ±50 m float32 catastrophic-cancellation error of
|
|
16
|
+
// ellipsoidHeightMeters(wp) in the old shader.
|
|
17
|
+
// • Single merged vertex + index buffer uploaded each frame.
|
|
18
|
+
// • Reversed-Z infinite projection: depth clear = 0, compare = GREATER.
|
|
19
|
+
// • Sky drawn first (no depth write), terrain drawn after.
|
|
20
|
+
// • Triple-buffered persistent MTLBuffers: one slot per in-flight frame.
|
|
21
|
+
|
|
22
|
+
#import <Foundation/Foundation.h>
|
|
23
|
+
#import <Metal/Metal.h>
|
|
24
|
+
#import <QuartzCore/CAMetalLayer.h>
|
|
25
|
+
#import <dispatch/dispatch.h>
|
|
26
|
+
|
|
27
|
+
#include "MetalBackend.h"
|
|
28
|
+
|
|
29
|
+
#include <glm/gtc/type_ptr.hpp>
|
|
30
|
+
#include <cstring> // memcpy
|
|
31
|
+
#include <algorithm> // std::max
|
|
32
|
+
|
|
33
|
+
// ── CPU-side uniform structs ──────────────────────────────────────────────────
|
|
34
|
+
// float3 in MSL is 16-byte aligned (same as float4), so we always use float[4]
|
|
35
|
+
// on the C++ side to keep sizes identical between CPU and GPU structs.
|
|
36
|
+
|
|
37
|
+
// Per-draw vertex uniforms: the tile's RTC MVP matrix (double-precision on CPU,
|
|
38
|
+
// cast to float32 for the GPU).
|
|
39
|
+
struct PerDrawVertexUniformsCPP {
|
|
40
|
+
float mvpMatrix[16]; // column-major float4x4 (64 bytes)
|
|
41
|
+
}; // total 64 bytes
|
|
42
|
+
|
|
43
|
+
struct SkyUniforms {
|
|
44
|
+
float invVP[16]; // column-major float4x4 (64 bytes)
|
|
45
|
+
float cameraEcef[4]; // xyz used, w=0 (16 bytes)
|
|
46
|
+
float lightDir[4]; // xyz used, w=0 (16 bytes)
|
|
47
|
+
}; // total 96 bytes
|
|
48
|
+
|
|
49
|
+
// Per-draw fragment uniforms: all overlay/water-mask parameters plus the RTC
|
|
50
|
+
// centre and camera-in-tile-space needed to reconstruct world position.
|
|
51
|
+
struct OverlayParamsCPP {
|
|
52
|
+
uint32_t hasOverlay; // offset 0
|
|
53
|
+
uint32_t isEllipsoidFallback; // offset 4
|
|
54
|
+
uint32_t isOnlyWater; // offset 8
|
|
55
|
+
uint32_t hasWaterMask; // offset 12
|
|
56
|
+
float translation[2]; // offset 16
|
|
57
|
+
float scale[2]; // offset 24
|
|
58
|
+
float wmWest; // offset 32
|
|
59
|
+
float wmSouth; // offset 36
|
|
60
|
+
float wmEast; // offset 40
|
|
61
|
+
float wmNorth; // offset 44
|
|
62
|
+
float wmTransX; // offset 48
|
|
63
|
+
float wmTransY; // offset 52
|
|
64
|
+
float wmScale; // offset 56
|
|
65
|
+
float _pad1; // offset 60
|
|
66
|
+
// RTC centre in absolute ECEF (float3). Fragment shader reconstructs world
|
|
67
|
+
// position as: wp = in.localPos + rtcCentre (see shader).
|
|
68
|
+
float rtcCenterX; // offset 64
|
|
69
|
+
float rtcCenterY; // offset 68
|
|
70
|
+
float rtcCenterZ; // offset 72
|
|
71
|
+
float _pad2; // offset 76
|
|
72
|
+
// Camera position in tile-local space (camera_ECEF − rtcCentre), cast to
|
|
73
|
+
// float32. Used for the view direction vector in lighting.
|
|
74
|
+
float cameraTilespaceX; // offset 80
|
|
75
|
+
float cameraTilespaceY; // offset 84
|
|
76
|
+
float cameraTilespaceZ; // offset 88
|
|
77
|
+
float _pad3; // offset 92
|
|
78
|
+
}; // total 96 bytes
|
|
79
|
+
|
|
80
|
+
// ── Shader source ─────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
static NSString* const kTerrainShaderSrc = @R"MSL(
|
|
83
|
+
#include <metal_stdlib>
|
|
84
|
+
using namespace metal;
|
|
85
|
+
|
|
86
|
+
// ── Vertex shader ────────────────────────────────────────────────────────────
|
|
87
|
+
// RTC: vertex positions are stored relative to each tile's RTC centre (small
|
|
88
|
+
// floats). The per-tile MVP matrix was computed on the CPU entirely in double
|
|
89
|
+
// precision, so the large camera↔centre translation is represented accurately.
|
|
90
|
+
struct PerDrawVertexUniforms {
|
|
91
|
+
float4x4 mvpMatrix; // per-tile MVP: P_d * R_d * T_d(rtcCentre − camera) → float
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
struct TerrainVOut {
|
|
95
|
+
float4 position [[position]];
|
|
96
|
+
float3 localPos; // tile-local position (vertex − rtcCentre, float32)
|
|
97
|
+
float altitude; // ellipsoid height metres from vertex attribute (double source)
|
|
98
|
+
float2 uv;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
vertex TerrainVOut terrainVertex(
|
|
102
|
+
uint vid [[vertex_id]],
|
|
103
|
+
const device packed_float3* pos [[buffer(0)]],
|
|
104
|
+
const device float* altitudes [[buffer(1)]],
|
|
105
|
+
constant PerDrawVertexUniforms& u [[buffer(2)]],
|
|
106
|
+
const device packed_float2* uvs [[buffer(3)]])
|
|
107
|
+
{
|
|
108
|
+
TerrainVOut o;
|
|
109
|
+
float3 lp = float3(pos[vid]);
|
|
110
|
+
o.position = u.mvpMatrix * float4(lp, 1.0f);
|
|
111
|
+
o.localPos = lp;
|
|
112
|
+
o.altitude = altitudes[vid];
|
|
113
|
+
o.uv = float2(uvs[vid]);
|
|
114
|
+
return o;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Fragment shader ──────────────────────────────────────────────────────────
|
|
118
|
+
struct OverlayParams {
|
|
119
|
+
uint hasOverlay;
|
|
120
|
+
uint isEllipsoidFallback;
|
|
121
|
+
uint isOnlyWater;
|
|
122
|
+
uint hasWaterMask;
|
|
123
|
+
float translation[2];
|
|
124
|
+
float scale[2];
|
|
125
|
+
float wmWest, wmSouth, wmEast, wmNorth;
|
|
126
|
+
float wmTransX, wmTransY;
|
|
127
|
+
float wmScale;
|
|
128
|
+
float _pad1;
|
|
129
|
+
// RTC centre in ECEF (float). wp = in.localPos + rtcCentre gives a world
|
|
130
|
+
// position with ~0.75 m ULP (vs ~4 m for eyeRelPos + cameraEcef at altitude).
|
|
131
|
+
float rtcCenterX, rtcCenterY, rtcCenterZ;
|
|
132
|
+
float _pad2;
|
|
133
|
+
// Camera position in tile-local space: camera_ECEF − rtcCentre.
|
|
134
|
+
// Pre-computed on CPU in double, cast to float — used for lighting vd vector.
|
|
135
|
+
float cameraTilespaceX, cameraTilespaceY, cameraTilespaceZ;
|
|
136
|
+
float _pad3;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
float3 hypsometricColor(float alt, float steep) {
|
|
140
|
+
float3 cDeep=float3(.03f,.20f,.46f),cShallow=float3(.08f,.44f,.78f);
|
|
141
|
+
float3 cLow=float3(.46f,.54f,.30f),cFoot=float3(.62f,.55f,.32f);
|
|
142
|
+
float3 cHigh=float3(.48f,.38f,.28f),cAlp=float3(.52f,.50f,.48f),cSnow=float3(.90f,.92f,.94f);
|
|
143
|
+
if(alt<0.f)return mix(cDeep,cShallow,smoothstep(-4500.f,-20.f,alt));
|
|
144
|
+
float3 c=cLow;
|
|
145
|
+
c=mix(c,cFoot,smoothstep(120.f,900.f,alt));
|
|
146
|
+
c=mix(c,cHigh,smoothstep(750.f,2000.f,alt));
|
|
147
|
+
c=mix(c,cAlp,smoothstep(1700.f,3600.f,alt));
|
|
148
|
+
c=mix(c,cSnow,smoothstep(3200.f,5200.f,alt));
|
|
149
|
+
float3 scree=float3(.38f,.35f,.32f);
|
|
150
|
+
float rock=saturate(steep*1.08f)*smoothstep(35.f,140.f,alt);
|
|
151
|
+
return mix(c,scree,rock);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fragment float4 terrainFragment(
|
|
155
|
+
TerrainVOut in [[stage_in]],
|
|
156
|
+
constant OverlayParams& ov [[buffer(0)]],
|
|
157
|
+
texture2d<float> overlayTex [[texture(0)]],
|
|
158
|
+
texture2d<float> waterMaskTex [[texture(1)]])
|
|
159
|
+
{
|
|
160
|
+
constexpr sampler s(filter::linear, address::clamp_to_edge);
|
|
161
|
+
|
|
162
|
+
// ── World position (RTC reconstruction) ──────────────────────────────────
|
|
163
|
+
// wp = tile-local position + RTC centre (both float32, sum at ~6.4 Mm).
|
|
164
|
+
// Precision: ~0.75 m ULP — much better than eyeRelPos + cameraEcef which
|
|
165
|
+
// had ~4 m ULP at typical camera altitudes. Used for lat/lon computation
|
|
166
|
+
// (water-mask UV) and lighting surface normal (up direction).
|
|
167
|
+
float3 rtcCentre = float3(ov.rtcCenterX, ov.rtcCenterY, ov.rtcCenterZ);
|
|
168
|
+
float3 wp = in.localPos + rtcCentre;
|
|
169
|
+
float pxy = sqrt(wp.x*wp.x + wp.y*wp.y);
|
|
170
|
+
float lon = atan2(wp.y, wp.x);
|
|
171
|
+
|
|
172
|
+
// Geodetic latitude (Bowring iteration in float32 — good to ~0.1 m for
|
|
173
|
+
// water-mask UV, which is all we need here).
|
|
174
|
+
const float a_e=6378137.f, b_e=6356752.31424518f;
|
|
175
|
+
const float ep2=(a_e*a_e-b_e*b_e)/(b_e*b_e), e2=1.f-(b_e*b_e)/(a_e*a_e);
|
|
176
|
+
float theta_g=atan2(wp.z*a_e, pxy*b_e);
|
|
177
|
+
float sg=sin(theta_g), cg=cos(theta_g);
|
|
178
|
+
float lat=atan2(wp.z+ep2*b_e*sg*sg*sg, pxy-e2*a_e*cg*cg*cg);
|
|
179
|
+
|
|
180
|
+
// ── Water-mask UV ─────────────────────────────────────────────────────────
|
|
181
|
+
// Cesium GEOGRAPHIC UV convention:
|
|
182
|
+
// U: 0=west … 1=east (unchanged for Metal texture)
|
|
183
|
+
// V: 0=south … 1=north (geographic, must be flipped for texture)
|
|
184
|
+
float wmU = (lon - ov.wmWest) / max(ov.wmEast - ov.wmWest, 1e-7f);
|
|
185
|
+
float v_geo = (lat - ov.wmSouth) / max(ov.wmNorth - ov.wmSouth, 1e-7f);
|
|
186
|
+
float2 geoUV = float2(wmU, v_geo) * ov.wmScale + float2(ov.wmTransX, ov.wmTransY);
|
|
187
|
+
float2 wmUV = clamp(float2(geoUV.x, 1.0f - geoUV.y), 0.0f, 1.0f);
|
|
188
|
+
|
|
189
|
+
float waterVal = (ov.isOnlyWater != 0u)
|
|
190
|
+
? 1.0f
|
|
191
|
+
: waterMaskTex.sample(s, wmUV).r;
|
|
192
|
+
|
|
193
|
+
float3 lit;
|
|
194
|
+
|
|
195
|
+
if (ov.hasOverlay != 0u) {
|
|
196
|
+
float2 texUV = in.uv * float2(ov.scale[0], ov.scale[1])
|
|
197
|
+
+ float2(ov.translation[0], ov.translation[1]);
|
|
198
|
+
texUV.y = 1.0f - texUV.y;
|
|
199
|
+
lit = overlayTex.sample(s, texUV).rgb;
|
|
200
|
+
} else {
|
|
201
|
+
// ── Lighting ──────────────────────────────────────────────────────────
|
|
202
|
+
// Normals from screen-space derivatives of tile-local position.
|
|
203
|
+
// (Using localPos rather than eyeRelPos makes no difference for the
|
|
204
|
+
// direction of the normal — only the scale changes.)
|
|
205
|
+
float3 dpdx = dfdx(in.localPos), dpdy = dfdy(in.localPos);
|
|
206
|
+
float3 rawN = cross(dpdx, dpdy);
|
|
207
|
+
float nLen = length(rawN);
|
|
208
|
+
float3 n = (nLen > 1e-8f) ? rawN / nLen : float3(0.f, 0.f, 1.f);
|
|
209
|
+
|
|
210
|
+
// View direction: camera−tile-centre minus vertex−tile-centre.
|
|
211
|
+
float3 cameraLocal = float3(ov.cameraTilespaceX, ov.cameraTilespaceY, ov.cameraTilespaceZ);
|
|
212
|
+
float3 vd = normalize(cameraLocal - in.localPos);
|
|
213
|
+
if (dot(n, vd) < 0.f) n = -n;
|
|
214
|
+
|
|
215
|
+
float3 gu = normalize(wp); // surface up (towards space)
|
|
216
|
+
float3 sun = normalize(gu + float3(0.3f, 0.2f, 0.1f));
|
|
217
|
+
float diff = saturate(dot(n, sun));
|
|
218
|
+
float amb = 0.18f;
|
|
219
|
+
float rim = saturate(1.f - dot(n, vd)) * 0.06f;
|
|
220
|
+
float steep = 1.f - saturate(abs(dot(n, gu)));
|
|
221
|
+
|
|
222
|
+
// ── Altitude (from vertex attribute — double-precision source) ────────
|
|
223
|
+
// In.altitude was computed on the CPU with Bowring formula in double
|
|
224
|
+
// (sub-mm accuracy). This completely avoids the ±50 m float32
|
|
225
|
+
// catastrophic cancellation of the old pxy/cos(lat) − N in the shader.
|
|
226
|
+
// The ellipsoid fallback is tessellated at −20 m → renders as ocean.
|
|
227
|
+
float rawAlt = in.altitude;
|
|
228
|
+
|
|
229
|
+
float3 oceanBlue = float3(0.04f, 0.26f, 0.52f);
|
|
230
|
+
// When a real water-mask texture is present it controls ocean pixels via
|
|
231
|
+
// waterVal, so clamp land altitude ≥ 0 to prevent residual float noise
|
|
232
|
+
// from ocean vertices colouring land blue. Without a water mask, use
|
|
233
|
+
// raw altitude so bathymetric depth renders as ocean.
|
|
234
|
+
float landAlt = (ov.hasWaterMask != 0u) ? max(rawAlt, 0.0f) : rawAlt;
|
|
235
|
+
float3 landLit = hypsometricColor(landAlt, steep) * (amb + diff * 0.72f + rim);
|
|
236
|
+
lit = mix(landLit, oceanBlue, waterVal);
|
|
237
|
+
|
|
238
|
+
// 1 km lat/lon grid — fades when sub-pixel to suppress Moiré.
|
|
239
|
+
float gridLat = 1000.f / 6371000.f;
|
|
240
|
+
float cosLat = max(cos(lat), 0.001f);
|
|
241
|
+
float gridLon = gridLat / cosLat;
|
|
242
|
+
float wLat = fwidth(lat) * 1.5f, wLon = fwidth(lon) * 1.5f;
|
|
243
|
+
float gridVis = clamp(min(gridLat / max(wLat, 1e-9f),
|
|
244
|
+
gridLon / max(wLon, 1e-9f)), 0.f, 1.f);
|
|
245
|
+
float latCell = lat - gridLat * floor(lat / gridLat);
|
|
246
|
+
float lonCell = lon - gridLon * floor(lon / gridLon);
|
|
247
|
+
float dLat = min(latCell, gridLat - latCell);
|
|
248
|
+
float dLon = min(lonCell, gridLon - lonCell);
|
|
249
|
+
float gridA = max(1.f - smoothstep(0.f, wLat, dLat),
|
|
250
|
+
1.f - smoothstep(0.f, wLon, dLon)) * gridVis;
|
|
251
|
+
lit = mix(lit, float3(0.15f, 0.15f, 0.15f), gridA * 0.6f);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Coastline outline from water mask (magenta for easy diagnostics).
|
|
255
|
+
if (ov.isEllipsoidFallback == 0u) {
|
|
256
|
+
float dWater = fwidth(waterVal);
|
|
257
|
+
float coastPx = abs(waterVal - 0.5f) / max(dWater * 0.5f, 0.01f);
|
|
258
|
+
float coastA = 1.f - smoothstep(0.f, 1.5f, coastPx);
|
|
259
|
+
lit = mix(lit, float3(0.18f, 0.14f, 0.10f), coastA * 0.7f);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return float4(lit, 1.f);
|
|
263
|
+
}
|
|
264
|
+
)MSL";
|
|
265
|
+
|
|
266
|
+
static NSString* const kSkyShaderSrc = @R"MSL(
|
|
267
|
+
#include <metal_stdlib>
|
|
268
|
+
using namespace metal;
|
|
269
|
+
|
|
270
|
+
struct SkyUniforms {
|
|
271
|
+
float4x4 invVP;
|
|
272
|
+
float4 cameraEcef; // xyz used, w=0
|
|
273
|
+
float4 lightDir; // xyz used, w=0
|
|
274
|
+
};
|
|
275
|
+
struct SkyVOut {
|
|
276
|
+
float4 position [[position]];
|
|
277
|
+
float2 ndc;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
vertex SkyVOut skyVertex(uint vid [[vertex_id]],
|
|
281
|
+
constant SkyUniforms& u [[buffer(0)]])
|
|
282
|
+
{
|
|
283
|
+
float2 ps[3]={float2(-1,-1),float2(3,-1),float2(-1,3)};
|
|
284
|
+
SkyVOut o;
|
|
285
|
+
o.position=float4(ps[vid],0,1);
|
|
286
|
+
o.ndc=ps[vid];
|
|
287
|
+
return o;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
constant float ER=6371000.f,AR=ER+111000.f;
|
|
291
|
+
constant float3 RAY=float3(3.2e-6f,8.5e-6f,38e-6f),MIE3=float3(1.8e-6f,1.8e-6f,1.8e-6f);
|
|
292
|
+
constant float MG=.76f,LINT=18.f;
|
|
293
|
+
float2 rsi(float3 o,float3 d,float r){
|
|
294
|
+
float a=dot(d,d),b=2*dot(d,o),c=dot(o,o)-r*r,disc=b*b-4*a*c;
|
|
295
|
+
if(disc<0)return float2(-1);
|
|
296
|
+
float sq=sqrt(disc);return float2((-b-sq)/(2*a),(-b+sq)/(2*a));
|
|
297
|
+
}
|
|
298
|
+
fragment float4 skyFragment(SkyVOut in [[stage_in]],
|
|
299
|
+
constant SkyUniforms& u [[buffer(0)]])
|
|
300
|
+
{
|
|
301
|
+
float4 wn=u.invVP*float4(in.ndc,-1,1),wf=u.invVP*float4(in.ndc,1,1);
|
|
302
|
+
float3 near3=wn.xyz/wn.w,far3=wf.xyz/wf.w;
|
|
303
|
+
float3 rd=normalize(far3-near3);
|
|
304
|
+
float3 org=u.cameraEcef.xyz;
|
|
305
|
+
float2 at=rsi(org,rd,AR);
|
|
306
|
+
if(at.y<0)return float4(0,0,0,1);
|
|
307
|
+
float2 er=rsi(org,rd,ER);
|
|
308
|
+
float tmax=at.y;if(er.x>0)tmax=min(tmax,er.x);
|
|
309
|
+
float tmin=max(at.x,0.f);if(tmin>=tmax)return float4(0,0,0,1);
|
|
310
|
+
const int S=16,L=4;
|
|
311
|
+
float ss=(tmax-tmin)/float(S);
|
|
312
|
+
float3 rayl=0,miel=0;float oR=0,oM=0;
|
|
313
|
+
for(int i=0;i<S;++i){
|
|
314
|
+
float t=tmin+(float(i)+.5f)*ss;
|
|
315
|
+
float3 sp=org+rd*t;float h=length(sp)-ER;
|
|
316
|
+
float hr=exp(-h/10000.f)*ss,hm=exp(-h/3200.f)*ss;
|
|
317
|
+
oR+=hr;oM+=hm;
|
|
318
|
+
float2 lh=rsi(sp,u.lightDir.xyz,AR);
|
|
319
|
+
if(lh.y>0){
|
|
320
|
+
float ls=lh.y/float(L);float lR=0,lM=0;bool bl=false;
|
|
321
|
+
for(int j=0;j<L;++j){
|
|
322
|
+
float3 lsp=sp+u.lightDir.xyz*((float(j)+.5f)*ls);
|
|
323
|
+
float lh2=length(lsp)-ER;if(lh2<0){bl=true;break;}
|
|
324
|
+
lR+=exp(-lh2/10000.f)*ls;lM+=exp(-lh2/3200.f)*ls;
|
|
325
|
+
}
|
|
326
|
+
if(!bl){float3 tau=RAY*(oR+lR)+MIE3*1.1f*(oM+lM);float3 att=exp(-tau);rayl+=hr*att;miel+=hm*att;}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
float cosT=dot(rd,u.lightDir.xyz),cos2=cosT*cosT;
|
|
330
|
+
float rPh=.75f*(1+cos2);
|
|
331
|
+
float g2=MG*MG,mPh=1.5f*((1-g2)/(2+g2))*(1+cos2)/pow(1+g2-2*MG*cosT,1.5f);
|
|
332
|
+
float3 col=LINT*(rayl*RAY*rPh+miel*MIE3*mPh);
|
|
333
|
+
col=1-exp(-col*1.1f);col*=float3(.18f,.45f,1.f);
|
|
334
|
+
float3 nu=normalize(org);float up=saturate(dot(rd,nu));
|
|
335
|
+
float camH=max(0.f,length(org)-ER);
|
|
336
|
+
float hStr=saturate(1-camH/35000.f);
|
|
337
|
+
col=mix(col,float3(.40f,.62f,.96f),(1-smoothstep(.05f,.45f,up))*.40f*hStr);
|
|
338
|
+
return float4(col,1.f);
|
|
339
|
+
}
|
|
340
|
+
)MSL";
|
|
341
|
+
|
|
342
|
+
// ── Helper: ensure a persistent buffer is large enough, then memcpy into it ──
|
|
343
|
+
// Grows by doubling (rounded up to 4 KB) to amortise reallocation cost.
|
|
344
|
+
// Returns a non-owning bridge pointer to the buffer (ownership in *pBuf).
|
|
345
|
+
static id<MTLBuffer> ensureBuffer(void** pBuf, size_t* pCap,
|
|
346
|
+
size_t needed, const void* data,
|
|
347
|
+
id<MTLDevice> dev) {
|
|
348
|
+
if (needed == 0) return nil;
|
|
349
|
+
if (needed > *pCap) {
|
|
350
|
+
size_t newCap = std::max(needed, *pCap > 0 ? *pCap * 2 : needed);
|
|
351
|
+
newCap = (newCap + 4095UL) & ~4095UL; // round up to 4 KB page
|
|
352
|
+
if (*pBuf) CFRelease(*pBuf);
|
|
353
|
+
id<MTLBuffer> b = [dev newBufferWithLength:newCap
|
|
354
|
+
options:MTLResourceStorageModeShared];
|
|
355
|
+
*pBuf = (__bridge_retained void*)b;
|
|
356
|
+
*pCap = newCap;
|
|
357
|
+
}
|
|
358
|
+
id<MTLBuffer> b = (__bridge id<MTLBuffer>)*pBuf;
|
|
359
|
+
memcpy(b.contents, data, needed);
|
|
360
|
+
return b;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
namespace reactnativecesium {
|
|
364
|
+
|
|
365
|
+
MetalBackend::MetalBackend()
|
|
366
|
+
: device_(nullptr), commandQueue_(nullptr), metalLayer_(nullptr),
|
|
367
|
+
terrainPipeline_(nullptr), skyPipeline_(nullptr),
|
|
368
|
+
terrainDepthState_(nullptr), skyDepthState_(nullptr),
|
|
369
|
+
depthTexture_(nullptr), fallbackTexture_(nullptr),
|
|
370
|
+
fallbackWaterMaskTexture_(nullptr),
|
|
371
|
+
currentDrawable_(nullptr), currentCommandBuffer_(nullptr),
|
|
372
|
+
currentEncoder_(nullptr),
|
|
373
|
+
frameSemaphore_(nullptr), frameIndex_(0) {
|
|
374
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
375
|
+
vtxBufs_[i] = idxBufs_[i] = uvBufs_[i] = altBufs_[i] = nullptr;
|
|
376
|
+
vtxCaps_[i] = idxCaps_[i] = uvCaps_[i] = altCaps_[i] = 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
MetalBackend::~MetalBackend() { shutdown(); }
|
|
381
|
+
|
|
382
|
+
void MetalBackend::initialize(void* nativeSurface, int width, int height) {
|
|
383
|
+
viewportWidth_ = width;
|
|
384
|
+
viewportHeight_ = height;
|
|
385
|
+
|
|
386
|
+
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
|
|
387
|
+
device_ = (__bridge_retained void*)dev;
|
|
388
|
+
commandQueue_ = (__bridge_retained void*)[dev newCommandQueue];
|
|
389
|
+
metalLayer_ = nativeSurface; // weak — owned by CAMetalLayer
|
|
390
|
+
|
|
391
|
+
// Semaphore starts at kMaxFramesInFlight; each beginFrame decrements it and
|
|
392
|
+
// each GPU completion handler increments it.
|
|
393
|
+
dispatch_semaphore_t sem = dispatch_semaphore_create(kMaxFramesInFlight);
|
|
394
|
+
frameSemaphore_ = (__bridge_retained void*)sem;
|
|
395
|
+
|
|
396
|
+
CAMetalLayer* layer = (__bridge CAMetalLayer*)metalLayer_;
|
|
397
|
+
layer.device = dev;
|
|
398
|
+
layer.framebufferOnly = YES;
|
|
399
|
+
|
|
400
|
+
buildRenderPipelines();
|
|
401
|
+
createDepthTexture();
|
|
402
|
+
createFallbackTexture();
|
|
403
|
+
createWaterMaskFallback();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
void MetalBackend::buildRenderPipelines() {
|
|
407
|
+
if (!device_) return;
|
|
408
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
409
|
+
CAMetalLayer* layer = (__bridge CAMetalLayer*)metalLayer_;
|
|
410
|
+
const MTLPixelFormat colorFmt = layer.pixelFormat;
|
|
411
|
+
|
|
412
|
+
NSError* err = nil;
|
|
413
|
+
|
|
414
|
+
if (terrainPipeline_) {
|
|
415
|
+
CFRelease(terrainPipeline_);
|
|
416
|
+
terrainPipeline_ = nullptr;
|
|
417
|
+
}
|
|
418
|
+
if (skyPipeline_) {
|
|
419
|
+
CFRelease(skyPipeline_);
|
|
420
|
+
skyPipeline_ = nullptr;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
id<MTLLibrary> tLib = [dev newLibraryWithSource:kTerrainShaderSrc
|
|
424
|
+
options:nil error:&err];
|
|
425
|
+
if (err) NSLog(@"[MetalBackend] terrain shader error: %@", err);
|
|
426
|
+
|
|
427
|
+
MTLRenderPipelineDescriptor* tDesc = [MTLRenderPipelineDescriptor new];
|
|
428
|
+
tDesc.vertexFunction = [tLib newFunctionWithName:@"terrainVertex"];
|
|
429
|
+
tDesc.fragmentFunction = [tLib newFunctionWithName:@"terrainFragment"];
|
|
430
|
+
tDesc.colorAttachments[0].pixelFormat = colorFmt;
|
|
431
|
+
tDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float;
|
|
432
|
+
tDesc.rasterSampleCount = (NSUInteger)std::max(1, sampleCount_);
|
|
433
|
+
terrainPipeline_ = (__bridge_retained void*)
|
|
434
|
+
[dev newRenderPipelineStateWithDescriptor:tDesc error:&err];
|
|
435
|
+
if (err) NSLog(@"[MetalBackend] terrain pipeline error: %@", err);
|
|
436
|
+
|
|
437
|
+
id<MTLLibrary> sLib = [dev newLibraryWithSource:kSkyShaderSrc
|
|
438
|
+
options:nil error:&err];
|
|
439
|
+
if (err) NSLog(@"[MetalBackend] sky shader error: %@", err);
|
|
440
|
+
|
|
441
|
+
MTLRenderPipelineDescriptor* sDesc = [MTLRenderPipelineDescriptor new];
|
|
442
|
+
sDesc.vertexFunction = [sLib newFunctionWithName:@"skyVertex"];
|
|
443
|
+
sDesc.fragmentFunction = [sLib newFunctionWithName:@"skyFragment"];
|
|
444
|
+
sDesc.colorAttachments[0].pixelFormat = colorFmt;
|
|
445
|
+
sDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float;
|
|
446
|
+
sDesc.rasterSampleCount = (NSUInteger)std::max(1, sampleCount_);
|
|
447
|
+
skyPipeline_ = (__bridge_retained void*)
|
|
448
|
+
[dev newRenderPipelineStateWithDescriptor:sDesc error:&err];
|
|
449
|
+
if (err) NSLog(@"[MetalBackend] sky pipeline error: %@", err);
|
|
450
|
+
|
|
451
|
+
if (!terrainDepthState_) {
|
|
452
|
+
MTLDepthStencilDescriptor* dd = [MTLDepthStencilDescriptor new];
|
|
453
|
+
dd.depthCompareFunction = MTLCompareFunctionGreater;
|
|
454
|
+
dd.depthWriteEnabled = YES;
|
|
455
|
+
terrainDepthState_ = (__bridge_retained void*)
|
|
456
|
+
[dev newDepthStencilStateWithDescriptor:dd];
|
|
457
|
+
}
|
|
458
|
+
if (!skyDepthState_) {
|
|
459
|
+
MTLDepthStencilDescriptor* sd = [MTLDepthStencilDescriptor new];
|
|
460
|
+
sd.depthCompareFunction = MTLCompareFunctionAlways;
|
|
461
|
+
sd.depthWriteEnabled = NO;
|
|
462
|
+
skyDepthState_ = (__bridge_retained void*)
|
|
463
|
+
[dev newDepthStencilStateWithDescriptor:sd];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
void MetalBackend::setMsaaSampleCount(int sc) {
|
|
468
|
+
int n = sc;
|
|
469
|
+
if (n != 2 && n != 4) n = 1;
|
|
470
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
471
|
+
if (dev && n > 1 && ![dev supportsTextureSampleCount:(NSUInteger)n]) n = 1;
|
|
472
|
+
if (n == sampleCount_) return;
|
|
473
|
+
|
|
474
|
+
if (frameSemaphore_) {
|
|
475
|
+
dispatch_semaphore_t sem = (__bridge dispatch_semaphore_t)frameSemaphore_;
|
|
476
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
477
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
sampleCount_ = n;
|
|
482
|
+
buildRenderPipelines();
|
|
483
|
+
createDepthTexture();
|
|
484
|
+
|
|
485
|
+
if (frameSemaphore_) {
|
|
486
|
+
dispatch_semaphore_t sem = (__bridge dispatch_semaphore_t)frameSemaphore_;
|
|
487
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
488
|
+
dispatch_semaphore_signal(sem);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
void MetalBackend::resize(int width, int height) {
|
|
494
|
+
viewportWidth_ = width;
|
|
495
|
+
viewportHeight_ = height;
|
|
496
|
+
createDepthTexture();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
void MetalBackend::shutdown() {
|
|
500
|
+
// Drain in-flight frames: wait until all GPU completion handlers have fired
|
|
501
|
+
// before releasing the semaphore and the persistent buffers they reference.
|
|
502
|
+
if (frameSemaphore_) {
|
|
503
|
+
dispatch_semaphore_t sem = (__bridge dispatch_semaphore_t)frameSemaphore_;
|
|
504
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
505
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
506
|
+
}
|
|
507
|
+
CFRelease(frameSemaphore_);
|
|
508
|
+
frameSemaphore_ = nullptr;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
512
|
+
if (vtxBufs_[i]) { CFRelease(vtxBufs_[i]); vtxBufs_[i] = nullptr; }
|
|
513
|
+
if (idxBufs_[i]) { CFRelease(idxBufs_[i]); idxBufs_[i] = nullptr; }
|
|
514
|
+
if (uvBufs_[i]) { CFRelease(uvBufs_[i]); uvBufs_[i] = nullptr; }
|
|
515
|
+
if (altBufs_[i]) { CFRelease(altBufs_[i]); altBufs_[i] = nullptr; }
|
|
516
|
+
vtxCaps_[i] = idxCaps_[i] = uvCaps_[i] = altCaps_[i] = 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (device_) { CFRelease(device_); device_ = nullptr; }
|
|
520
|
+
if (commandQueue_) { CFRelease(commandQueue_); commandQueue_ = nullptr; }
|
|
521
|
+
if (terrainPipeline_) { CFRelease(terrainPipeline_); terrainPipeline_ = nullptr; }
|
|
522
|
+
if (skyPipeline_) { CFRelease(skyPipeline_); skyPipeline_ = nullptr; }
|
|
523
|
+
if (terrainDepthState_) { CFRelease(terrainDepthState_); terrainDepthState_ = nullptr; }
|
|
524
|
+
if (skyDepthState_) { CFRelease(skyDepthState_); skyDepthState_ = nullptr; }
|
|
525
|
+
if (depthTexture_) { CFRelease(depthTexture_); depthTexture_ = nullptr; }
|
|
526
|
+
if (fallbackTexture_) { CFRelease(fallbackTexture_); fallbackTexture_ = nullptr; }
|
|
527
|
+
if (fallbackWaterMaskTexture_) { CFRelease(fallbackWaterMaskTexture_); fallbackWaterMaskTexture_= nullptr; }
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
void MetalBackend::createDepthTexture() {
|
|
531
|
+
if (viewportWidth_ <= 0 || viewportHeight_ <= 0 || !device_) return;
|
|
532
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
533
|
+
|
|
534
|
+
if (depthTexture_) { CFRelease(depthTexture_); depthTexture_ = nullptr; }
|
|
535
|
+
|
|
536
|
+
MTLTextureDescriptor* dd =
|
|
537
|
+
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatDepth32Float
|
|
538
|
+
width:viewportWidth_
|
|
539
|
+
height:viewportHeight_
|
|
540
|
+
mipmapped:NO];
|
|
541
|
+
const int sc = std::max(1, sampleCount_);
|
|
542
|
+
if (sc > 1) {
|
|
543
|
+
dd.textureType = MTLTextureType2DMultisample;
|
|
544
|
+
dd.sampleCount = (NSUInteger)sc;
|
|
545
|
+
}
|
|
546
|
+
dd.storageMode = MTLStorageModePrivate;
|
|
547
|
+
dd.usage = MTLTextureUsageRenderTarget;
|
|
548
|
+
depthTexture_ = (__bridge_retained void*)[dev newTextureWithDescriptor:dd];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void MetalBackend::createFallbackTexture() {
|
|
552
|
+
if (!device_) return;
|
|
553
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
554
|
+
|
|
555
|
+
MTLTextureDescriptor* td =
|
|
556
|
+
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
|
557
|
+
width:1
|
|
558
|
+
height:1
|
|
559
|
+
mipmapped:NO];
|
|
560
|
+
td.usage = MTLTextureUsageShaderRead;
|
|
561
|
+
td.storageMode = MTLStorageModeShared;
|
|
562
|
+
id<MTLTexture> tex = [dev newTextureWithDescriptor:td];
|
|
563
|
+
|
|
564
|
+
const uint8_t white[4] = {255, 255, 255, 255};
|
|
565
|
+
[tex replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
|
|
566
|
+
mipmapLevel:0
|
|
567
|
+
withBytes:white
|
|
568
|
+
bytesPerRow:4];
|
|
569
|
+
fallbackTexture_ = (__bridge_retained void*)tex;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
void MetalBackend::createWaterMaskFallback() {
|
|
573
|
+
if (!device_) return;
|
|
574
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
575
|
+
|
|
576
|
+
if (fallbackWaterMaskTexture_) {
|
|
577
|
+
CFRelease(fallbackWaterMaskTexture_);
|
|
578
|
+
fallbackWaterMaskTexture_ = nullptr;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
MTLTextureDescriptor* td =
|
|
582
|
+
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
|
583
|
+
width:1
|
|
584
|
+
height:1
|
|
585
|
+
mipmapped:NO];
|
|
586
|
+
td.usage = MTLTextureUsageShaderRead;
|
|
587
|
+
td.storageMode = MTLStorageModeShared;
|
|
588
|
+
id<MTLTexture> tex = [dev newTextureWithDescriptor:td];
|
|
589
|
+
|
|
590
|
+
// All-zero = land; fwidth(0.0) = 0.0 so coastA = 0 for solid-land tiles.
|
|
591
|
+
const uint8_t zero[4] = {0, 0, 0, 255};
|
|
592
|
+
[tex replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
|
|
593
|
+
mipmapLevel:0
|
|
594
|
+
withBytes:zero
|
|
595
|
+
bytesPerRow:4];
|
|
596
|
+
fallbackWaterMaskTexture_ = (__bridge_retained void*)tex;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
void* MetalBackend::createRasterTexture(const uint8_t* pixels,
|
|
600
|
+
int32_t width,
|
|
601
|
+
int32_t height) {
|
|
602
|
+
if (!device_ || !pixels || width <= 0 || height <= 0) return nullptr;
|
|
603
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
604
|
+
|
|
605
|
+
MTLTextureDescriptor* td =
|
|
606
|
+
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
|
607
|
+
width:(NSUInteger)width
|
|
608
|
+
height:(NSUInteger)height
|
|
609
|
+
mipmapped:NO];
|
|
610
|
+
td.usage = MTLTextureUsageShaderRead;
|
|
611
|
+
td.storageMode = MTLStorageModeShared;
|
|
612
|
+
|
|
613
|
+
id<MTLTexture> tex = [dev newTextureWithDescriptor:td];
|
|
614
|
+
if (!tex) return nullptr;
|
|
615
|
+
|
|
616
|
+
[tex replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height)
|
|
617
|
+
mipmapLevel:0
|
|
618
|
+
withBytes:pixels
|
|
619
|
+
bytesPerRow:(NSUInteger)(width * 4)];
|
|
620
|
+
|
|
621
|
+
return (__bridge_retained void*)tex;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ── Per-frame ─────────────────────────────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
void MetalBackend::beginFrame(const FrameParams& /*params*/) {
|
|
627
|
+
// Acquire a frame slot. Blocks if kMaxFramesInFlight frames are already
|
|
628
|
+
// queued on the GPU. The completion handler in endFrame() signals the
|
|
629
|
+
// semaphore to release the slot.
|
|
630
|
+
dispatch_semaphore_wait((__bridge dispatch_semaphore_t)frameSemaphore_,
|
|
631
|
+
DISPATCH_TIME_FOREVER);
|
|
632
|
+
frameIndex_ = (frameIndex_ + 1) % kMaxFramesInFlight;
|
|
633
|
+
|
|
634
|
+
CAMetalLayer* layer = (__bridge CAMetalLayer*)metalLayer_;
|
|
635
|
+
id<MTLCommandQueue> cq = (__bridge id<MTLCommandQueue>)commandQueue_;
|
|
636
|
+
|
|
637
|
+
// Sync depth texture with actual drawable size if it changed.
|
|
638
|
+
CGSize drawSize = layer.drawableSize;
|
|
639
|
+
int dw = (int)drawSize.width, dh = (int)drawSize.height;
|
|
640
|
+
if (dw > 0 && dh > 0) {
|
|
641
|
+
bool needsCreate = !depthTexture_;
|
|
642
|
+
if (depthTexture_) {
|
|
643
|
+
id<MTLTexture> dt = (__bridge id<MTLTexture>)depthTexture_;
|
|
644
|
+
needsCreate = ((int)dt.width != dw || (int)dt.height != dh ||
|
|
645
|
+
(int)dt.sampleCount != std::max(1, sampleCount_));
|
|
646
|
+
}
|
|
647
|
+
if (needsCreate) {
|
|
648
|
+
viewportWidth_ = dw;
|
|
649
|
+
viewportHeight_ = dh;
|
|
650
|
+
createDepthTexture();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
|
655
|
+
if (!drawable) {
|
|
656
|
+
// No drawable available — restore semaphore slot and bail.
|
|
657
|
+
dispatch_semaphore_signal((__bridge dispatch_semaphore_t)frameSemaphore_);
|
|
658
|
+
currentDrawable_ = nullptr;
|
|
659
|
+
currentCommandBuffer_ = nullptr;
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
currentDrawable_ = (__bridge_retained void*)drawable;
|
|
663
|
+
|
|
664
|
+
id<MTLCommandBuffer> cb = [cq commandBuffer];
|
|
665
|
+
currentCommandBuffer_ = (__bridge_retained void*)cb;
|
|
666
|
+
|
|
667
|
+
MTLRenderPassDescriptor* rp = [MTLRenderPassDescriptor new];
|
|
668
|
+
rp.colorAttachments[0].texture = drawable.texture;
|
|
669
|
+
rp.colorAttachments[0].loadAction = MTLLoadActionClear;
|
|
670
|
+
rp.colorAttachments[0].storeAction = MTLStoreActionStore;
|
|
671
|
+
rp.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1);
|
|
672
|
+
|
|
673
|
+
if (depthTexture_) {
|
|
674
|
+
rp.depthAttachment.texture = (__bridge id<MTLTexture>)depthTexture_;
|
|
675
|
+
rp.depthAttachment.loadAction = MTLLoadActionClear;
|
|
676
|
+
rp.depthAttachment.storeAction = MTLStoreActionDontCare;
|
|
677
|
+
rp.depthAttachment.clearDepth = 0.0; // reversed-Z: 0 = far
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
id<MTLRenderCommandEncoder> enc =
|
|
681
|
+
[cb renderCommandEncoderWithDescriptor:rp];
|
|
682
|
+
currentEncoder_ = (__bridge_retained void*)enc;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
void MetalBackend::drawScene(const FrameResult& frame) {
|
|
686
|
+
if (!currentEncoder_ || !currentCommandBuffer_ || !currentDrawable_) return;
|
|
687
|
+
|
|
688
|
+
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)currentEncoder_;
|
|
689
|
+
id<MTLDevice> dev = (__bridge id<MTLDevice>)device_;
|
|
690
|
+
|
|
691
|
+
// ── Sky pass ──────────────────────────────────────────────────────────────
|
|
692
|
+
if (skyPipeline_ && skyDepthState_) {
|
|
693
|
+
SkyUniforms skyU{};
|
|
694
|
+
const float* iv = glm::value_ptr(frame.invVP);
|
|
695
|
+
for (int i = 0; i < 16; ++i) skyU.invVP[i] = iv[i];
|
|
696
|
+
skyU.cameraEcef[0] = frame.cameraEcef.x;
|
|
697
|
+
skyU.cameraEcef[1] = frame.cameraEcef.y;
|
|
698
|
+
skyU.cameraEcef[2] = frame.cameraEcef.z;
|
|
699
|
+
float cl = sqrtf(frame.cameraEcef.x * frame.cameraEcef.x +
|
|
700
|
+
frame.cameraEcef.y * frame.cameraEcef.y +
|
|
701
|
+
frame.cameraEcef.z * frame.cameraEcef.z);
|
|
702
|
+
if (cl > 0.0f) {
|
|
703
|
+
skyU.lightDir[0] = frame.cameraEcef.x / cl;
|
|
704
|
+
skyU.lightDir[1] = frame.cameraEcef.y / cl;
|
|
705
|
+
skyU.lightDir[2] = frame.cameraEcef.z / cl;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
[enc setRenderPipelineState:(__bridge id<MTLRenderPipelineState>)skyPipeline_];
|
|
709
|
+
[enc setDepthStencilState:(__bridge id<MTLDepthStencilState>)skyDepthState_];
|
|
710
|
+
[enc setCullMode:MTLCullModeNone];
|
|
711
|
+
[enc setVertexBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
712
|
+
[enc setFragmentBytes:&skyU length:sizeof(skyU) atIndex:0];
|
|
713
|
+
[enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ── Terrain pass ──────────────────────────────────────────────────────────
|
|
717
|
+
if (frame.draws.empty() || frame.localPositions.empty() || frame.indices.empty() ||
|
|
718
|
+
!terrainPipeline_ || !terrainDepthState_) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const int fi = frameIndex_;
|
|
723
|
+
|
|
724
|
+
// Upload merged geometry to the current frame's persistent Metal buffers.
|
|
725
|
+
const size_t vtxBytes = frame.localPositions.size() * sizeof(float);
|
|
726
|
+
const size_t idxBytes = frame.indices.size() * sizeof(uint32_t);
|
|
727
|
+
const size_t uvBytes = frame.uvs.size() * sizeof(float);
|
|
728
|
+
const size_t altBytes = frame.altitudes.size() * sizeof(float);
|
|
729
|
+
|
|
730
|
+
id<MTLBuffer> vtxBuf = ensureBuffer(&vtxBufs_[fi], &vtxCaps_[fi],
|
|
731
|
+
vtxBytes, frame.localPositions.data(), dev);
|
|
732
|
+
id<MTLBuffer> idxBuf = ensureBuffer(&idxBufs_[fi], &idxCaps_[fi],
|
|
733
|
+
idxBytes, frame.indices.data(), dev);
|
|
734
|
+
id<MTLBuffer> uvBuf = ensureBuffer(&uvBufs_[fi], &uvCaps_[fi],
|
|
735
|
+
uvBytes, frame.uvs.data(), dev);
|
|
736
|
+
id<MTLBuffer> altBuf = ensureBuffer(&altBufs_[fi], &altCaps_[fi],
|
|
737
|
+
altBytes, frame.altitudes.data(), dev);
|
|
738
|
+
|
|
739
|
+
if (!vtxBuf || !idxBuf || !altBuf) return;
|
|
740
|
+
|
|
741
|
+
[enc setRenderPipelineState:(__bridge id<MTLRenderPipelineState>)terrainPipeline_];
|
|
742
|
+
[enc setDepthStencilState:(__bridge id<MTLDepthStencilState>)terrainDepthState_];
|
|
743
|
+
[enc setFrontFacingWinding:MTLWindingCounterClockwise];
|
|
744
|
+
[enc setCullMode:MTLCullModeBack];
|
|
745
|
+
|
|
746
|
+
// Set vertex buffers that are shared across all draws.
|
|
747
|
+
// buffer(0): tile-local positions, buffer(1): altitudes, buffer(3): UVs.
|
|
748
|
+
// buffer(2) is the per-draw MVP set in the loop below.
|
|
749
|
+
[enc setVertexBuffer:vtxBuf offset:0 atIndex:0];
|
|
750
|
+
[enc setVertexBuffer:altBuf offset:0 atIndex:1];
|
|
751
|
+
if (uvBuf) [enc setVertexBuffer:uvBuf offset:0 atIndex:3];
|
|
752
|
+
|
|
753
|
+
id<MTLTexture> fallbackTex = (__bridge id<MTLTexture>)fallbackTexture_;
|
|
754
|
+
id<MTLTexture> fallbackWMTex = (__bridge id<MTLTexture>)fallbackWaterMaskTexture_;
|
|
755
|
+
|
|
756
|
+
if (fallbackWMTex) [enc setFragmentTexture:fallbackWMTex atIndex:1];
|
|
757
|
+
|
|
758
|
+
for (const auto& draw : frame.draws) {
|
|
759
|
+
if (draw.indexCount == 0) continue;
|
|
760
|
+
|
|
761
|
+
// ── Per-draw vertex uniforms: the tile's RTC MVP matrix ───────────────
|
|
762
|
+
PerDrawVertexUniformsCPP vdu{};
|
|
763
|
+
const float* mp = glm::value_ptr(draw.mvpMatrix);
|
|
764
|
+
for (int i = 0; i < 16; ++i) vdu.mvpMatrix[i] = mp[i];
|
|
765
|
+
[enc setVertexBytes:&vdu length:sizeof(vdu) atIndex:2];
|
|
766
|
+
|
|
767
|
+
// ── Per-draw fragment uniforms ─────────────────────────────────────────
|
|
768
|
+
OverlayParamsCPP ov{};
|
|
769
|
+
ov.hasOverlay = (draw.overlayTexture && draw.hasUVs) ? 1u : 0u;
|
|
770
|
+
ov.isEllipsoidFallback = draw.isEllipsoidFallback ? 1u : 0u;
|
|
771
|
+
ov.isOnlyWater = draw.isOnlyWater ? 1u : 0u;
|
|
772
|
+
ov.hasWaterMask = draw.waterMaskTexture ? 1u : 0u;
|
|
773
|
+
ov.translation[0] = draw.overlayTranslation.x;
|
|
774
|
+
ov.translation[1] = draw.overlayTranslation.y;
|
|
775
|
+
ov.scale[0] = draw.overlayScale.x;
|
|
776
|
+
ov.scale[1] = draw.overlayScale.y;
|
|
777
|
+
ov.wmWest = draw.wmTileBounds.x;
|
|
778
|
+
ov.wmSouth = draw.wmTileBounds.y;
|
|
779
|
+
ov.wmEast = draw.wmTileBounds.z;
|
|
780
|
+
ov.wmNorth = draw.wmTileBounds.w;
|
|
781
|
+
ov.wmTransX = draw.wmTranslation.x;
|
|
782
|
+
ov.wmTransY = draw.wmTranslation.y;
|
|
783
|
+
ov.wmScale = draw.wmScale;
|
|
784
|
+
// RTC: tile centre and camera-in-tile-space for fragment world-position
|
|
785
|
+
// reconstruction and view-direction lighting.
|
|
786
|
+
ov.rtcCenterX = draw.rtcCenterEcef.x;
|
|
787
|
+
ov.rtcCenterY = draw.rtcCenterEcef.y;
|
|
788
|
+
ov.rtcCenterZ = draw.rtcCenterEcef.z;
|
|
789
|
+
// cameraTilespace = cameraEcef − rtcCentre. Calculated here to keep the
|
|
790
|
+
// fragment shader free of subtraction of two large float3 values.
|
|
791
|
+
ov.cameraTilespaceX = frame.cameraEcef.x - draw.rtcCenterEcef.x;
|
|
792
|
+
ov.cameraTilespaceY = frame.cameraEcef.y - draw.rtcCenterEcef.y;
|
|
793
|
+
ov.cameraTilespaceZ = frame.cameraEcef.z - draw.rtcCenterEcef.z;
|
|
794
|
+
[enc setFragmentBytes:&ov length:sizeof(ov) atIndex:0];
|
|
795
|
+
|
|
796
|
+
id<MTLTexture> tex = (draw.overlayTexture && draw.hasUVs)
|
|
797
|
+
? (__bridge id<MTLTexture>)draw.overlayTexture
|
|
798
|
+
: fallbackTex;
|
|
799
|
+
if (tex) [enc setFragmentTexture:tex atIndex:0];
|
|
800
|
+
|
|
801
|
+
id<MTLTexture> wmTex = draw.waterMaskTexture
|
|
802
|
+
? (__bridge id<MTLTexture>)draw.waterMaskTexture
|
|
803
|
+
: fallbackWMTex;
|
|
804
|
+
if (wmTex) [enc setFragmentTexture:wmTex atIndex:1];
|
|
805
|
+
|
|
806
|
+
[enc drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
|
807
|
+
indexCount:draw.indexCount
|
|
808
|
+
indexType:MTLIndexTypeUInt32
|
|
809
|
+
indexBuffer:idxBuf
|
|
810
|
+
indexBufferOffset:draw.indexByteOffset];
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
void MetalBackend::endFrame() {
|
|
815
|
+
if (!currentCommandBuffer_ || !currentDrawable_) return;
|
|
816
|
+
|
|
817
|
+
id<MTLRenderCommandEncoder> enc =
|
|
818
|
+
(__bridge_transfer id<MTLRenderCommandEncoder>)currentEncoder_;
|
|
819
|
+
[enc endEncoding];
|
|
820
|
+
currentEncoder_ = nullptr;
|
|
821
|
+
|
|
822
|
+
id<CAMetalDrawable> drawable =
|
|
823
|
+
(__bridge_transfer id<CAMetalDrawable>)currentDrawable_;
|
|
824
|
+
id<MTLCommandBuffer> cb =
|
|
825
|
+
(__bridge_transfer id<MTLCommandBuffer>)currentCommandBuffer_;
|
|
826
|
+
|
|
827
|
+
// Signal the frame semaphore when the GPU is done with this frame's buffers.
|
|
828
|
+
void* semPtr = frameSemaphore_;
|
|
829
|
+
[cb addCompletedHandler:^(id<MTLCommandBuffer> __unused) {
|
|
830
|
+
dispatch_semaphore_signal((__bridge dispatch_semaphore_t)semPtr);
|
|
831
|
+
}];
|
|
832
|
+
|
|
833
|
+
[cb presentDrawable:drawable];
|
|
834
|
+
[cb commit];
|
|
835
|
+
currentDrawable_ = nullptr;
|
|
836
|
+
currentCommandBuffer_ = nullptr;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
} // namespace reactnativecesium
|